mirror of
https://github.com/JulianGro/overte.git
synced 2025-08-13 14:19:38 +02:00
Merging back with upstream and trying to fix counter for them to be more stable
This commit is contained in:
commit
f274ef1a59
1087 changed files with 51251 additions and 21523 deletions
|
@ -28,6 +28,7 @@ module.exports = {
|
|||
"ModelCache": false,
|
||||
"MyAvatar": false,
|
||||
"Overlays": false,
|
||||
"OverlayWebWindow": false,
|
||||
"Paths": false,
|
||||
"Quat": false,
|
||||
"Rates": false,
|
||||
|
@ -40,8 +41,10 @@ module.exports = {
|
|||
"SoundCache": false,
|
||||
"Stats": false,
|
||||
"TextureCache": false,
|
||||
"Toolbars": false,
|
||||
"Uuid": false,
|
||||
"UndoStack": false,
|
||||
"UserActivityLogger": false,
|
||||
"Vec3": false,
|
||||
"WebSocket": false,
|
||||
"WebWindow": false,
|
||||
|
@ -55,6 +58,7 @@ module.exports = {
|
|||
"comma-dangle": ["error", "never"],
|
||||
"camelcase": ["error"],
|
||||
"curly": ["error", "all"],
|
||||
"eqeqeq": ["error", "always"],
|
||||
"indent": ["error", 4, { "SwitchCase": 1 }],
|
||||
"keyword-spacing": ["error", { "before": true, "after": true }],
|
||||
"max-len": ["error", 128, 4],
|
||||
|
@ -68,6 +72,6 @@ module.exports = {
|
|||
"spaced-comment": ["error", "always", {
|
||||
"line": { "markers": ["/"] }
|
||||
}],
|
||||
"space-before-function-paren": ["error", {"anonymous": "always", "named": "never"}]
|
||||
"space-before-function-paren": ["error", {"anonymous": "ignore", "named": "never"}]
|
||||
}
|
||||
};
|
||||
|
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -45,7 +45,7 @@ gvr-interface/libs/*
|
|||
|
||||
# ignore files for various dev environments
|
||||
TAGS
|
||||
*.swp
|
||||
*.sw[po]
|
||||
|
||||
# ignore node files for the console
|
||||
node_modules
|
||||
|
|
13
BUILD.md
13
BUILD.md
|
@ -1,7 +1,7 @@
|
|||
###Dependencies
|
||||
|
||||
* [cmake](http://www.cmake.org/cmake/resources/software.html) ~> 3.3.2
|
||||
* [Qt](http://www.qt.io/download-open-source) ~> 5.5.1
|
||||
* [Qt](http://www.qt.io/download-open-source) ~> 5.6.1
|
||||
* [OpenSSL](https://www.openssl.org/community/binaries.html) ~> 1.0.1m
|
||||
* IMPORTANT: Using the recommended version of OpenSSL is critical to avoid security vulnerabilities.
|
||||
* [VHACD](https://github.com/virneo/v-hacd)(clone this repository)(Optional)
|
||||
|
@ -18,6 +18,7 @@
|
|||
* [oglplus](http://oglplus.org/) ~> 0.63
|
||||
* [OpenVR](https://github.com/ValveSoftware/openvr) ~> 0.91 (Win32 only)
|
||||
* [Polyvox](http://www.volumesoffun.com/) ~> 0.2.1
|
||||
* [QuaZip](http://sourceforge.net/projects/quazip/files/quazip/) ~> 0.7.1
|
||||
* [SDL2](https://www.libsdl.org/download-2.0.php) ~> 2.0.3
|
||||
* [soxr](http://soxr.sourceforge.net) ~> 0.1.1
|
||||
* [Intel Threading Building Blocks](https://www.threadingbuildingblocks.org/) ~> 4.3
|
||||
|
@ -40,14 +41,14 @@ If you would like to use a specific install of a dependency instead of the versi
|
|||
Hifi uses CMake to generate build files and project files for your platform.
|
||||
|
||||
####Qt
|
||||
In order for CMake to find the Qt5 find modules, you will need to set an ENV variable pointing to your Qt installation.
|
||||
In order for CMake to find the Qt5 find modules, you will need to set a QT_CMAKE_PREFIX_PATH environment variable pointing to your Qt installation.
|
||||
|
||||
For example, a Qt5 5.5.1 installation to /usr/local/qt5 would require that QT_CMAKE_PREFIX_PATH be set with the following command. This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
|
||||
This can either be entered directly into your shell session before you build or in your shell profile (e.g.: ~/.bash_profile, ~/.bashrc, ~/.zshrc - this depends on your shell and environment).
|
||||
|
||||
The path it needs to be set to will depend on where and how Qt5 was installed. e.g.
|
||||
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.5.1/clang_64/lib/cmake/
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.5.1/lib/cmake
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.1/clang_64/lib/cmake/
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.1-1/lib/cmake
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/opt/qt5/lib/cmake
|
||||
|
||||
####Generating build files
|
||||
|
@ -64,7 +65,7 @@ Any variables that need to be set for CMake to find dependencies can be set as E
|
|||
|
||||
For example, to pass the QT_CMAKE_PREFIX_PATH variable during build file generation:
|
||||
|
||||
cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.5.1/lib/cmake
|
||||
cmake .. -DQT_CMAKE_PREFIX_PATH=/usr/local/qt/5.6.1/lib/cmake
|
||||
|
||||
####Finding Dependencies
|
||||
|
||||
|
|
|
@ -4,13 +4,11 @@ Please read the [general build guide](BUILD.md) for information on dependencies
|
|||
|
||||
You will need the following tools to build our Android targets.
|
||||
|
||||
* [cmake](http://www.cmake.org/download/) ~> 3.1.0
|
||||
* Note that this is a newer version required than the minimum for hifi desktop targets.
|
||||
* [Qt](http://www.qt.io/download-open-source/#) ~> 5.4.0
|
||||
* Note that this is a newer version required than the minimum for hifi desktop targets.
|
||||
* [cmake](http://www.cmake.org/download/) ~> 3.5.1
|
||||
* [Qt](http://www.qt.io/download-open-source/#) ~> 5.5.1
|
||||
* [ant](http://ant.apache.org/bindownload.cgi) ~> 1.9.4
|
||||
* [Android NDK](https://developer.android.com/tools/sdk/ndk/index.html) = r10c
|
||||
* [Android SDK](http://developer.android.com/sdk/installing/index.html) ~> 24.0.2
|
||||
* [Android NDK](https://developer.android.com/tools/sdk/ndk/index.html) ~> r10d
|
||||
* [Android SDK](http://developer.android.com/sdk/installing/index.html) ~> 24.4.1.1
|
||||
* Install the latest Platform-tools
|
||||
* Install the latest Build-tools
|
||||
* Install the SDK Platform for API Level 19
|
||||
|
@ -19,6 +17,12 @@ You will need the following tools to build our Android targets.
|
|||
|
||||
You will also need to cross-compile the dependencies required for all platforms for Android, and help CMake find these compiled libraries on your machine.
|
||||
|
||||
####Scribe
|
||||
|
||||
High Fidelity has a shader pre-processing tool called `scribe` that various libraries will call on during the build process. You must compile scribe using your native toolchain (following the build instructions for your platform) and then pass a CMake variable or set an ENV variable `SCRIBE_PATH` that is a path to the scribe executable.
|
||||
|
||||
CMake will fatally error if it does not find the scribe executable while using the android toolchain.
|
||||
|
||||
####Optional Components
|
||||
|
||||
* [Oculus Mobile SDK](https://developer.oculus.com/downloads/#sdk=mobile) ~> 0.4.2
|
||||
|
@ -31,11 +35,11 @@ This is most easily accomplished by installing all Android dependencies in the s
|
|||
|
||||
####Qt
|
||||
|
||||
Install Qt 5.4 for Android for your host environment from the [Qt downloads page](http://www.qt.io/download/). Install Qt to ``$ANDROID_LIB_DIR/Qt``. This is required so that our root CMakeLists file can help CMake find your Android Qt installation.
|
||||
Install Qt 5.5.1 for Android for your host environment from the [Qt downloads page](http://www.qt.io/download/). Install Qt to ``$ANDROID_LIB_DIR/Qt``. This is required so that our root CMakeLists file can help CMake find your Android Qt installation.
|
||||
|
||||
The component required for the Android build is the `Android armv7` component.
|
||||
|
||||
If you would like to install Qt to a different location, or attempt to build with a different Qt version, you can pass `ANDROID_QT_CMAKE_PREFIX_PATH` to CMake. Point to the `cmake` folder inside `$VERSION_NUMBER/android_armv7/lib`. Otherwise, our root CMakeLists will set it to `$ANDROID_LIB_DIR/Qt/5.3/android_armv7/lib/cmake`.
|
||||
If you would like to install Qt to a different location, or attempt to build with a different Qt version, you can pass `ANDROID_QT_CMAKE_PREFIX_PATH` to CMake. Point to the `cmake` folder inside `$VERSION_NUMBER/android_armv7/lib`. Otherwise, our root CMakeLists will set it to `$ANDROID_LIB_DIR/Qt/5.5/android_armv7/lib/cmake`.
|
||||
|
||||
####OpenSSL
|
||||
|
||||
|
|
26
BUILD_OSX.md
26
BUILD_OSX.md
|
@ -1,25 +1,31 @@
|
|||
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only OS X specific instructions are found in this file.
|
||||
|
||||
###Homebrew
|
||||
[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of all High Fidelity dependencies very simple.
|
||||
[Homebrew](http://brew.sh/) is an excellent package manager for OS X. It makes install of some High Fidelity dependencies very simple.
|
||||
|
||||
brew tap homebrew/versions
|
||||
brew install cmake openssl qt55
|
||||
brew install cmake openssl
|
||||
|
||||
We no longer require install of qt5 via our [homebrew formulas repository](https://github.com/highfidelity/homebrew-formulas). Versions of Qt that are 5.5.x provide a mechanism to disable the wireless scanning we previously had a custom patch for.
|
||||
###OpenSSL
|
||||
|
||||
###OpenSSL and Qt
|
||||
|
||||
Assuming you've installed OpenSSL or Qt 5 using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR and QT_CMAKE_PREFIX_PATH so CMake can find your installations.
|
||||
Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR so CMake can find your installations.
|
||||
For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR:
|
||||
|
||||
export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2h_1/
|
||||
|
||||
For Qt 5.5.1 installed via homebrew, set QT_CMAKE_PREFIX_PATH as follows.
|
||||
|
||||
export QT_CMAKE_PREFIX_PATH=/usr/local/Cellar/qt55/5.5.1/lib/cmake
|
||||
Note that this uses the version from the homebrew formula at the time of this writing, and the version in the path will likely change.
|
||||
|
||||
Note that these use the versions from homebrew formulae at the time of this writing, and the version in the path will likely change.
|
||||
###Qt
|
||||
You can use the online installer or the offline installer.
|
||||
|
||||
* [Download the online installer](http://www.qt.io/download-open-source/#section-2)
|
||||
* When it asks you to select components, select the following:
|
||||
* Qt > Qt 5.6
|
||||
|
||||
* [Download the offline installer](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-mac-x64-clang-5.6.1-1.dmg)
|
||||
|
||||
Once Qt is installed, you need to manually configure the following:
|
||||
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt5.6.1/5.6/clang_64/lib/cmake/` directory.
|
||||
|
||||
###Xcode
|
||||
If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles.
|
||||
|
|
12
BUILD_WIN.md
12
BUILD_WIN.md
|
@ -27,17 +27,17 @@ We expect nmake.exe to be located at the following path.
|
|||
###Qt
|
||||
You can use the online installer or the offline installer. If you use the offline installer, be sure to select the "OpenGL" version.
|
||||
|
||||
* [Download the online installer](http://qt-project.org/downloads)
|
||||
* [Download the online installer](http://www.qt.io/download-open-source/#section-2)
|
||||
* When it asks you to select components, ONLY select one of the following, 32- or 64-bit to match your build preference:
|
||||
* Qt > Qt 5.5.1 > **msvc2013 32-bit**
|
||||
* Qt > Qt 5.5.1 > **msvc2013 64-bit**
|
||||
* Qt > Qt 5.6.1 > **msvc2013 32-bit**
|
||||
* Qt > Qt 5.6.1 > **msvc2013 64-bit**
|
||||
|
||||
* Download the offline installer, 32- or 64-bit to match your build preference:
|
||||
* [32-bit](http://download.qt.io/official_releases/qt/5.5/5.5.1/qt-opensource-windows-x86-msvc2013-5.5.1.exe)
|
||||
* [64-bit](http://download.qt.io/official_releases/qt/5.5/5.5.1/qt-opensource-windows-x86-msvc2013_64-5.5.1.exe)
|
||||
* [32-bit](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013-5.6.1-1.exe)
|
||||
* [64-bit](http://download.qt.io/official_releases/qt/5.6/5.6.1-1/qt-opensource-windows-x86-msvc2013_64-5.6.1-1.exe)
|
||||
|
||||
Once Qt is installed, you need to manually configure the following:
|
||||
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.5.1\msvc2013\lib\cmake` or `Qt\5.5.1\msvc2013_64\lib\cmake` directory.
|
||||
* Set the QT_CMAKE_PREFIX_PATH environment variable to your `Qt\5.6.1\msvc2013\lib\cmake` or `Qt\5.6.1\msvc2013_64\lib\cmake` directory.
|
||||
* You can set an environment variable from Control Panel > System > Advanced System Settings > Environment Variables > New
|
||||
|
||||
###External Libraries
|
||||
|
|
|
@ -3,6 +3,8 @@ cmake_minimum_required(VERSION 3.2)
|
|||
if (USE_ANDROID_TOOLCHAIN)
|
||||
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/android/android.toolchain.cmake")
|
||||
set(ANDROID_NATIVE_API_LEVEL 19)
|
||||
set(ANDROID_TOOLCHAIN_NAME arm-linux-androideabi-clang3.5)
|
||||
set(ANDROID_STL c++_shared)
|
||||
endif ()
|
||||
|
||||
if (WIN32)
|
||||
|
@ -64,23 +66,38 @@ if (WIN32)
|
|||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /DEBUG /OPT:REF /OPT:ICF")
|
||||
else ()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -fno-strict-aliasing -Wno-unused-parameter")
|
||||
if (CMAKE_COMPILER_IS_GNUCC OR CMAKE_COMPILER_IS_GNUCXX)
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ggdb -Woverloaded-virtual -Wdouble-promotion")
|
||||
if (CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "5.1") # gcc 5.1 and on have suggest-override
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsuggest-override")
|
||||
endif ()
|
||||
endif ()
|
||||
endif(WIN32)
|
||||
|
||||
if ((NOT MSVC12) AND (NOT MSVC14))
|
||||
include(CheckCXXCompilerFlag)
|
||||
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
|
||||
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
|
||||
|
||||
if (COMPILER_SUPPORTS_CXX11)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
elseif(COMPILER_SUPPORTS_CXX0X)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
|
||||
else()
|
||||
message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS "5.3")
|
||||
# GLM 0.9.8 on Ubuntu 14 (gcc 4.4) has issues with the simd declarations
|
||||
add_definitions(-DGLM_FORCE_PURE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID)
|
||||
if ((NOT MSVC12) AND (NOT MSVC14))
|
||||
include(CheckCXXCompilerFlag)
|
||||
CHECK_CXX_COMPILER_FLAG("-std=c++11" COMPILER_SUPPORTS_CXX11)
|
||||
CHECK_CXX_COMPILER_FLAG("-std=c++0x" COMPILER_SUPPORTS_CXX0X)
|
||||
|
||||
if (COMPILER_SUPPORTS_CXX11)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
elseif(COMPILER_SUPPORTS_CXX0X)
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x")
|
||||
else()
|
||||
message(STATUS "The compiler ${CMAKE_CXX_COMPILER} has no C++11 support. Please use a different C++ compiler.")
|
||||
endif()
|
||||
endif ()
|
||||
else ()
|
||||
# assume that the toolchain selected for android has C++11 support
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
|
@ -95,7 +112,7 @@ endif ()
|
|||
|
||||
if (ANDROID)
|
||||
if (NOT ANDROID_QT_CMAKE_PREFIX_PATH)
|
||||
set(QT_CMAKE_PREFIX_PATH ${ANDROID_LIB_DIR}/Qt/5.4/android_armv7/lib/cmake)
|
||||
set(QT_CMAKE_PREFIX_PATH ${ANDROID_LIB_DIR}/Qt/5.5/android_armv7/lib/cmake)
|
||||
else ()
|
||||
set(QT_CMAKE_PREFIX_PATH ${ANDROID_QT_CMAKE_PREFIX_PATH})
|
||||
endif ()
|
||||
|
@ -201,6 +218,7 @@ set_property(DIRECTORY PROPERTY EP_PREFIX ${EXTERNAL_PROJECT_PREFIX})
|
|||
setup_externals_binary_dir()
|
||||
|
||||
option(USE_NSIGHT "Attempt to find the nSight libraries" 1)
|
||||
option(GET_QUAZIP "Get QuaZip library automatically as external project" 1)
|
||||
|
||||
|
||||
if (WIN32)
|
||||
|
@ -232,7 +250,9 @@ if (NOT ANDROID)
|
|||
endif()
|
||||
|
||||
if (ANDROID OR DESKTOP_GVR)
|
||||
add_subdirectory(interface)
|
||||
add_subdirectory(gvr-interface)
|
||||
add_subdirectory(plugins)
|
||||
endif ()
|
||||
|
||||
if (DEFINED ENV{HIFI_MEMORY_DEBUGGING})
|
||||
|
|
|
@ -2,6 +2,11 @@ set(TARGET_NAME assignment-client)
|
|||
|
||||
setup_hifi_project(Core Gui Network Script Quick Widgets WebSockets)
|
||||
|
||||
# Fix up the rpath so macdeployqt works
|
||||
if (APPLE)
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks")
|
||||
endif ()
|
||||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(
|
||||
audio avatars octree gpu model fbx entities
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <QtNetwork/QNetworkDiskCache>
|
||||
#include <QtNetwork/QNetworkRequest>
|
||||
#include <QtNetwork/QNetworkReply>
|
||||
#include <QThread>
|
||||
|
||||
#include <AssetClient.h>
|
||||
#include <AvatarHashMap.h>
|
||||
|
@ -27,12 +28,16 @@
|
|||
#include <ResourceCache.h>
|
||||
#include <ScriptCache.h>
|
||||
#include <SoundCache.h>
|
||||
#include <ScriptEngines.h>
|
||||
#include <UUID.h>
|
||||
|
||||
#include <recording/Deck.h>
|
||||
#include <recording/Recorder.h>
|
||||
#include <recording/Frame.h>
|
||||
|
||||
#include <plugins/CodecPlugin.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
|
||||
#include <WebSocketServerClass.h>
|
||||
#include <EntityScriptingInterface.h> // TODO: consider moving to scriptengine.h
|
||||
|
||||
|
@ -42,17 +47,14 @@
|
|||
#include "AbstractAudioInterface.h"
|
||||
|
||||
#include "Agent.h"
|
||||
#include "AvatarAudioTimer.h"
|
||||
|
||||
static const int RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES = 10;
|
||||
|
||||
Agent::Agent(ReceivedMessage& message) :
|
||||
ThreadedAssignment(message),
|
||||
_entityEditSender(),
|
||||
_receivedAudioStream(AudioConstants::NETWORK_FRAME_SAMPLES_STEREO, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES,
|
||||
InboundAudioStream::Settings(0, false, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, false,
|
||||
DEFAULT_WINDOW_STARVE_THRESHOLD, DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES,
|
||||
DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION, false))
|
||||
{
|
||||
_receivedAudioStream(RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES, RECEIVED_AUDIO_STREAM_CAPACITY_FRAMES) {
|
||||
DependencyManager::get<EntityScriptingInterface>()->setPacketSender(&_entityEditSender);
|
||||
|
||||
ResourceManager::init();
|
||||
|
@ -65,6 +67,8 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
DependencyManager::set<recording::Deck>();
|
||||
DependencyManager::set<recording::Recorder>();
|
||||
DependencyManager::set<RecordingScriptingInterface>();
|
||||
DependencyManager::set<ScriptCache>();
|
||||
DependencyManager::set<ScriptEngines>();
|
||||
|
||||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
|
||||
|
@ -75,6 +79,21 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
{ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||
this, "handleOctreePacket");
|
||||
packetReceiver.registerListener(PacketType::Jurisdiction, this, "handleJurisdictionPacket");
|
||||
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
||||
}
|
||||
|
||||
void Agent::playAvatarSound(SharedSoundPointer sound) {
|
||||
// this must happen on Agent's main thread
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "playAvatarSound", Q_ARG(SharedSoundPointer, sound));
|
||||
return;
|
||||
} else {
|
||||
// TODO: seems to add occasional artifact in tests. I believe it is
|
||||
// correct to do this, but need to figure out for sure, so commenting this
|
||||
// out until I verify.
|
||||
// _numAvatarSoundSentBytes = 0;
|
||||
setAvatarSound(sound);
|
||||
}
|
||||
}
|
||||
|
||||
void Agent::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
@ -121,7 +140,6 @@ void Agent::handleAudioPacket(QSharedPointer<ReceivedMessage> message) {
|
|||
_receivedAudioStream.parseData(*message);
|
||||
|
||||
_lastReceivedAudioLoudness = _receivedAudioStream.getNextOutputFrameLoudness();
|
||||
|
||||
_receivedAudioStream.clearBuffer();
|
||||
}
|
||||
|
||||
|
@ -217,6 +235,59 @@ void Agent::nodeActivated(SharedNodePointer activatedNode) {
|
|||
|
||||
_pendingScriptRequest = nullptr;
|
||||
}
|
||||
if (activatedNode->getType() == NodeType::AudioMixer) {
|
||||
negotiateAudioFormat();
|
||||
}
|
||||
}
|
||||
|
||||
void Agent::negotiateAudioFormat() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto negotiateFormatPacket = NLPacket::create(PacketType::NegotiateAudioFormat);
|
||||
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
|
||||
quint8 numberOfCodecs = (quint8)codecPlugins.size();
|
||||
negotiateFormatPacket->writePrimitive(numberOfCodecs);
|
||||
for (auto& plugin : codecPlugins) {
|
||||
auto codecName = plugin->getName();
|
||||
negotiateFormatPacket->writeString(codecName);
|
||||
}
|
||||
|
||||
// grab our audio mixer from the NodeList, if it exists
|
||||
SharedNodePointer audioMixer = nodeList->soloNodeOfType(NodeType::AudioMixer);
|
||||
|
||||
if (audioMixer) {
|
||||
// send off this mute packet
|
||||
nodeList->sendPacket(std::move(negotiateFormatPacket), *audioMixer);
|
||||
}
|
||||
}
|
||||
|
||||
void Agent::handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message) {
|
||||
QString selectedCodecName = message->readString();
|
||||
selectAudioFormat(selectedCodecName);
|
||||
}
|
||||
|
||||
void Agent::selectAudioFormat(const QString& selectedCodecName) {
|
||||
_selectedCodecName = selectedCodecName;
|
||||
|
||||
qDebug() << "Selected Codec:" << _selectedCodecName;
|
||||
|
||||
// release any old codec encoder/decoder first...
|
||||
if (_codec && _encoder) {
|
||||
_codec->releaseEncoder(_encoder);
|
||||
_encoder = nullptr;
|
||||
_codec = nullptr;
|
||||
}
|
||||
_receivedAudioStream.cleanupCodec();
|
||||
|
||||
auto codecPlugins = PluginManager::getInstance()->getCodecPlugins();
|
||||
for (auto& plugin : codecPlugins) {
|
||||
if (_selectedCodecName == plugin->getName()) {
|
||||
_codec = plugin;
|
||||
_receivedAudioStream.setupCodec(plugin, _selectedCodecName, AudioConstants::STEREO);
|
||||
_encoder = plugin->createEncoder(AudioConstants::SAMPLE_RATE, AudioConstants::MONO);
|
||||
qDebug() << "Selected Codec Plugin:" << _codec.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Agent::scriptRequestFinished() {
|
||||
|
@ -294,10 +365,6 @@ void Agent::executeScript() {
|
|||
// register ourselves to the script engine
|
||||
_scriptEngine->registerGlobalObject("Agent", this);
|
||||
|
||||
// FIXME -we shouldn't be calling this directly, it's normally called by run(), not sure why
|
||||
// viewers would need this called.
|
||||
//_scriptEngine->init(); // must be done before we set up the viewers
|
||||
|
||||
_scriptEngine->registerGlobalObject("SoundCache", DependencyManager::get<SoundCache>().data());
|
||||
|
||||
QScriptValue webSocketServerConstructorValue = _scriptEngine->newFunction(WebSocketServerClass::constructor);
|
||||
|
@ -317,10 +384,18 @@ void Agent::executeScript() {
|
|||
entityScriptingInterface->setEntityTree(_entityViewer.getTree());
|
||||
|
||||
DependencyManager::set<AssignmentParentFinder>(_entityViewer.getTree());
|
||||
|
||||
// wire up our additional agent related processing to the update signal
|
||||
QObject::connect(_scriptEngine.get(), &ScriptEngine::update, this, &Agent::processAgentAvatarAndAudio);
|
||||
|
||||
|
||||
// 100Hz timer for audio
|
||||
AvatarAudioTimer* audioTimerWorker = new AvatarAudioTimer();
|
||||
audioTimerWorker->moveToThread(&_avatarAudioTimerThread);
|
||||
connect(audioTimerWorker, &AvatarAudioTimer::avatarTick, this, &Agent::processAgentAvatarAudio);
|
||||
connect(this, &Agent::startAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::start);
|
||||
connect(this, &Agent::stopAvatarAudioTimer, audioTimerWorker, &AvatarAudioTimer::stop);
|
||||
connect(&_avatarAudioTimerThread, &QThread::finished, audioTimerWorker, &QObject::deleteLater);
|
||||
_avatarAudioTimerThread.start();
|
||||
|
||||
// 60Hz timer for avatar
|
||||
QObject::connect(_scriptEngine.get(), &ScriptEngine::update, this, &Agent::processAgentAvatar);
|
||||
_scriptEngine->run();
|
||||
|
||||
Frame::clearFrameHandler(AUDIO_FRAME_TYPE);
|
||||
|
@ -333,8 +408,37 @@ QUuid Agent::getSessionUUID() const {
|
|||
return DependencyManager::get<NodeList>()->getSessionUUID();
|
||||
}
|
||||
|
||||
void Agent::setIsListeningToAudioStream(bool isListeningToAudioStream) {
|
||||
// this must happen on Agent's main thread
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setIsListeningToAudioStream", Q_ARG(bool, isListeningToAudioStream));
|
||||
return;
|
||||
}
|
||||
if (_isListeningToAudioStream) {
|
||||
// have to tell just the audio mixer to KillAvatar.
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->eachMatchingNode(
|
||||
[&](const SharedNodePointer& node)->bool {
|
||||
return (node->getType() == NodeType::AudioMixer) && node->getActiveSocket();
|
||||
},
|
||||
[&](const SharedNodePointer& node) {
|
||||
qDebug() << "sending KillAvatar message to Audio Mixers";
|
||||
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID, true);
|
||||
packet->write(getSessionUUID().toRfc4122());
|
||||
nodeList->sendPacket(std::move(packet), *node);
|
||||
});
|
||||
|
||||
}
|
||||
_isListeningToAudioStream = isListeningToAudioStream;
|
||||
}
|
||||
|
||||
void Agent::setIsAvatar(bool isAvatar) {
|
||||
// this must happen on Agent's main thread
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "setIsAvatar", Q_ARG(bool, isAvatar));
|
||||
return;
|
||||
}
|
||||
_isAvatar = isAvatar;
|
||||
|
||||
if (_isAvatar && !_avatarIdentityTimer) {
|
||||
|
@ -346,6 +450,10 @@ void Agent::setIsAvatar(bool isAvatar) {
|
|||
|
||||
// start the timers
|
||||
_avatarIdentityTimer->start(AVATAR_IDENTITY_PACKET_SEND_INTERVAL_MSECS);
|
||||
|
||||
// tell the avatarAudioTimer to start ticking
|
||||
emit startAvatarAudioTimer();
|
||||
|
||||
}
|
||||
|
||||
if (!_isAvatar) {
|
||||
|
@ -354,7 +462,25 @@ void Agent::setIsAvatar(bool isAvatar) {
|
|||
_avatarIdentityTimer->stop();
|
||||
delete _avatarIdentityTimer;
|
||||
_avatarIdentityTimer = nullptr;
|
||||
|
||||
// The avatar mixer never times out a connection (e.g., based on identity or data packets)
|
||||
// but rather keeps avatars in its list as long as "connected". As a result, clients timeout
|
||||
// when we stop sending identity, but then get woken up again by the mixer itself, which sends
|
||||
// identity packets to everyone. Here we explicitly tell the mixer to kill the entry for us.
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->eachMatchingNode(
|
||||
[&](const SharedNodePointer& node)->bool {
|
||||
return (node->getType() == NodeType::AvatarMixer || node->getType() == NodeType::AudioMixer)
|
||||
&& node->getActiveSocket();
|
||||
},
|
||||
[&](const SharedNodePointer& node) {
|
||||
qDebug() << "sending KillAvatar message to Avatar and Audio Mixers";
|
||||
auto packet = NLPacket::create(PacketType::KillAvatar, NUM_BYTES_RFC4122_UUID, true);
|
||||
packet->write(getSessionUUID().toRfc4122());
|
||||
nodeList->sendPacket(std::move(packet), *node);
|
||||
});
|
||||
}
|
||||
emit stopAvatarAudioTimer();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -365,11 +491,9 @@ void Agent::sendAvatarIdentityPacket() {
|
|||
}
|
||||
}
|
||||
|
||||
void Agent::processAgentAvatarAndAudio(float deltaTime) {
|
||||
void Agent::processAgentAvatar() {
|
||||
if (!_scriptEngine->isFinished() && _isAvatar) {
|
||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||
const int SCRIPT_AUDIO_BUFFER_SAMPLES = AudioConstants::SAMPLE_RATE / SCRIPT_FPS + 0.5;
|
||||
const int SCRIPT_AUDIO_BUFFER_BYTES = SCRIPT_AUDIO_BUFFER_SAMPLES * sizeof(int16_t);
|
||||
|
||||
QByteArray avatarByteArray = scriptedAvatar->toByteArray(true, randFloat() < AVATAR_SEND_FULL_UPDATE_RATIO);
|
||||
scriptedAvatar->doneEncoding(true);
|
||||
|
@ -383,92 +507,120 @@ void Agent::processAgentAvatarAndAudio(float deltaTime) {
|
|||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
nodeList->broadcastToNodes(std::move(avatarPacket), NodeSet() << NodeType::AvatarMixer);
|
||||
}
|
||||
}
|
||||
void Agent::encodeFrameOfZeros(QByteArray& encodedZeros) {
|
||||
_flushEncoder = false;
|
||||
static const QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL, 0);
|
||||
if (_encoder) {
|
||||
_encoder->encode(zeros, encodedZeros);
|
||||
} else {
|
||||
encodedZeros = zeros;
|
||||
}
|
||||
}
|
||||
|
||||
if (_isListeningToAudioStream || _avatarSound) {
|
||||
// if we have an avatar audio stream then send it out to our audio-mixer
|
||||
bool silentFrame = true;
|
||||
void Agent::processAgentAvatarAudio() {
|
||||
if (_isAvatar && (_isListeningToAudioStream || _avatarSound)) {
|
||||
// if we have an avatar audio stream then send it out to our audio-mixer
|
||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||
bool silentFrame = true;
|
||||
|
||||
int16_t numAvailableSamples = SCRIPT_AUDIO_BUFFER_SAMPLES;
|
||||
const int16_t* nextSoundOutput = NULL;
|
||||
int16_t numAvailableSamples = AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL;
|
||||
const int16_t* nextSoundOutput = NULL;
|
||||
|
||||
if (_avatarSound) {
|
||||
|
||||
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
||||
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
||||
if (_avatarSound) {
|
||||
const QByteArray& soundByteArray = _avatarSound->getByteArray();
|
||||
nextSoundOutput = reinterpret_cast<const int16_t*>(soundByteArray.data()
|
||||
+ _numAvatarSoundSentBytes);
|
||||
|
||||
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > SCRIPT_AUDIO_BUFFER_BYTES
|
||||
? SCRIPT_AUDIO_BUFFER_BYTES
|
||||
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
||||
numAvailableSamples = (int16_t)numAvailableBytes / sizeof(int16_t);
|
||||
int numAvailableBytes = (soundByteArray.size() - _numAvatarSoundSentBytes) > AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL
|
||||
? AudioConstants::NETWORK_FRAME_BYTES_PER_CHANNEL
|
||||
: soundByteArray.size() - _numAvatarSoundSentBytes;
|
||||
numAvailableSamples = (int16_t)numAvailableBytes / sizeof(int16_t);
|
||||
|
||||
|
||||
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
||||
for (int i = 0; i < numAvailableSamples; ++i) {
|
||||
if (nextSoundOutput[i] != 0) {
|
||||
silentFrame = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_numAvatarSoundSentBytes += numAvailableBytes;
|
||||
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
||||
// we're done with this sound object - so set our pointer back to NULL
|
||||
// and our sent bytes back to zero
|
||||
_avatarSound.clear();
|
||||
_numAvatarSoundSentBytes = 0;
|
||||
// check if the all of the _numAvatarAudioBufferSamples to be sent are silence
|
||||
for (int i = 0; i < numAvailableSamples; ++i) {
|
||||
if (nextSoundOutput[i] != 0) {
|
||||
silentFrame = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
auto audioPacket = NLPacket::create(silentFrame
|
||||
_numAvatarSoundSentBytes += numAvailableBytes;
|
||||
if (_numAvatarSoundSentBytes == soundByteArray.size()) {
|
||||
// we're done with this sound object - so set our pointer back to NULL
|
||||
// and our sent bytes back to zero
|
||||
_avatarSound.clear();
|
||||
_numAvatarSoundSentBytes = 0;
|
||||
_flushEncoder = true;
|
||||
}
|
||||
}
|
||||
|
||||
auto audioPacket = NLPacket::create(silentFrame && !_flushEncoder
|
||||
? PacketType::SilentAudioFrame
|
||||
: PacketType::MicrophoneAudioNoEcho);
|
||||
|
||||
// seek past the sequence number, will be packed when destination node is known
|
||||
audioPacket->seek(sizeof(quint16));
|
||||
// seek past the sequence number, will be packed when destination node is known
|
||||
audioPacket->seek(sizeof(quint16));
|
||||
|
||||
if (silentFrame) {
|
||||
if (!_isListeningToAudioStream) {
|
||||
// if we have a silent frame and we're not listening then just send nothing and break out of here
|
||||
return;
|
||||
}
|
||||
|
||||
// write the number of silent samples so the audio-mixer can uphold timing
|
||||
audioPacket->writePrimitive(SCRIPT_AUDIO_BUFFER_SAMPLES);
|
||||
|
||||
// use the orientation and position of this avatar for the source of this audio
|
||||
audioPacket->writePrimitive(scriptedAvatar->getPosition());
|
||||
glm::quat headOrientation = scriptedAvatar->getHeadOrientation();
|
||||
audioPacket->writePrimitive(headOrientation);
|
||||
|
||||
} else if (nextSoundOutput) {
|
||||
// assume scripted avatar audio is mono and set channel flag to zero
|
||||
audioPacket->writePrimitive((quint8)0);
|
||||
|
||||
// use the orientation and position of this avatar for the source of this audio
|
||||
audioPacket->writePrimitive(scriptedAvatar->getPosition());
|
||||
glm::quat headOrientation = scriptedAvatar->getHeadOrientation();
|
||||
audioPacket->writePrimitive(headOrientation);
|
||||
|
||||
// write the raw audio data
|
||||
audioPacket->write(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples * sizeof(int16_t));
|
||||
if (silentFrame) {
|
||||
if (!_isListeningToAudioStream) {
|
||||
// if we have a silent frame and we're not listening then just send nothing and break out of here
|
||||
return;
|
||||
}
|
||||
|
||||
// write audio packet to AudioMixer nodes
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node){
|
||||
// only send to nodes of type AudioMixer
|
||||
if (node->getType() == NodeType::AudioMixer) {
|
||||
// pack sequence number
|
||||
quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++;
|
||||
audioPacket->seek(0);
|
||||
audioPacket->writePrimitive(sequence);
|
||||
// write the codec
|
||||
audioPacket->writeString(_selectedCodecName);
|
||||
|
||||
// write the number of silent samples so the audio-mixer can uphold timing
|
||||
audioPacket->writePrimitive(numAvailableSamples);
|
||||
|
||||
// send audio packet
|
||||
nodeList->sendUnreliablePacket(*audioPacket, *node);
|
||||
// use the orientation and position of this avatar for the source of this audio
|
||||
audioPacket->writePrimitive(scriptedAvatar->getPosition());
|
||||
glm::quat headOrientation = scriptedAvatar->getHeadOrientation();
|
||||
audioPacket->writePrimitive(headOrientation);
|
||||
} else if (nextSoundOutput) {
|
||||
|
||||
// write the codec
|
||||
audioPacket->writeString(_selectedCodecName);
|
||||
|
||||
// assume scripted avatar audio is mono and set channel flag to zero
|
||||
audioPacket->writePrimitive((quint8)0);
|
||||
|
||||
// use the orientation and position of this avatar for the source of this audio
|
||||
audioPacket->writePrimitive(scriptedAvatar->getPosition());
|
||||
glm::quat headOrientation = scriptedAvatar->getHeadOrientation();
|
||||
audioPacket->writePrimitive(headOrientation);
|
||||
|
||||
QByteArray encodedBuffer;
|
||||
if (_flushEncoder) {
|
||||
encodeFrameOfZeros(encodedBuffer);
|
||||
} else {
|
||||
QByteArray decodedBuffer(reinterpret_cast<const char*>(nextSoundOutput), numAvailableSamples*sizeof(int16_t));
|
||||
if (_encoder) {
|
||||
// encode it
|
||||
_encoder->encode(decodedBuffer, encodedBuffer);
|
||||
} else {
|
||||
encodedBuffer = decodedBuffer;
|
||||
}
|
||||
});
|
||||
}
|
||||
audioPacket->write(encodedBuffer.constData(), encodedBuffer.size());
|
||||
}
|
||||
|
||||
// write audio packet to AudioMixer nodes
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->eachNode([this, &nodeList, &audioPacket](const SharedNodePointer& node) {
|
||||
// only send to nodes of type AudioMixer
|
||||
if (node->getType() == NodeType::AudioMixer) {
|
||||
// pack sequence number
|
||||
quint16 sequence = _outgoingScriptAudioSequenceNumbers[node->getUUID()]++;
|
||||
audioPacket->seek(0);
|
||||
audioPacket->writePrimitive(sequence);
|
||||
// send audio packet
|
||||
nodeList->sendUnreliablePacket(*audioPacket, *node);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -483,7 +635,17 @@ void Agent::aboutToFinish() {
|
|||
DependencyManager::get<EntityScriptingInterface>()->setEntityTree(nullptr);
|
||||
|
||||
ResourceManager::cleanup();
|
||||
|
||||
|
||||
// cleanup the AudioInjectorManager (and any still running injectors)
|
||||
DependencyManager::destroy<AudioInjectorManager>();
|
||||
DependencyManager::destroy<ScriptEngines>();
|
||||
|
||||
emit stopAvatarAudioTimer();
|
||||
_avatarAudioTimerThread.quit();
|
||||
|
||||
// cleanup codec & encoder
|
||||
if (_codec && _encoder) {
|
||||
_codec->releaseEncoder(_encoder);
|
||||
_encoder = nullptr;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include <QtScript/QScriptEngine>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QUrl>
|
||||
#include <QtCore/QTimer>
|
||||
#include <QUuid>
|
||||
|
||||
#include <EntityEditPacketSender.h>
|
||||
|
@ -26,8 +27,9 @@
|
|||
#include <ScriptEngine.h>
|
||||
#include <ThreadedAssignment.h>
|
||||
|
||||
#include "MixedAudioStream.h"
|
||||
#include <plugins/CodecPlugin.h>
|
||||
|
||||
#include "MixedAudioStream.h"
|
||||
|
||||
class Agent : public ThreadedAssignment {
|
||||
Q_OBJECT
|
||||
|
@ -47,16 +49,16 @@ public:
|
|||
bool isPlayingAvatarSound() const { return _avatarSound != NULL; }
|
||||
|
||||
bool isListeningToAudioStream() const { return _isListeningToAudioStream; }
|
||||
void setIsListeningToAudioStream(bool isListeningToAudioStream) { _isListeningToAudioStream = isListeningToAudioStream; }
|
||||
void setIsListeningToAudioStream(bool isListeningToAudioStream);
|
||||
|
||||
float getLastReceivedAudioLoudness() const { return _lastReceivedAudioLoudness; }
|
||||
QUuid getSessionUUID() const;
|
||||
|
||||
virtual void aboutToFinish();
|
||||
virtual void aboutToFinish() override;
|
||||
|
||||
public slots:
|
||||
void run();
|
||||
void playAvatarSound(SharedSoundPointer avatarSound) { setAvatarSound(avatarSound); }
|
||||
void run() override;
|
||||
void playAvatarSound(SharedSoundPointer avatarSound);
|
||||
|
||||
private slots:
|
||||
void requestScript();
|
||||
|
@ -66,12 +68,21 @@ private slots:
|
|||
void handleAudioPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
||||
void processAgentAvatarAndAudio(float deltaTime);
|
||||
void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void nodeActivated(SharedNodePointer activatedNode);
|
||||
|
||||
void processAgentAvatar();
|
||||
void processAgentAvatarAudio();
|
||||
|
||||
signals:
|
||||
void startAvatarAudioTimer();
|
||||
void stopAvatarAudioTimer();
|
||||
private:
|
||||
void negotiateAudioFormat();
|
||||
void selectAudioFormat(const QString& selectedCodecName);
|
||||
void encodeFrameOfZeros(QByteArray& encodedZeros);
|
||||
|
||||
std::unique_ptr<ScriptEngine> _scriptEngine;
|
||||
EntityEditPacketSender _entityEditSender;
|
||||
EntityTreeHeadlessViewer _entityViewer;
|
||||
|
@ -92,7 +103,12 @@ private:
|
|||
bool _isAvatar = false;
|
||||
QTimer* _avatarIdentityTimer = nullptr;
|
||||
QHash<QUuid, quint16> _outgoingScriptAudioSequenceNumbers;
|
||||
|
||||
|
||||
CodecPluginPointer _codec;
|
||||
QString _selectedCodecName;
|
||||
Encoder* _encoder { nullptr };
|
||||
QThread _avatarAudioTimerThread;
|
||||
bool _flushEncoder { false };
|
||||
};
|
||||
|
||||
#endif // hifi_Agent_h
|
||||
|
|
|
@ -24,27 +24,27 @@ public:
|
|||
AssignmentAction(EntityActionType type, const QUuid& id, EntityItemPointer ownerEntity);
|
||||
virtual ~AssignmentAction();
|
||||
|
||||
virtual void removeFromSimulation(EntitySimulationPointer simulation) const;
|
||||
virtual EntityItemWeakPointer getOwnerEntity() const { return _ownerEntity; }
|
||||
virtual void setOwnerEntity(const EntityItemPointer ownerEntity) { _ownerEntity = ownerEntity; }
|
||||
virtual bool updateArguments(QVariantMap arguments);
|
||||
virtual QVariantMap getArguments();
|
||||
virtual void removeFromSimulation(EntitySimulationPointer simulation) const override;
|
||||
virtual EntityItemWeakPointer getOwnerEntity() const override { return _ownerEntity; }
|
||||
virtual void setOwnerEntity(const EntityItemPointer ownerEntity) override { _ownerEntity = ownerEntity; }
|
||||
virtual bool updateArguments(QVariantMap arguments) override;
|
||||
virtual QVariantMap getArguments() override;
|
||||
|
||||
virtual QByteArray serialize() const;
|
||||
virtual void deserialize(QByteArray serializedArguments);
|
||||
virtual QByteArray serialize() const override;
|
||||
virtual void deserialize(QByteArray serializedArguments) override;
|
||||
|
||||
private:
|
||||
QByteArray _data;
|
||||
|
||||
protected:
|
||||
virtual glm::vec3 getPosition();
|
||||
virtual void setPosition(glm::vec3 position);
|
||||
virtual glm::quat getRotation();
|
||||
virtual void setRotation(glm::quat rotation);
|
||||
virtual glm::vec3 getLinearVelocity();
|
||||
virtual void setLinearVelocity(glm::vec3 linearVelocity);
|
||||
virtual glm::vec3 getAngularVelocity();
|
||||
virtual void setAngularVelocity(glm::vec3 angularVelocity);
|
||||
virtual glm::vec3 getPosition() override;
|
||||
virtual void setPosition(glm::vec3 position) override;
|
||||
virtual glm::quat getRotation() override;
|
||||
virtual void setRotation(glm::quat rotation) override;
|
||||
virtual glm::vec3 getLinearVelocity() override;
|
||||
virtual void setLinearVelocity(glm::vec3 linearVelocity) override;
|
||||
virtual glm::vec3 getAngularVelocity() override;
|
||||
virtual void setAngularVelocity(glm::vec3 angularVelocity) override;
|
||||
|
||||
bool _active;
|
||||
EntityItemWeakPointer _ownerEntity;
|
||||
|
|
|
@ -22,8 +22,8 @@ public:
|
|||
virtual EntityActionPointer factory(EntityActionType type,
|
||||
const QUuid& id,
|
||||
EntityItemPointer ownerEntity,
|
||||
QVariantMap arguments);
|
||||
virtual EntityActionPointer factoryBA(EntityItemPointer ownerEntity, QByteArray data);
|
||||
QVariantMap arguments) override;
|
||||
virtual EntityActionPointer factoryBA(EntityItemPointer ownerEntity, QByteArray data) override;
|
||||
};
|
||||
|
||||
#endif // hifi_AssignmentActionFactory_h
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
#include <ShutdownEventListener.h>
|
||||
#include <SoundCache.h>
|
||||
#include <ResourceScriptingInterface.h>
|
||||
#include <ScriptEngines.h>
|
||||
|
||||
#include "AssignmentFactory.h"
|
||||
#include "AssignmentActionFactory.h"
|
||||
|
@ -53,10 +52,9 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
QSettings::setDefaultFormat(QSettings::IniFormat);
|
||||
|
||||
DependencyManager::set<AccountManager>();
|
||||
|
||||
|
||||
auto scriptableAvatar = DependencyManager::set<ScriptableAvatar>();
|
||||
auto addressManager = DependencyManager::set<AddressManager>();
|
||||
auto scriptEngines = DependencyManager::set<ScriptEngines>();
|
||||
|
||||
// create a NodeList as an unassigned client, must be after addressManager
|
||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Unassigned, listenPort);
|
||||
|
@ -91,7 +89,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
// check for a wallet UUID on the command line or in the config
|
||||
// this would represent where the user running AC wants funds sent to
|
||||
if (!walletUUID.isNull()) {
|
||||
qCDebug(assigmnentclient) << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID);
|
||||
qCDebug(assignment_client) << "The destination wallet UUID for credits is" << uuidStringWithoutCurlyBraces(walletUUID);
|
||||
_requestAssignment.setWalletUUID(walletUUID);
|
||||
}
|
||||
|
||||
|
@ -102,16 +100,16 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
}
|
||||
|
||||
_assignmentServerSocket = HifiSockAddr(_assignmentServerHostname, assignmentServerPort, true);
|
||||
_assignmentServerSocket.setObjectName("AssigmentServer");
|
||||
_assignmentServerSocket.setObjectName("AssignmentServer");
|
||||
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
|
||||
|
||||
qCDebug(assigmnentclient) << "Assignment server socket is" << _assignmentServerSocket;
|
||||
qCDebug(assignment_client) << "Assignment server socket is" << _assignmentServerSocket;
|
||||
|
||||
// call a timer function every ASSIGNMENT_REQUEST_INTERVAL_MSECS to ask for assignment, if required
|
||||
qCDebug(assigmnentclient) << "Waiting for assignment -" << _requestAssignment;
|
||||
qCDebug(assignment_client) << "Waiting for assignment -" << _requestAssignment;
|
||||
|
||||
if (_assignmentServerHostname != "localhost") {
|
||||
qCDebug(assigmnentclient) << "- will attempt to connect to domain-server on" << _assignmentServerSocket.getPort();
|
||||
qCDebug(assignment_client) << "- will attempt to connect to domain-server on" << _assignmentServerSocket.getPort();
|
||||
}
|
||||
|
||||
connect(&_requestTimer, SIGNAL(timeout()), SLOT(sendAssignmentRequest()));
|
||||
|
@ -129,7 +127,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
_assignmentClientMonitorSocket = HifiSockAddr(DEFAULT_ASSIGNMENT_CLIENT_MONITOR_HOSTNAME, assignmentMonitorPort);
|
||||
_assignmentClientMonitorSocket.setObjectName("AssignmentClientMonitor");
|
||||
|
||||
qCDebug(assigmnentclient) << "Assignment-client monitor socket is" << _assignmentClientMonitorSocket;
|
||||
qCDebug(assignment_client) << "Assignment-client monitor socket is" << _assignmentClientMonitorSocket;
|
||||
|
||||
// Hook up a timer to send this child's status to the Monitor once per second
|
||||
setUpStatusToMonitor();
|
||||
|
@ -140,7 +138,7 @@ AssignmentClient::AssignmentClient(Assignment::Type requestAssignmentType, QStri
|
|||
}
|
||||
|
||||
void AssignmentClient::stopAssignmentClient() {
|
||||
qCDebug(assigmnentclient) << "Forced stop of assignment-client.";
|
||||
qCDebug(assignment_client) << "Forced stop of assignment-client.";
|
||||
|
||||
_requestTimer.stop();
|
||||
_statsTimerACM.stop();
|
||||
|
@ -178,8 +176,6 @@ AssignmentClient::~AssignmentClient() {
|
|||
void AssignmentClient::aboutToQuit() {
|
||||
stopAssignmentClient();
|
||||
|
||||
DependencyManager::destroy<ScriptEngines>();
|
||||
|
||||
// clear the log handler so that Qt doesn't call the destructor on LogHandler
|
||||
qInstallMessageHandler(0);
|
||||
}
|
||||
|
@ -218,14 +214,14 @@ void AssignmentClient::sendAssignmentRequest() {
|
|||
quint16 localAssignmentServerPort;
|
||||
if (nodeList->getLocalServerPortFromSharedMemory(DOMAIN_SERVER_LOCAL_PORT_SMEM_KEY, localAssignmentServerPort)) {
|
||||
if (localAssignmentServerPort != _assignmentServerSocket.getPort()) {
|
||||
qCDebug(assigmnentclient) << "Port for local assignment server read from shared memory is"
|
||||
qCDebug(assignment_client) << "Port for local assignment server read from shared memory is"
|
||||
<< localAssignmentServerPort;
|
||||
|
||||
_assignmentServerSocket.setPort(localAssignmentServerPort);
|
||||
nodeList->setAssignmentServerSocket(_assignmentServerSocket);
|
||||
}
|
||||
} else {
|
||||
qCWarning(assigmnentclient) << "Failed to read local assignment server port from shared memory"
|
||||
qCWarning(assignment_client) << "Failed to read local assignment server port from shared memory"
|
||||
<< "- will send assignment request to previous assignment server socket.";
|
||||
}
|
||||
}
|
||||
|
@ -235,10 +231,10 @@ void AssignmentClient::sendAssignmentRequest() {
|
|||
}
|
||||
|
||||
void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
qCDebug(assigmnentclient) << "Received a PacketType::CreateAssignment - attempting to unpack.";
|
||||
qCDebug(assignment_client) << "Received a PacketType::CreateAssignment - attempting to unpack.";
|
||||
|
||||
if (_currentAssignment) {
|
||||
qCWarning(assigmnentclient) << "Received a PacketType::CreateAssignment while still running an active assignment. Ignoring.";
|
||||
qCWarning(assignment_client) << "Received a PacketType::CreateAssignment while still running an active assignment. Ignoring.";
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -246,7 +242,7 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<ReceivedMessa
|
|||
_currentAssignment = AssignmentFactory::unpackAssignment(*message);
|
||||
|
||||
if (_currentAssignment && !_isAssigned) {
|
||||
qDebug(assigmnentclient) << "Received an assignment -" << *_currentAssignment;
|
||||
qDebug(assignment_client) << "Received an assignment -" << *_currentAssignment;
|
||||
_isAssigned = true;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -256,7 +252,7 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<ReceivedMessa
|
|||
nodeList->getDomainHandler().setSockAddr(message->getSenderSockAddr(), _assignmentServerHostname);
|
||||
nodeList->getDomainHandler().setAssignmentUUID(_currentAssignment->getUUID());
|
||||
|
||||
qCDebug(assigmnentclient) << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString();
|
||||
qCDebug(assignment_client) << "Destination IP for assignment is" << nodeList->getDomainHandler().getIP().toString();
|
||||
|
||||
// start the deployed assignment
|
||||
QThread* workerThread = new QThread;
|
||||
|
@ -284,7 +280,7 @@ void AssignmentClient::handleCreateAssignmentPacket(QSharedPointer<ReceivedMessa
|
|||
// Starts an event loop, and emits workerThread->started()
|
||||
workerThread->start();
|
||||
} else {
|
||||
qCWarning(assigmnentclient) << "Received an assignment that could not be unpacked. Re-requesting.";
|
||||
qCWarning(assignment_client) << "Received an assignment that could not be unpacked. Re-requesting.";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -294,10 +290,10 @@ void AssignmentClient::handleStopNodePacket(QSharedPointer<ReceivedMessage> mess
|
|||
if (senderSockAddr.getAddress() == QHostAddress::LocalHost ||
|
||||
senderSockAddr.getAddress() == QHostAddress::LocalHostIPv6) {
|
||||
|
||||
qCDebug(assigmnentclient) << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketType::StopNode.";
|
||||
qCDebug(assignment_client) << "AssignmentClientMonitor at" << senderSockAddr << "requested stop via PacketType::StopNode.";
|
||||
QCoreApplication::quit();
|
||||
} else {
|
||||
qCWarning(assigmnentclient) << "Got a stop packet from other than localhost.";
|
||||
qCWarning(assignment_client) << "Got a stop packet from other than localhost.";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,7 +313,7 @@ void AssignmentClient::handleAuthenticationRequest() {
|
|||
// ask the account manager to log us in from the env variables
|
||||
accountManager->requestAccessToken(username, password);
|
||||
} else {
|
||||
qCWarning(assigmnentclient) << "Authentication was requested against" << qPrintable(accountManager->getAuthURL().toString())
|
||||
qCWarning(assignment_client) << "Authentication was requested against" << qPrintable(accountManager->getAuthURL().toString())
|
||||
<< "but both or one of" << qPrintable(DATA_SERVER_USERNAME_ENV)
|
||||
<< "/" << qPrintable(DATA_SERVER_PASSWORD_ENV) << "are not set. Unable to authenticate.";
|
||||
|
||||
|
@ -335,7 +331,7 @@ void AssignmentClient::assignmentCompleted() {
|
|||
// reset the logging target to the the CHILD_TARGET_NAME
|
||||
LogHandler::getInstance().setTargetName(ASSIGNMENT_CLIENT_TARGET_NAME);
|
||||
|
||||
qCDebug(assigmnentclient) << "Assignment finished or never started - waiting for new assignment.";
|
||||
qCDebug(assignment_client) << "Assignment finished or never started - waiting for new assignment.";
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
|
|
|
@ -11,4 +11,4 @@
|
|||
|
||||
#include "AssignmentClientLogging.h"
|
||||
|
||||
Q_LOGGING_CATEGORY(assigmnentclient, "hifi.assignment-client")
|
||||
Q_LOGGING_CATEGORY(assignment_client, "hifi.assignment-client")
|
|
@ -14,6 +14,6 @@
|
|||
|
||||
#include <QLoggingCategory>
|
||||
|
||||
Q_DECLARE_LOGGING_CATEGORY(assigmnentclient)
|
||||
Q_DECLARE_LOGGING_CATEGORY(assignment_client)
|
||||
|
||||
#endif // hifi_AssignmentClientLogging_h
|
37
assignment-client/src/AvatarAudioTimer.cpp
Normal file
37
assignment-client/src/AvatarAudioTimer.cpp
Normal file
|
@ -0,0 +1,37 @@
|
|||
//
|
||||
// AvatarAudioTimer.cpp
|
||||
// assignment-client/src
|
||||
//
|
||||
// Created by David Kelly on 10/12/13.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
#include <QDebug>
|
||||
#include <SharedUtil.h>
|
||||
#include "AvatarAudioTimer.h"
|
||||
|
||||
// this should send a signal every 10ms, with pretty good precision. Hardcoding
|
||||
// to 10ms since that's what you'd want for audio.
|
||||
void AvatarAudioTimer::start() {
|
||||
qDebug() << __FUNCTION__;
|
||||
auto startTime = usecTimestampNow();
|
||||
quint64 frameCounter = 0;
|
||||
const int TARGET_INTERVAL_USEC = 10000; // 10ms
|
||||
while (!_quit) {
|
||||
++frameCounter;
|
||||
|
||||
// tick every 10ms from startTime
|
||||
quint64 targetTime = startTime + frameCounter * TARGET_INTERVAL_USEC;
|
||||
quint64 now = usecTimestampNow();
|
||||
|
||||
// avoid quint64 underflow
|
||||
if (now < targetTime) {
|
||||
usleep(targetTime - now);
|
||||
}
|
||||
|
||||
emit avatarTick();
|
||||
}
|
||||
qDebug() << "AvatarAudioTimer is finished";
|
||||
}
|
31
assignment-client/src/AvatarAudioTimer.h
Normal file
31
assignment-client/src/AvatarAudioTimer.h
Normal file
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// AvatarAudioTimer.h
|
||||
// assignment-client/src
|
||||
//
|
||||
// Created by David Kelly on 10/12/13.
|
||||
// Copyright 2016 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#ifndef hifi_AvatarAudioTimer_h
|
||||
#define hifi_AvatarAudioTimer_h
|
||||
|
||||
#include <QtCore/QObject>
|
||||
|
||||
class AvatarAudioTimer : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void avatarTick();
|
||||
|
||||
public slots:
|
||||
void start();
|
||||
void stop() { _quit = true; }
|
||||
|
||||
private:
|
||||
bool _quit { false };
|
||||
};
|
||||
|
||||
#endif //hifi_AvatarAudioTimer_h
|
|
@ -453,7 +453,7 @@ bool AssetServer::loadMappingsFromFile() {
|
|||
while (it != _fileMappings.end()) {
|
||||
bool shouldDrop = false;
|
||||
|
||||
if (!isValidPath(it.key())) {
|
||||
if (!isValidFilePath(it.key())) {
|
||||
qWarning() << "Will not keep mapping for" << it.key() << "since it is not a valid path.";
|
||||
shouldDrop = true;
|
||||
}
|
||||
|
@ -508,7 +508,7 @@ bool AssetServer::writeMappingsToFile() {
|
|||
bool AssetServer::setMapping(AssetPath path, AssetHash hash) {
|
||||
path = path.trimmed();
|
||||
|
||||
if (!isValidPath(path)) {
|
||||
if (!isValidFilePath(path)) {
|
||||
qWarning() << "Cannot set a mapping for invalid path:" << path << "=>" << hash;
|
||||
return false;
|
||||
}
|
||||
|
@ -637,8 +637,8 @@ bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) {
|
|||
oldPath = oldPath.trimmed();
|
||||
newPath = newPath.trimmed();
|
||||
|
||||
if (!isValidPath(oldPath) || !isValidPath(newPath)) {
|
||||
qWarning() << "Cannot perform rename with invalid paths - both should have leading forward slashes:"
|
||||
if (!isValidFilePath(oldPath) || !isValidFilePath(newPath)) {
|
||||
qWarning() << "Cannot perform rename with invalid paths - both should have leading forward and no ending slashes:"
|
||||
<< oldPath << "=>" << newPath;
|
||||
|
||||
return false;
|
||||
|
|
|
@ -26,7 +26,7 @@ public:
|
|||
AssetServer(ReceivedMessage& message);
|
||||
|
||||
public slots:
|
||||
void run();
|
||||
void run() override;
|
||||
|
||||
private slots:
|
||||
void completeSetup();
|
||||
|
@ -35,9 +35,9 @@ private slots:
|
|||
void handleAssetGet(QSharedPointer<ReceivedMessage> packet, SharedNodePointer senderNode);
|
||||
void handleAssetUpload(QSharedPointer<ReceivedMessage> packetList, SharedNodePointer senderNode);
|
||||
void handleAssetMappingOperation(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
||||
void sendStatsPacket();
|
||||
|
||||
|
||||
void sendStatsPacket() override;
|
||||
|
||||
private:
|
||||
using Mappings = QVariantHash;
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class SendAssetTask : public QRunnable {
|
|||
public:
|
||||
SendAssetTask(QSharedPointer<ReceivedMessage> message, const SharedNodePointer& sendToNode, const QDir& resourcesDir);
|
||||
|
||||
void run();
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
QSharedPointer<ReceivedMessage> _message;
|
||||
|
|
|
@ -27,9 +27,9 @@ class Node;
|
|||
class UploadAssetTask : public QRunnable {
|
||||
public:
|
||||
UploadAssetTask(QSharedPointer<ReceivedMessage> message, QSharedPointer<Node> senderNode, const QDir& resourcesDir);
|
||||
|
||||
void run();
|
||||
|
||||
|
||||
void run() override;
|
||||
|
||||
private:
|
||||
QSharedPointer<ReceivedMessage> _receivedMessage;
|
||||
QSharedPointer<Node> _senderNode;
|
||||
|
|
|
@ -61,15 +61,14 @@
|
|||
|
||||
#include "AudioMixer.h"
|
||||
|
||||
const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f;
|
||||
const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance)
|
||||
const float DEFAULT_NOISE_MUTING_THRESHOLD = 0.003f;
|
||||
const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer";
|
||||
const QString AUDIO_ENV_GROUP_KEY = "audio_env";
|
||||
const QString AUDIO_BUFFER_GROUP_KEY = "audio_buffer";
|
||||
|
||||
InboundAudioStream::Settings AudioMixer::_streamSettings;
|
||||
static const float LOUDNESS_TO_DISTANCE_RATIO = 0.00001f;
|
||||
static const float DEFAULT_ATTENUATION_PER_DOUBLING_IN_DISTANCE = 0.5f; // attenuation = -6dB * log2(distance)
|
||||
static const float DEFAULT_NOISE_MUTING_THRESHOLD = 0.003f;
|
||||
static const QString AUDIO_MIXER_LOGGING_TARGET_NAME = "audio-mixer";
|
||||
static const QString AUDIO_ENV_GROUP_KEY = "audio_env";
|
||||
static const QString AUDIO_BUFFER_GROUP_KEY = "audio_buffer";
|
||||
|
||||
int AudioMixer::_numStaticJitterFrames{ -1 };
|
||||
bool AudioMixer::_enableFilter = true;
|
||||
|
||||
bool AudioMixer::shouldMute(float quietestFrame) {
|
||||
|
@ -91,15 +90,73 @@ AudioMixer::AudioMixer(ReceivedMessage& message) :
|
|||
PacketType::InjectAudio, PacketType::SilentAudioFrame,
|
||||
PacketType::AudioStreamStats },
|
||||
this, "handleNodeAudioPacket");
|
||||
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
|
||||
packetReceiver.registerListener(PacketType::NegotiateAudioFormat, this, "handleNegotiateAudioFormat");
|
||||
packetReceiver.registerListener(PacketType::MuteEnvironment, this, "handleMuteEnvironmentPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeIgnoreRequest, this, "handleNodeIgnoreRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::KillAvatar, this, "handleKillAvatarPacket");
|
||||
|
||||
connect(nodeList.data(), &NodeList::nodeKilled, this, &AudioMixer::handleNodeKilled);
|
||||
}
|
||||
|
||||
const float ATTENUATION_BEGINS_AT_DISTANCE = 1.0f;
|
||||
|
||||
const int IEEE754_MANT_BITS = 23;
|
||||
const int IEEE754_EXPN_BIAS = 127;
|
||||
|
||||
//
|
||||
// for x > 0.0f, returns log2(x)
|
||||
// for x <= 0.0f, returns large negative value
|
||||
//
|
||||
// abs |error| < 8e-3, smooth (exact for x=2^N) for NPOLY=3
|
||||
// abs |error| < 2e-4, smooth (exact for x=2^N) for NPOLY=5
|
||||
// rel |error| < 0.4 from precision loss very close to 1.0f
|
||||
//
|
||||
static inline float fastlog2(float x) {
|
||||
|
||||
union { float f; int32_t i; } mant, bits = { x };
|
||||
|
||||
// split into mantissa and exponent
|
||||
mant.i = (bits.i & ((1 << IEEE754_MANT_BITS) - 1)) | (IEEE754_EXPN_BIAS << IEEE754_MANT_BITS);
|
||||
int32_t expn = (bits.i >> IEEE754_MANT_BITS) - IEEE754_EXPN_BIAS;
|
||||
|
||||
mant.f -= 1.0f;
|
||||
|
||||
// polynomial for log2(1+x) over x=[0,1]
|
||||
//x = (-0.346555386f * mant.f + 1.346555386f) * mant.f;
|
||||
x = (((-0.0821307180f * mant.f + 0.321188984f) * mant.f - 0.677784014f) * mant.f + 1.43872575f) * mant.f;
|
||||
|
||||
return x + expn;
|
||||
}
|
||||
|
||||
//
|
||||
// for -126 <= x < 128, returns exp2(x)
|
||||
//
|
||||
// rel |error| < 3e-3, smooth (exact for x=N) for NPOLY=3
|
||||
// rel |error| < 9e-6, smooth (exact for x=N) for NPOLY=5
|
||||
//
|
||||
static inline float fastexp2(float x) {
|
||||
|
||||
union { float f; int32_t i; } xi;
|
||||
|
||||
// bias such that x > 0
|
||||
x += IEEE754_EXPN_BIAS;
|
||||
//x = MAX(x, 1.0f);
|
||||
//x = MIN(x, 254.9999f);
|
||||
|
||||
// split into integer and fraction
|
||||
xi.i = (int32_t)x;
|
||||
x -= xi.i;
|
||||
|
||||
// construct exp2(xi) as a float
|
||||
xi.i <<= IEEE754_MANT_BITS;
|
||||
|
||||
// polynomial for exp2(x) over x=[0,1]
|
||||
//x = (0.339766028f * x + 0.660233972f) * x + 1.0f;
|
||||
x = (((0.0135557472f * x + 0.0520323690f) * x + 0.241379763f) * x + 0.693032121f) * x + 1.0f;
|
||||
|
||||
return x * xi.f;
|
||||
}
|
||||
|
||||
float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd,
|
||||
const AvatarAudioStream& listeningNodeStream, const glm::vec3& relativePosition, bool isEcho) {
|
||||
float gain = 1.0f;
|
||||
|
@ -148,7 +205,7 @@ float AudioMixer::gainForSource(const PositionalAudioStream& streamToAdd,
|
|||
g = (g > 1.0f) ? 1.0f : g;
|
||||
|
||||
// calculate the distance coefficient using the distance to this node
|
||||
float distanceCoefficient = exp2f(log2f(g) * log2f(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE));
|
||||
float distanceCoefficient = fastexp2(fastlog2(g) * fastlog2(distanceBetween/ATTENUATION_BEGINS_AT_DISTANCE));
|
||||
|
||||
// multiply the current attenuation coefficient by the distance coefficient
|
||||
gain *= distanceCoefficient;
|
||||
|
@ -212,20 +269,18 @@ void AudioMixer::addStreamToMixForListeningNodeWithStream(AudioMixerClientData&
|
|||
if (!streamToAdd.lastPopSucceeded()) {
|
||||
bool forceSilentBlock = true;
|
||||
|
||||
if (_streamSettings._repetitionWithFade && !streamToAdd.getLastPopOutput().isNull()) {
|
||||
if (!streamToAdd.getLastPopOutput().isNull()) {
|
||||
bool isInjector = dynamic_cast<const InjectedAudioStream*>(&streamToAdd);
|
||||
|
||||
// reptition with fade is enabled, and we do have a valid previous frame to repeat
|
||||
// so we mix the previously-mixed block
|
||||
|
||||
// this is preferable to not mixing it at all to avoid the harsh jump to silence
|
||||
// in an injector, just go silent - the injector has likely ended
|
||||
// in other inputs (microphone, &c.), repeat with fade to avoid the harsh jump to silence
|
||||
|
||||
// we'll repeat the last block until it has a block to mix
|
||||
// and we'll gradually fade that repeated block into silence.
|
||||
|
||||
// calculate its fade factor, which depends on how many times it's already been repeated.
|
||||
|
||||
repeatedFrameFadeFactor = calculateRepeatedFrameFadeFactor(streamToAdd.getConsecutiveNotMixedCount() - 1);
|
||||
if (repeatedFrameFadeFactor > 0.0f) {
|
||||
if (!isInjector && repeatedFrameFadeFactor > 0.0f) {
|
||||
// apply the repeatedFrameFadeFactor to the gain
|
||||
gain *= repeatedFrameFadeFactor;
|
||||
|
||||
|
@ -427,6 +482,7 @@ void AudioMixer::sendAudioEnvironmentPacket(SharedNodePointer node) {
|
|||
}
|
||||
|
||||
void AudioMixer::handleNodeAudioPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) {
|
||||
getOrCreateClientData(sendingNode.data());
|
||||
DependencyManager::get<NodeList>()->updateNodeWithDataFromPacket(message, sendingNode);
|
||||
}
|
||||
|
||||
|
@ -525,18 +581,8 @@ void AudioMixer::handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> mess
|
|||
}
|
||||
}
|
||||
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
|
||||
|
||||
// FIXME - why would we not have client data at this point??
|
||||
if (!clientData) {
|
||||
qDebug() << "UNEXPECTED -- didn't have node linked data in " << __FUNCTION__;
|
||||
sendingNode->setLinkedData(std::unique_ptr<NodeData> { new AudioMixerClientData(sendingNode->getUUID()) });
|
||||
clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
|
||||
connect(clientData, &AudioMixerClientData::injectorStreamFinished, this, &AudioMixer::removeHRTFsForFinishedInjector);
|
||||
}
|
||||
|
||||
auto clientData = getOrCreateClientData(sendingNode.data());
|
||||
clientData->setupCodec(selectedCodec, selectedCodecName);
|
||||
|
||||
qDebug() << "selectedCodecName:" << selectedCodecName;
|
||||
clientData->sendSelectAudioFormat(sendingNode, selectedCodecName);
|
||||
}
|
||||
|
@ -545,14 +591,29 @@ void AudioMixer::handleNodeKilled(SharedNodePointer killedNode) {
|
|||
// enumerate the connected listeners to remove HRTF objects for the disconnected node
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
nodeList->eachNode([](const SharedNodePointer& node) {
|
||||
nodeList->eachNode([&killedNode](const SharedNodePointer& node) {
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
if (clientData) {
|
||||
clientData->removeHRTFsForNode(node->getUUID());
|
||||
clientData->removeHRTFsForNode(killedNode->getUUID());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void AudioMixer::handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(sendingNode->getLinkedData());
|
||||
if (clientData) {
|
||||
clientData->removeAgentAvatarAudioStream();
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->eachNode([sendingNode](const SharedNodePointer& node){
|
||||
auto listenerClientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
if (listenerClientData) {
|
||||
listenerClientData->removeHRTFForStream(sendingNode->getUUID());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioMixer::handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode) {
|
||||
sendingNode->parseIgnoreRequestMessage(packet);
|
||||
}
|
||||
|
@ -582,13 +643,18 @@ QString AudioMixer::percentageForMixStats(int counter) {
|
|||
}
|
||||
|
||||
void AudioMixer::sendStatsPacket() {
|
||||
static QJsonObject statsObject;
|
||||
QJsonObject statsObject;
|
||||
|
||||
statsObject["useDynamicJitterBuffers"] = _streamSettings._dynamicJitterBuffers;
|
||||
if (_numStatFrames == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
statsObject["useDynamicJitterBuffers"] = _numStaticJitterFrames == -1;
|
||||
statsObject["trailing_sleep_percentage"] = _trailingSleepRatio * 100.0f;
|
||||
statsObject["performance_throttling_ratio"] = _performanceThrottlingRatio;
|
||||
|
||||
statsObject["avg_listeners_per_frame"] = (float) _sumListeners / (float) _numStatFrames;
|
||||
statsObject["avg_streams_per_frame"] = (float)_sumStreams / (float)_numStatFrames;
|
||||
statsObject["avg_listeners_per_frame"] = (float)_sumListeners / (float)_numStatFrames;
|
||||
|
||||
QJsonObject mixStats;
|
||||
mixStats["%_hrtf_mixes"] = percentageForMixStats(_hrtfRenders);
|
||||
|
@ -602,6 +668,7 @@ void AudioMixer::sendStatsPacket() {
|
|||
|
||||
statsObject["mix_stats"] = mixStats;
|
||||
|
||||
_sumStreams = 0;
|
||||
_sumListeners = 0;
|
||||
_hrtfRenders = 0;
|
||||
_hrtfSilentRenders = 0;
|
||||
|
@ -649,17 +716,24 @@ void AudioMixer::run() {
|
|||
ThreadedAssignment::commonInit(AUDIO_MIXER_LOGGING_TARGET_NAME, NodeType::AudioMixer);
|
||||
}
|
||||
|
||||
AudioMixerClientData* AudioMixer::getOrCreateClientData(Node* node) {
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
|
||||
if (!clientData) {
|
||||
node->setLinkedData(std::unique_ptr<NodeData> { new AudioMixerClientData(node->getUUID()) });
|
||||
clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
connect(clientData, &AudioMixerClientData::injectorStreamFinished, this, &AudioMixer::removeHRTFsForFinishedInjector);
|
||||
}
|
||||
|
||||
return clientData;
|
||||
}
|
||||
|
||||
void AudioMixer::domainSettingsRequestComplete() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
nodeList->addNodeTypeToInterestSet(NodeType::Agent);
|
||||
|
||||
nodeList->linkedDataCreateCallback = [&](Node* node) {
|
||||
node->setLinkedData(std::unique_ptr<NodeData> { new AudioMixerClientData(node->getUUID()) });
|
||||
auto clientData = dynamic_cast<AudioMixerClientData*>(node->getLinkedData());
|
||||
|
||||
connect(clientData, &AudioMixerClientData::injectorStreamFinished, this, &AudioMixer::removeHRTFsForFinishedInjector);
|
||||
};
|
||||
nodeList->linkedDataCreateCallback = [&](Node* node) { getOrCreateClientData(node); };
|
||||
|
||||
DomainHandler& domainHandler = nodeList->getDomainHandler();
|
||||
const QJsonObject& settingsObject = domainHandler.getSettingsObject();
|
||||
|
@ -672,79 +746,71 @@ void AudioMixer::domainSettingsRequestComplete() {
|
|||
}
|
||||
|
||||
void AudioMixer::broadcastMixes() {
|
||||
const int TRAILING_AVERAGE_FRAMES = 100;
|
||||
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
|
||||
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
|
||||
|
||||
const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f;
|
||||
const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f;
|
||||
|
||||
const float RATIO_BACK_OFF = 0.02f;
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
auto nextFrameTimestamp = p_high_resolution_clock::now();
|
||||
auto timeToSleep = std::chrono::microseconds(0);
|
||||
|
||||
const int TRAILING_AVERAGE_FRAMES = 100;
|
||||
int currentFrame = 1;
|
||||
int numFramesPerSecond = (int) ceil(AudioConstants::NETWORK_FRAMES_PER_SEC);
|
||||
int framesSinceCutoffEvent = TRAILING_AVERAGE_FRAMES;
|
||||
|
||||
int currentFrame { 1 };
|
||||
int numFramesPerSecond { (int) ceil(AudioConstants::NETWORK_FRAMES_PER_SEC) };
|
||||
|
||||
while (!_isFinished) {
|
||||
const float STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.10f;
|
||||
const float BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD = 0.20f;
|
||||
// manage mixer load
|
||||
{
|
||||
_trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio) +
|
||||
// ratio of frame spent sleeping / total frame time
|
||||
((CURRENT_FRAME_RATIO * timeToSleep.count()) / (float) AudioConstants::NETWORK_FRAME_USECS);
|
||||
|
||||
const float RATIO_BACK_OFF = 0.02f;
|
||||
bool hasRatioChanged = false;
|
||||
|
||||
const float CURRENT_FRAME_RATIO = 1.0f / TRAILING_AVERAGE_FRAMES;
|
||||
const float PREVIOUS_FRAMES_RATIO = 1.0f - CURRENT_FRAME_RATIO;
|
||||
|
||||
if (timeToSleep.count() < 0) {
|
||||
timeToSleep = std::chrono::microseconds(0);
|
||||
}
|
||||
|
||||
_trailingSleepRatio = (PREVIOUS_FRAMES_RATIO * _trailingSleepRatio)
|
||||
+ (timeToSleep.count() * CURRENT_FRAME_RATIO / (float) AudioConstants::NETWORK_FRAME_USECS);
|
||||
|
||||
float lastCutoffRatio = _performanceThrottlingRatio;
|
||||
bool hasRatioChanged = false;
|
||||
|
||||
if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) {
|
||||
if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) {
|
||||
// we're struggling - change our min required loudness to reduce some load
|
||||
_performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio));
|
||||
|
||||
qDebug() << "Mixer is struggling, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
hasRatioChanged = true;
|
||||
} else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) {
|
||||
// we've recovered and can back off the required loudness
|
||||
_performanceThrottlingRatio = _performanceThrottlingRatio - RATIO_BACK_OFF;
|
||||
|
||||
if (_performanceThrottlingRatio < 0) {
|
||||
_performanceThrottlingRatio = 0;
|
||||
if (framesSinceCutoffEvent >= TRAILING_AVERAGE_FRAMES) {
|
||||
if (_trailingSleepRatio <= STRUGGLE_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD) {
|
||||
qDebug() << "Mixer is struggling";
|
||||
// change our min required loudness to reduce some load
|
||||
_performanceThrottlingRatio = _performanceThrottlingRatio + (0.5f * (1.0f - _performanceThrottlingRatio));
|
||||
hasRatioChanged = true;
|
||||
} else if (_trailingSleepRatio >= BACK_OFF_TRIGGER_SLEEP_PERCENTAGE_THRESHOLD && _performanceThrottlingRatio != 0) {
|
||||
qDebug() << "Mixer is recovering";
|
||||
// back off the required loudness
|
||||
_performanceThrottlingRatio = std::max(0.0f, _performanceThrottlingRatio - RATIO_BACK_OFF);
|
||||
hasRatioChanged = true;
|
||||
}
|
||||
|
||||
qDebug() << "Mixer is recovering, sleeping" << _trailingSleepRatio * 100 << "% of frame time. Old cutoff was"
|
||||
<< lastCutoffRatio << "and is now" << _performanceThrottlingRatio;
|
||||
hasRatioChanged = true;
|
||||
if (hasRatioChanged) {
|
||||
// set out min audability threshold from the new ratio
|
||||
_minAudibilityThreshold = LOUDNESS_TO_DISTANCE_RATIO / (2.0f * (1.0f - _performanceThrottlingRatio));
|
||||
framesSinceCutoffEvent = 0;
|
||||
|
||||
qDebug() << "Sleeping" << _trailingSleepRatio << "of frame";
|
||||
qDebug() << "Cutoff is" << _performanceThrottlingRatio;
|
||||
qDebug() << "Minimum audibility to be mixed is" << _minAudibilityThreshold;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasRatioChanged) {
|
||||
// set out min audability threshold from the new ratio
|
||||
_minAudibilityThreshold = LOUDNESS_TO_DISTANCE_RATIO / (2.0f * (1.0f - _performanceThrottlingRatio));
|
||||
qDebug() << "Minimum audability required to be mixed is now" << _minAudibilityThreshold;
|
||||
|
||||
framesSinceCutoffEvent = 0;
|
||||
if (!hasRatioChanged) {
|
||||
++framesSinceCutoffEvent;
|
||||
}
|
||||
}
|
||||
|
||||
if (!hasRatioChanged) {
|
||||
++framesSinceCutoffEvent;
|
||||
}
|
||||
|
||||
// mix
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
|
||||
if (node->getLinkedData()) {
|
||||
AudioMixerClientData* nodeData = (AudioMixerClientData*)node->getLinkedData();
|
||||
|
||||
// this function will attempt to pop a frame from each audio stream.
|
||||
// a pointer to the popped data is stored as a member in InboundAudioStream.
|
||||
// That's how the popped audio data will be read for mixing (but only if the pop was successful)
|
||||
nodeData->checkBuffersBeforeFrameSend();
|
||||
_sumStreams += nodeData->checkBuffersBeforeFrameSend();
|
||||
|
||||
// if the stream should be muted, send mute packet
|
||||
if (nodeData->getAvatarAudioStream()
|
||||
|
@ -760,7 +826,8 @@ void AudioMixer::broadcastMixes() {
|
|||
|
||||
std::unique_ptr<NLPacket> mixPacket;
|
||||
|
||||
if (mixHasAudio) {
|
||||
if (mixHasAudio || nodeData->shouldFlushEncoder()) {
|
||||
|
||||
int mixPacketBytes = sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE
|
||||
+ AudioConstants::NETWORK_FRAME_BYTES_STEREO;
|
||||
mixPacket = NLPacket::create(PacketType::MixedAudio, mixPacketBytes);
|
||||
|
@ -773,12 +840,17 @@ void AudioMixer::broadcastMixes() {
|
|||
QString codecInPacket = nodeData->getCodecName();
|
||||
mixPacket->writeString(codecInPacket);
|
||||
|
||||
QByteArray decodedBuffer(reinterpret_cast<char*>(_clampedSamples), AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||
QByteArray encodedBuffer;
|
||||
nodeData->encode(decodedBuffer, encodedBuffer);
|
||||
|
||||
if (mixHasAudio) {
|
||||
QByteArray decodedBuffer(reinterpret_cast<char*>(_clampedSamples), AudioConstants::NETWORK_FRAME_BYTES_STEREO);
|
||||
nodeData->encode(decodedBuffer, encodedBuffer);
|
||||
} else {
|
||||
// time to flush, which resets the shouldFlush until next time we encode something
|
||||
nodeData->encodeFrameOfZeros(encodedBuffer);
|
||||
}
|
||||
// pack mixed audio samples
|
||||
mixPacket->write(encodedBuffer.constData(), encodedBuffer.size());
|
||||
|
||||
} else {
|
||||
int silentPacketBytes = sizeof(quint16) + sizeof(quint16) + AudioConstants::MAX_CODEC_NAME_LENGTH_ON_WIRE;
|
||||
mixPacket = NLPacket::create(PacketType::SilentAudioFrame, silentPacketBytes);
|
||||
|
@ -818,24 +890,32 @@ void AudioMixer::broadcastMixes() {
|
|||
|
||||
++_numStatFrames;
|
||||
|
||||
// since we're a while loop we need to help Qt's event processing
|
||||
QCoreApplication::processEvents();
|
||||
// play nice with qt event-looping
|
||||
{
|
||||
// since we're a while loop we need to help qt's event processing
|
||||
QCoreApplication::processEvents();
|
||||
|
||||
if (_isFinished) {
|
||||
// at this point the audio-mixer is done
|
||||
// check if we have a deferred delete event to process (which we should once finished)
|
||||
QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete);
|
||||
break;
|
||||
if (_isFinished) {
|
||||
// alert qt that this is finished
|
||||
QCoreApplication::sendPostedEvents(this, QEvent::DeferredDelete);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// push the next frame timestamp to when we should send the next
|
||||
nextFrameTimestamp += std::chrono::microseconds(AudioConstants::NETWORK_FRAME_USECS);
|
||||
// sleep until the next frame, if necessary
|
||||
{
|
||||
nextFrameTimestamp += std::chrono::microseconds(AudioConstants::NETWORK_FRAME_USECS);
|
||||
|
||||
// sleep as long as we need until next frame, if we can
|
||||
auto now = p_high_resolution_clock::now();
|
||||
timeToSleep = std::chrono::duration_cast<std::chrono::microseconds>(nextFrameTimestamp - now);
|
||||
auto now = p_high_resolution_clock::now();
|
||||
timeToSleep = std::chrono::duration_cast<std::chrono::microseconds>(nextFrameTimestamp - now);
|
||||
|
||||
std::this_thread::sleep_for(timeToSleep);
|
||||
if (timeToSleep.count() < 0) {
|
||||
nextFrameTimestamp = now;
|
||||
timeToSleep = std::chrono::microseconds(0);
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(timeToSleep);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -845,63 +925,62 @@ void AudioMixer::parseSettingsObject(const QJsonObject &settingsObject) {
|
|||
|
||||
// check the payload to see if we have asked for dynamicJitterBuffer support
|
||||
const QString DYNAMIC_JITTER_BUFFER_JSON_KEY = "dynamic_jitter_buffer";
|
||||
_streamSettings._dynamicJitterBuffers = audioBufferGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool();
|
||||
if (_streamSettings._dynamicJitterBuffers) {
|
||||
qDebug() << "Enable dynamic jitter buffers.";
|
||||
bool enableDynamicJitterBuffer = audioBufferGroupObject[DYNAMIC_JITTER_BUFFER_JSON_KEY].toBool();
|
||||
if (enableDynamicJitterBuffer) {
|
||||
qDebug() << "Enabling dynamic jitter buffers.";
|
||||
|
||||
bool ok;
|
||||
const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static_desired_jitter_buffer_frames";
|
||||
_numStaticJitterFrames = audioBufferGroupObject[DESIRED_JITTER_BUFFER_FRAMES_KEY].toString().toInt(&ok);
|
||||
if (!ok) {
|
||||
_numStaticJitterFrames = InboundAudioStream::DEFAULT_STATIC_JITTER_FRAMES;
|
||||
}
|
||||
qDebug() << "Static desired jitter buffer frames:" << _numStaticJitterFrames;
|
||||
} else {
|
||||
qDebug() << "Dynamic jitter buffers disabled.";
|
||||
qDebug() << "Disabling dynamic jitter buffers.";
|
||||
_numStaticJitterFrames = -1;
|
||||
}
|
||||
|
||||
// check for deprecated audio settings
|
||||
auto deprecationNotice = [](const QString& setting, const QString& value) {
|
||||
qInfo().nospace() << "[DEPRECATION NOTICE] " << setting << "(" << value << ") has been deprecated, and has no effect";
|
||||
};
|
||||
bool ok;
|
||||
const QString DESIRED_JITTER_BUFFER_FRAMES_KEY = "static_desired_jitter_buffer_frames";
|
||||
_streamSettings._staticDesiredJitterBufferFrames = audioBufferGroupObject[DESIRED_JITTER_BUFFER_FRAMES_KEY].toString().toInt(&ok);
|
||||
if (!ok) {
|
||||
_streamSettings._staticDesiredJitterBufferFrames = DEFAULT_STATIC_DESIRED_JITTER_BUFFER_FRAMES;
|
||||
}
|
||||
qDebug() << "Static desired jitter buffer frames:" << _streamSettings._staticDesiredJitterBufferFrames;
|
||||
|
||||
const QString MAX_FRAMES_OVER_DESIRED_JSON_KEY = "max_frames_over_desired";
|
||||
_streamSettings._maxFramesOverDesired = audioBufferGroupObject[MAX_FRAMES_OVER_DESIRED_JSON_KEY].toString().toInt(&ok);
|
||||
if (!ok) {
|
||||
_streamSettings._maxFramesOverDesired = DEFAULT_MAX_FRAMES_OVER_DESIRED;
|
||||
}
|
||||
qDebug() << "Max frames over desired:" << _streamSettings._maxFramesOverDesired;
|
||||
|
||||
const QString USE_STDEV_FOR_DESIRED_CALC_JSON_KEY = "use_stdev_for_desired_calc";
|
||||
_streamSettings._useStDevForJitterCalc = audioBufferGroupObject[USE_STDEV_FOR_DESIRED_CALC_JSON_KEY].toBool();
|
||||
if (_streamSettings._useStDevForJitterCalc) {
|
||||
qDebug() << "Using stdev method for jitter calc if dynamic jitter buffers enabled";
|
||||
} else {
|
||||
qDebug() << "Using max-gap method for jitter calc if dynamic jitter buffers enabled";
|
||||
int maxFramesOverDesired = audioBufferGroupObject[MAX_FRAMES_OVER_DESIRED_JSON_KEY].toString().toInt(&ok);
|
||||
if (ok && maxFramesOverDesired != InboundAudioStream::MAX_FRAMES_OVER_DESIRED) {
|
||||
deprecationNotice(MAX_FRAMES_OVER_DESIRED_JSON_KEY, QString::number(maxFramesOverDesired));
|
||||
}
|
||||
|
||||
const QString WINDOW_STARVE_THRESHOLD_JSON_KEY = "window_starve_threshold";
|
||||
_streamSettings._windowStarveThreshold = audioBufferGroupObject[WINDOW_STARVE_THRESHOLD_JSON_KEY].toString().toInt(&ok);
|
||||
if (!ok) {
|
||||
_streamSettings._windowStarveThreshold = DEFAULT_WINDOW_STARVE_THRESHOLD;
|
||||
int windowStarveThreshold = audioBufferGroupObject[WINDOW_STARVE_THRESHOLD_JSON_KEY].toString().toInt(&ok);
|
||||
if (ok && windowStarveThreshold != InboundAudioStream::WINDOW_STARVE_THRESHOLD) {
|
||||
deprecationNotice(WINDOW_STARVE_THRESHOLD_JSON_KEY, QString::number(windowStarveThreshold));
|
||||
}
|
||||
qDebug() << "Window A starve threshold:" << _streamSettings._windowStarveThreshold;
|
||||
|
||||
const QString WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY = "window_seconds_for_desired_calc_on_too_many_starves";
|
||||
_streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = audioBufferGroupObject[WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY].toString().toInt(&ok);
|
||||
if (!ok) {
|
||||
_streamSettings._windowSecondsForDesiredCalcOnTooManyStarves = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES;
|
||||
int windowSecondsForDesiredCalcOnTooManyStarves = audioBufferGroupObject[WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY].toString().toInt(&ok);
|
||||
if (ok && windowSecondsForDesiredCalcOnTooManyStarves != InboundAudioStream::WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES) {
|
||||
deprecationNotice(WINDOW_SECONDS_FOR_DESIRED_CALC_ON_TOO_MANY_STARVES_JSON_KEY, QString::number(windowSecondsForDesiredCalcOnTooManyStarves));
|
||||
}
|
||||
qDebug() << "Window A length:" << _streamSettings._windowSecondsForDesiredCalcOnTooManyStarves << "seconds";
|
||||
|
||||
const QString WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY = "window_seconds_for_desired_reduction";
|
||||
_streamSettings._windowSecondsForDesiredReduction = audioBufferGroupObject[WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY].toString().toInt(&ok);
|
||||
if (!ok) {
|
||||
_streamSettings._windowSecondsForDesiredReduction = DEFAULT_WINDOW_SECONDS_FOR_DESIRED_REDUCTION;
|
||||
int windowSecondsForDesiredReduction = audioBufferGroupObject[WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY].toString().toInt(&ok);
|
||||
if (ok && windowSecondsForDesiredReduction != InboundAudioStream::WINDOW_SECONDS_FOR_DESIRED_REDUCTION) {
|
||||
deprecationNotice(WINDOW_SECONDS_FOR_DESIRED_REDUCTION_JSON_KEY, QString::number(windowSecondsForDesiredReduction));
|
||||
}
|
||||
|
||||
const QString USE_STDEV_FOR_JITTER_JSON_KEY = "use_stdev_for_desired_calc";
|
||||
bool useStDevForJitterCalc = audioBufferGroupObject[USE_STDEV_FOR_JITTER_JSON_KEY].toBool();
|
||||
if (useStDevForJitterCalc != InboundAudioStream::USE_STDEV_FOR_JITTER) {
|
||||
deprecationNotice(USE_STDEV_FOR_JITTER_JSON_KEY, useStDevForJitterCalc ? "true" : "false");
|
||||
}
|
||||
qDebug() << "Window B length:" << _streamSettings._windowSecondsForDesiredReduction << "seconds";
|
||||
|
||||
const QString REPETITION_WITH_FADE_JSON_KEY = "repetition_with_fade";
|
||||
_streamSettings._repetitionWithFade = audioBufferGroupObject[REPETITION_WITH_FADE_JSON_KEY].toBool();
|
||||
if (_streamSettings._repetitionWithFade) {
|
||||
qDebug() << "Repetition with fade enabled";
|
||||
} else {
|
||||
qDebug() << "Repetition with fade disabled";
|
||||
bool repetitionWithFade = audioBufferGroupObject[REPETITION_WITH_FADE_JSON_KEY].toBool();
|
||||
if (repetitionWithFade != InboundAudioStream::REPETITION_WITH_FADE) {
|
||||
deprecationNotice(REPETITION_WITH_FADE_JSON_KEY, repetitionWithFade ? "true" : "false");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -35,11 +35,11 @@ public:
|
|||
|
||||
public slots:
|
||||
/// threaded run of assignment
|
||||
void run();
|
||||
void run() override;
|
||||
|
||||
void sendStatsPacket();
|
||||
void sendStatsPacket() override;
|
||||
|
||||
static const InboundAudioStream::Settings& getStreamSettings() { return _streamSettings; }
|
||||
static int getStaticJitterFrames() { return _numStaticJitterFrames; }
|
||||
|
||||
private slots:
|
||||
void broadcastMixes();
|
||||
|
@ -48,10 +48,12 @@ private slots:
|
|||
void handleNegotiateAudioFormat(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
void handleNodeKilled(SharedNodePointer killedNode);
|
||||
void handleNodeIgnoreRequestPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
void handleKillAvatarPacket(QSharedPointer<ReceivedMessage> packet, SharedNodePointer sendingNode);
|
||||
|
||||
void removeHRTFsForFinishedInjector(const QUuid& streamID);
|
||||
|
||||
private:
|
||||
AudioMixerClientData* getOrCreateClientData(Node* node);
|
||||
void domainSettingsRequestComplete();
|
||||
|
||||
/// adds one stream to the mix for a listening node
|
||||
|
@ -85,6 +87,7 @@ private:
|
|||
float _attenuationPerDoublingInDistance;
|
||||
float _noiseMutingThreshold;
|
||||
int _numStatFrames { 0 };
|
||||
int _sumStreams { 0 };
|
||||
int _sumListeners { 0 };
|
||||
int _hrtfRenders { 0 };
|
||||
int _hrtfSilentRenders { 0 };
|
||||
|
@ -112,7 +115,7 @@ private:
|
|||
};
|
||||
QVector<ReverbSettings> _zoneReverbSettings;
|
||||
|
||||
static InboundAudioStream::Settings _streamSettings;
|
||||
static int _numStaticJitterFrames; // -1 denotes dynamic jitter buffering
|
||||
|
||||
static bool _enableFilter;
|
||||
};
|
||||
|
|
|
@ -49,7 +49,7 @@ AudioMixerClientData::~AudioMixerClientData() {
|
|||
|
||||
AvatarAudioStream* AudioMixerClientData::getAvatarAudioStream() {
|
||||
QReadLocker readLocker { &_streamsLock };
|
||||
|
||||
|
||||
auto it = _audioStreams.find(QUuid());
|
||||
if (it != _audioStreams.end()) {
|
||||
return dynamic_cast<AvatarAudioStream*>(it->second.get());
|
||||
|
@ -73,11 +73,19 @@ void AudioMixerClientData::removeHRTFForStream(const QUuid& nodeID, const QUuid&
|
|||
}
|
||||
}
|
||||
|
||||
void AudioMixerClientData::removeAgentAvatarAudioStream() {
|
||||
QWriteLocker writeLocker { &_streamsLock };
|
||||
auto it = _audioStreams.find(QUuid());
|
||||
if (it != _audioStreams.end()) {
|
||||
_audioStreams.erase(it);
|
||||
}
|
||||
writeLocker.unlock();
|
||||
}
|
||||
|
||||
int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
||||
PacketType packetType = message.getType();
|
||||
|
||||
if (packetType == PacketType::AudioStreamStats) {
|
||||
|
||||
if (packetType == PacketType::AudioStreamStats) {
|
||||
// skip over header, appendFlag, and num stats packed
|
||||
message.seek(sizeof(quint8) + sizeof(quint16));
|
||||
|
||||
|
@ -109,11 +117,11 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
|
||||
bool isStereo = channelFlag == 1;
|
||||
|
||||
auto avatarAudioStream = new AvatarAudioStream(isStereo, AudioMixer::getStreamSettings());
|
||||
auto avatarAudioStream = new AvatarAudioStream(isStereo, AudioMixer::getStaticJitterFrames());
|
||||
avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO);
|
||||
qDebug() << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
|
||||
|
||||
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioMixerClientData::sendSelectAudioFormat);
|
||||
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec, this, &AudioMixerClientData::handleMismatchAudioFormat);
|
||||
|
||||
auto emplaced = _audioStreams.emplace(
|
||||
QUuid(),
|
||||
|
@ -143,7 +151,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
|
||||
if (streamIt == _audioStreams.end()) {
|
||||
// we don't have this injected stream yet, so add it
|
||||
auto injectorStream = new InjectedAudioStream(streamIdentifier, isStereo, AudioMixer::getStreamSettings());
|
||||
auto injectorStream = new InjectedAudioStream(streamIdentifier, isStereo, AudioMixer::getStaticJitterFrames());
|
||||
|
||||
#if INJECTORS_SUPPORT_CODECS
|
||||
injectorStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
|
@ -180,7 +188,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
void AudioMixerClientData::checkBuffersBeforeFrameSend() {
|
||||
int AudioMixerClientData::checkBuffersBeforeFrameSend() {
|
||||
QWriteLocker writeLocker { &_streamsLock };
|
||||
|
||||
auto it = _audioStreams.begin();
|
||||
|
@ -208,6 +216,8 @@ void AudioMixerClientData::checkBuffersBeforeFrameSend() {
|
|||
++it;
|
||||
}
|
||||
}
|
||||
|
||||
return (int)_audioStreams.size();
|
||||
}
|
||||
|
||||
bool AudioMixerClientData::shouldSendStats(int frameNumber) {
|
||||
|
@ -218,11 +228,10 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer&
|
|||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
||||
// The append flag is a boolean value that will be packed right after the header. The first packet sent
|
||||
// inside this method will have 0 for this flag, while every subsequent packet will have 1 for this flag.
|
||||
// The sole purpose of this flag is so the client can clear its map of injected audio stream stats when
|
||||
// it receives a packet with an appendFlag of 0. This prevents the buildup of dead audio stream stats in the client.
|
||||
quint8 appendFlag = 0;
|
||||
// The append flag is a boolean value that will be packed right after the header.
|
||||
// This flag allows the client to know when it has received all stats packets, so it can group any downstream effects,
|
||||
// and clear its cache of injector stream stats; it helps to prevent buildup of dead audio stream stats in the client.
|
||||
quint8 appendFlag = AudioStreamStats::START;
|
||||
|
||||
auto streamsCopy = getAudioStreams();
|
||||
|
||||
|
@ -233,14 +242,21 @@ void AudioMixerClientData::sendAudioStreamStatsPackets(const SharedNodePointer&
|
|||
while (numStreamStatsRemaining > 0) {
|
||||
auto statsPacket = NLPacket::create(PacketType::AudioStreamStats);
|
||||
|
||||
// pack the append flag in this packet
|
||||
statsPacket->writePrimitive(appendFlag);
|
||||
appendFlag = 1;
|
||||
|
||||
int numStreamStatsRoomFor = (int)(statsPacket->size() - sizeof(quint8) - sizeof(quint16)) / sizeof(AudioStreamStats);
|
||||
|
||||
// calculate and pack the number of stream stats to follow
|
||||
// calculate the number of stream stats to follow
|
||||
quint16 numStreamStatsToPack = std::min(numStreamStatsRemaining, numStreamStatsRoomFor);
|
||||
|
||||
// is this the terminal packet?
|
||||
if (numStreamStatsRemaining <= numStreamStatsToPack) {
|
||||
appendFlag |= AudioStreamStats::END;
|
||||
}
|
||||
|
||||
// pack the append flag in this packet
|
||||
statsPacket->writePrimitive(appendFlag);
|
||||
appendFlag = 0;
|
||||
|
||||
// pack the number of stream stats to follow
|
||||
statsPacket->writePrimitive(numStreamStatsToPack);
|
||||
|
||||
// pack the calculated number of stream stats
|
||||
|
@ -270,6 +286,7 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
|||
downstreamStats["desired"] = streamStats._desiredJitterBufferFrames;
|
||||
downstreamStats["available_avg_10s"] = streamStats._framesAvailableAverage;
|
||||
downstreamStats["available"] = (double) streamStats._framesAvailable;
|
||||
downstreamStats["unplayed"] = (double) streamStats._unplayedMs;
|
||||
downstreamStats["starves"] = (double) streamStats._starveCount;
|
||||
downstreamStats["not_mixed"] = (double) streamStats._consecutiveNotMixedCount;
|
||||
downstreamStats["overflows"] = (double) streamStats._overflowCount;
|
||||
|
@ -294,6 +311,7 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
|||
upstreamStats["desired_calc"] = avatarAudioStream->getCalculatedJitterBufferFrames();
|
||||
upstreamStats["available_avg_10s"] = streamStats._framesAvailableAverage;
|
||||
upstreamStats["available"] = (double) streamStats._framesAvailable;
|
||||
upstreamStats["unplayed"] = (double) streamStats._unplayedMs;
|
||||
upstreamStats["starves"] = (double) streamStats._starveCount;
|
||||
upstreamStats["not_mixed"] = (double) streamStats._consecutiveNotMixedCount;
|
||||
upstreamStats["overflows"] = (double) streamStats._overflowCount;
|
||||
|
@ -323,6 +341,7 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
|||
upstreamStats["desired_calc"] = injectorPair.second->getCalculatedJitterBufferFrames();
|
||||
upstreamStats["available_avg_10s"] = streamStats._framesAvailableAverage;
|
||||
upstreamStats["available"] = (double) streamStats._framesAvailable;
|
||||
upstreamStats["unplayed"] = (double) streamStats._unplayedMs;
|
||||
upstreamStats["starves"] = (double) streamStats._starveCount;
|
||||
upstreamStats["not_mixed"] = (double) streamStats._consecutiveNotMixedCount;
|
||||
upstreamStats["overflows"] = (double) streamStats._overflowCount;
|
||||
|
@ -345,6 +364,14 @@ QJsonObject AudioMixerClientData::getAudioStreamStats() {
|
|||
return result;
|
||||
}
|
||||
|
||||
void AudioMixerClientData::handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec) {
|
||||
qDebug() << __FUNCTION__ <<
|
||||
"sendingNode:" << *node <<
|
||||
"currentCodec:" << currentCodec <<
|
||||
"receivedCodec:" << recievedCodec;
|
||||
sendSelectAudioFormat(node, currentCodec);
|
||||
}
|
||||
|
||||
void AudioMixerClientData::sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName) {
|
||||
auto replyPacket = NLPacket::create(PacketType::SelectedAudioFormat);
|
||||
replyPacket->writeString(selectedCodecName);
|
||||
|
@ -352,6 +379,17 @@ void AudioMixerClientData::sendSelectAudioFormat(SharedNodePointer node, const Q
|
|||
nodeList->sendPacket(std::move(replyPacket), *node);
|
||||
}
|
||||
|
||||
void AudioMixerClientData::encodeFrameOfZeros(QByteArray& encodedZeros) {
|
||||
static QByteArray zeros(AudioConstants::NETWORK_FRAME_BYTES_STEREO, 0);
|
||||
if (_shouldFlushEncoder) {
|
||||
if (_encoder) {
|
||||
_encoder->encode(zeros, encodedZeros);
|
||||
} else {
|
||||
encodedZeros = zeros;
|
||||
}
|
||||
}
|
||||
_shouldFlushEncoder = false;
|
||||
}
|
||||
|
||||
void AudioMixerClientData::setupCodec(CodecPluginPointer codec, const QString& codecName) {
|
||||
cleanupCodec(); // cleanup any previously allocated coders first
|
||||
|
|
|
@ -49,17 +49,20 @@ public:
|
|||
|
||||
// removes an AudioHRTF object for a given stream
|
||||
void removeHRTFForStream(const QUuid& nodeID, const QUuid& streamID = QUuid());
|
||||
|
||||
int parseData(ReceivedMessage& message);
|
||||
|
||||
void checkBuffersBeforeFrameSend();
|
||||
void removeAgentAvatarAudioStream();
|
||||
|
||||
int parseData(ReceivedMessage& message) override;
|
||||
|
||||
// attempt to pop a frame from each audio stream, and return the number of streams from this client
|
||||
int checkBuffersBeforeFrameSend();
|
||||
|
||||
void removeDeadInjectedStreams();
|
||||
|
||||
QJsonObject getAudioStreamStats();
|
||||
|
||||
|
||||
void sendAudioStreamStatsPackets(const SharedNodePointer& destinationNode);
|
||||
|
||||
|
||||
void incrementOutgoingMixedAudioSequenceNumber() { _outgoingMixedAudioSequenceNumber++; }
|
||||
quint16 getOutgoingSequenceNumber() const { return _outgoingMixedAudioSequenceNumber; }
|
||||
|
||||
|
@ -76,7 +79,11 @@ public:
|
|||
} else {
|
||||
encodedBuffer = decodedBuffer;
|
||||
}
|
||||
// once you have encoded, you need to flush eventually.
|
||||
_shouldFlushEncoder = true;
|
||||
}
|
||||
void encodeFrameOfZeros(QByteArray& encodedZeros);
|
||||
bool shouldFlushEncoder() { return _shouldFlushEncoder; }
|
||||
|
||||
QString getCodecName() { return _selectedCodecName; }
|
||||
|
||||
|
@ -84,6 +91,7 @@ signals:
|
|||
void injectorStreamFinished(const QUuid& streamIdentifier);
|
||||
|
||||
public slots:
|
||||
void handleMismatchAudioFormat(SharedNodePointer node, const QString& currentCodec, const QString& recievedCodec);
|
||||
void sendSelectAudioFormat(SharedNodePointer node, const QString& selectedCodecName);
|
||||
|
||||
private:
|
||||
|
@ -104,6 +112,8 @@ private:
|
|||
QString _selectedCodecName;
|
||||
Encoder* _encoder{ nullptr }; // for outbound mixed stream
|
||||
Decoder* _decoder{ nullptr }; // for mic stream
|
||||
|
||||
bool _shouldFlushEncoder { false };
|
||||
};
|
||||
|
||||
#endif // hifi_AudioMixerClientData_h
|
||||
|
|
|
@ -13,10 +13,8 @@
|
|||
|
||||
#include "AvatarAudioStream.h"
|
||||
|
||||
AvatarAudioStream::AvatarAudioStream(bool isStereo, const InboundAudioStream::Settings& settings) :
|
||||
PositionalAudioStream(PositionalAudioStream::Microphone, isStereo, settings)
|
||||
{
|
||||
}
|
||||
AvatarAudioStream::AvatarAudioStream(bool isStereo, int numStaticJitterFrames) :
|
||||
PositionalAudioStream(PositionalAudioStream::Microphone, isStereo, numStaticJitterFrames) {}
|
||||
|
||||
int AvatarAudioStream::parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) {
|
||||
int readBytes = 0;
|
||||
|
|
|
@ -18,14 +18,14 @@
|
|||
|
||||
class AvatarAudioStream : public PositionalAudioStream {
|
||||
public:
|
||||
AvatarAudioStream(bool isStereo, const InboundAudioStream::Settings& settings);
|
||||
AvatarAudioStream(bool isStereo, int numStaticJitterFrames = -1);
|
||||
|
||||
private:
|
||||
// disallow copying of AvatarAudioStream objects
|
||||
AvatarAudioStream(const AvatarAudioStream&);
|
||||
AvatarAudioStream& operator= (const AvatarAudioStream&);
|
||||
|
||||
int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples);
|
||||
int parseStreamProperties(PacketType type, const QByteArray& packetAfterSeqNum, int& numAudioSamples) override;
|
||||
};
|
||||
|
||||
#endif // hifi_AvatarAudioStream_h
|
||||
|
|
|
@ -413,6 +413,9 @@ void AvatarMixer::handleAvatarDataPacket(QSharedPointer<ReceivedMessage> message
|
|||
}
|
||||
|
||||
void AvatarMixer::handleAvatarIdentityPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->getOrCreateLinkedData(senderNode);
|
||||
|
||||
if (senderNode->getLinkedData()) {
|
||||
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(senderNode->getLinkedData());
|
||||
if (nodeData != nullptr) {
|
||||
|
|
|
@ -27,11 +27,11 @@ public:
|
|||
~AvatarMixer();
|
||||
public slots:
|
||||
/// runs the avatar mixer
|
||||
void run();
|
||||
void run() override;
|
||||
|
||||
void nodeKilled(SharedNodePointer killedNode);
|
||||
|
||||
void sendStatsPacket();
|
||||
|
||||
void sendStatsPacket() override;
|
||||
|
||||
private slots:
|
||||
void handleAvatarDataPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
@ -45,14 +45,14 @@ private slots:
|
|||
private:
|
||||
void broadcastAvatarData();
|
||||
void parseDomainServerSettings(const QJsonObject& domainSettings);
|
||||
|
||||
|
||||
QThread _broadcastThread;
|
||||
|
||||
|
||||
p_high_resolution_clock::time_point _lastFrameTimestamp;
|
||||
|
||||
|
||||
float _trailingSleepRatio { 1.0f };
|
||||
float _performanceThrottlingRatio { 0.0f };
|
||||
|
||||
|
||||
int _sumListeners { 0 };
|
||||
int _numStatFrames { 0 };
|
||||
int _sumIdentityPackets { 0 };
|
||||
|
|
|
@ -46,14 +46,21 @@ AnimationDetails ScriptableAvatar::getAnimationDetails() {
|
|||
return _animationDetails;
|
||||
}
|
||||
|
||||
void ScriptableAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||
_bind.reset();
|
||||
_animSkeleton.reset();
|
||||
AvatarData::setSkeletonModelURL(skeletonModelURL);
|
||||
}
|
||||
void ScriptableAvatar::update(float deltatime) {
|
||||
if (_bind.isNull() && !_skeletonFBXURL.isEmpty()) { // AvatarData will parse the .fst, but not get the .fbx skeleton.
|
||||
_bind = DependencyManager::get<AnimationCache>()->getAnimation(_skeletonFBXURL);
|
||||
}
|
||||
|
||||
// Run animation
|
||||
if (_animation && _animation->isLoaded() && _animation->getFrames().size() > 0 && _bind->isLoaded()) {
|
||||
|
||||
if (_animation && _animation->isLoaded() && _animation->getFrames().size() > 0 && !_bind.isNull() && _bind->isLoaded()) {
|
||||
if (!_animSkeleton) {
|
||||
_animSkeleton = std::make_shared<AnimSkeleton>(_bind->getGeometry());
|
||||
}
|
||||
float currentFrame = _animationDetails.currentFrame + deltatime * _animationDetails.fps;
|
||||
if (_animationDetails.loop || currentFrame < _animationDetails.lastFrame) {
|
||||
while (currentFrame >= _animationDetails.lastFrame) {
|
||||
|
@ -64,14 +71,16 @@ void ScriptableAvatar::update(float deltatime) {
|
|||
const QVector<FBXJoint>& modelJoints = _bind->getGeometry().joints;
|
||||
QStringList animationJointNames = _animation->getJointNames();
|
||||
|
||||
if (_jointData.size() != modelJoints.size()) {
|
||||
_jointData.resize(modelJoints.size());
|
||||
const int nJoints = modelJoints.size();
|
||||
if (_jointData.size() != nJoints) {
|
||||
_jointData.resize(nJoints);
|
||||
}
|
||||
|
||||
const int frameCount = _animation->getFrames().size();
|
||||
const FBXAnimationFrame& floorFrame = _animation->getFrames().at((int)glm::floor(currentFrame) % frameCount);
|
||||
const FBXAnimationFrame& ceilFrame = _animation->getFrames().at((int)glm::ceil(currentFrame) % frameCount);
|
||||
const float frameFraction = glm::fract(currentFrame);
|
||||
std::vector<AnimPose> poses = _animSkeleton->getRelativeDefaultPoses();
|
||||
|
||||
for (int i = 0; i < animationJointNames.size(); i++) {
|
||||
const QString& name = animationJointNames[i];
|
||||
|
@ -79,18 +88,21 @@ void ScriptableAvatar::update(float deltatime) {
|
|||
// trusting the .fst (which is sometimes not updated to match changes to .fbx).
|
||||
int mapping = _bind->getGeometry().getJointIndex(name);
|
||||
if (mapping != -1 && !_maskedJoints.contains(name)) {
|
||||
JointData& data = _jointData[mapping];
|
||||
|
||||
auto newRotation = modelJoints[mapping].preRotation *
|
||||
safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction);
|
||||
// We could probably do translations as in interpolation in model space (rather than the parent space that each frame is in),
|
||||
// but we don't do so for MyAvatar yet, so let's not be different here.
|
||||
if (data.rotation != newRotation) {
|
||||
data.rotation = newRotation;
|
||||
data.rotationSet = true;
|
||||
}
|
||||
// Eventually, this should probably deal with post rotations and translations, too.
|
||||
poses[mapping].rot = modelJoints[mapping].preRotation *
|
||||
safeMix(floorFrame.rotations.at(i), ceilFrame.rotations.at(i), frameFraction);;
|
||||
}
|
||||
}
|
||||
_animSkeleton->convertRelativePosesToAbsolute(poses);
|
||||
for (int i = 0; i < nJoints; i++) {
|
||||
JointData& data = _jointData[i];
|
||||
AnimPose& pose = poses[i];
|
||||
if (data.rotation != pose.rot) {
|
||||
data.rotation = pose.rot;
|
||||
data.rotationSet = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
_animation.clear();
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#define hifi_ScriptableAvatar_h
|
||||
|
||||
#include <AnimationCache.h>
|
||||
#include <AnimSkeleton.h>
|
||||
#include <AvatarData.h>
|
||||
#include <ScriptEngine.h>
|
||||
|
||||
|
@ -25,6 +26,7 @@ public:
|
|||
bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
|
||||
Q_INVOKABLE void stopAnimation();
|
||||
Q_INVOKABLE AnimationDetails getAnimationDetails();
|
||||
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
|
||||
|
||||
private slots:
|
||||
void update(float deltatime);
|
||||
|
@ -34,6 +36,7 @@ private:
|
|||
AnimationDetails _animationDetails;
|
||||
QStringList _maskedJoints;
|
||||
AnimationPointer _bind; // a sleazy way to get the skeleton, given the various library/cmake dependencies
|
||||
std::shared_ptr<AnimSkeleton> _animSkeleton;
|
||||
};
|
||||
|
||||
#endif // hifi_ScriptableAvatar_h
|
|
@ -25,7 +25,8 @@ class AssignmentParentFinder : public SpatialParentFinder {
|
|||
public:
|
||||
AssignmentParentFinder(EntityTreePointer tree) : _tree(tree) { }
|
||||
virtual ~AssignmentParentFinder() { }
|
||||
virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success, SpatialParentTree* entityTree = nullptr) const;
|
||||
virtual SpatiallyNestableWeakPointer find(QUuid parentID, bool& success,
|
||||
SpatialParentTree* entityTree = nullptr) const override;
|
||||
|
||||
protected:
|
||||
EntityTreePointer _tree;
|
||||
|
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
class EntityNodeData : public OctreeQueryNode {
|
||||
public:
|
||||
virtual PacketType getMyPacketType() const { return PacketType::EntityData; }
|
||||
virtual PacketType getMyPacketType() const override { return PacketType::EntityData; }
|
||||
|
||||
quint64 getLastDeletedEntitiesSentAt() const { return _lastDeletedEntitiesSentAt; }
|
||||
void setLastDeletedEntitiesSentAt(quint64 sentAt) { _lastDeletedEntitiesSentAt = sentAt; }
|
||||
|
|
|
@ -24,9 +24,9 @@ public:
|
|||
MessagesMixer(ReceivedMessage& message);
|
||||
|
||||
public slots:
|
||||
void run();
|
||||
void run() override;
|
||||
void nodeKilled(SharedNodePointer killedNode);
|
||||
void sendStatsPacket();
|
||||
void sendStatsPacket() override;
|
||||
|
||||
private slots:
|
||||
void handleMessages(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
|
|
@ -74,15 +74,15 @@ public:
|
|||
|
||||
NodeToSenderStatsMap getSingleSenderStats() { QReadLocker locker(&_senderStatsLock); return _singleSenderStats; }
|
||||
|
||||
virtual void terminating() { _shuttingDown = true; ReceivedPacketProcessor::terminating(); }
|
||||
virtual void terminating() override { _shuttingDown = true; ReceivedPacketProcessor::terminating(); }
|
||||
|
||||
protected:
|
||||
|
||||
virtual void processPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
virtual void processPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode) override;
|
||||
|
||||
virtual unsigned long getMaxWait() const;
|
||||
virtual void preProcess();
|
||||
virtual void midProcess();
|
||||
virtual unsigned long getMaxWait() const override;
|
||||
virtual void preProcess() override;
|
||||
virtual void midProcess() override;
|
||||
|
||||
private:
|
||||
int sendNackPackets();
|
||||
|
|
|
@ -47,7 +47,7 @@ public:
|
|||
|
||||
protected:
|
||||
/// Implements generic processing behavior for this thread.
|
||||
virtual bool process();
|
||||
virtual bool process() override;
|
||||
|
||||
private:
|
||||
int handlePacketSend(SharedNodePointer node, OctreeQueryNode* nodeData, int& trueBytesSent, int& truePacketsSent, bool dontSuppressDuplicate = false);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include <AccountManager.h>
|
||||
#include <HTTPConnection.h>
|
||||
#include <LogHandler.h>
|
||||
#include <shared/NetworkUtils.h>
|
||||
#include <NetworkingConstants.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <UUID.h>
|
||||
|
@ -1063,6 +1064,12 @@ void OctreeServer::readConfiguration() {
|
|||
_wantBackup = !noBackup;
|
||||
qDebug() << "wantBackup=" << _wantBackup;
|
||||
|
||||
if (!readOptionString("backupDirectoryPath", settingsSectionObject, _backupDirectoryPath)) {
|
||||
_backupDirectoryPath = "";
|
||||
}
|
||||
|
||||
qDebug() << "backupDirectoryPath=" << _backupDirectoryPath;
|
||||
|
||||
readOptionBool(QString("persistFileDownload"), settingsSectionObject, _persistFileDownload);
|
||||
qDebug() << "persistFileDownload=" << _persistFileDownload;
|
||||
|
||||
|
@ -1160,25 +1167,25 @@ void OctreeServer::domainSettingsRequestComplete() {
|
|||
// If persist filename does not exist, let's see if there is one beside the application binary
|
||||
// If there is, let's copy it over to our target persist directory
|
||||
QDir persistPath { _persistFilePath };
|
||||
QString absoluteFilePath = persistPath.absolutePath();
|
||||
QString persistAbsoluteFilePath = persistPath.absolutePath();
|
||||
|
||||
if (persistPath.isRelative()) {
|
||||
// if the domain settings passed us a relative path, make an absolute path that is relative to the
|
||||
// default data directory
|
||||
absoluteFilePath = QDir(ServerPathUtils::getDataFilePath("entities/")).absoluteFilePath(_persistFilePath);
|
||||
persistAbsoluteFilePath = QDir(ServerPathUtils::getDataFilePath("entities/")).absoluteFilePath(_persistFilePath);
|
||||
}
|
||||
|
||||
static const QString ENTITY_PERSIST_EXTENSION = ".json.gz";
|
||||
|
||||
// force the persist file to end with .json.gz
|
||||
if (!absoluteFilePath.endsWith(ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive)) {
|
||||
absoluteFilePath += ENTITY_PERSIST_EXTENSION;
|
||||
if (!persistAbsoluteFilePath.endsWith(ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive)) {
|
||||
persistAbsoluteFilePath += ENTITY_PERSIST_EXTENSION;
|
||||
} else {
|
||||
// make sure the casing of .json.gz is correct
|
||||
absoluteFilePath.replace(ENTITY_PERSIST_EXTENSION, ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive);
|
||||
persistAbsoluteFilePath.replace(ENTITY_PERSIST_EXTENSION, ENTITY_PERSIST_EXTENSION, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
if (!QFile::exists(absoluteFilePath)) {
|
||||
if (!QFile::exists(persistAbsoluteFilePath)) {
|
||||
qDebug() << "Persist file does not exist, checking for existence of persist file next to application";
|
||||
|
||||
static const QString OLD_DEFAULT_PERSIST_FILENAME = "resources/models.json.gz";
|
||||
|
@ -1204,7 +1211,7 @@ void OctreeServer::domainSettingsRequestComplete() {
|
|||
pathToCopyFrom = oldDefaultPersistPath;
|
||||
}
|
||||
|
||||
QDir persistFileDirectory { QDir::cleanPath(absoluteFilePath + "/..") };
|
||||
QDir persistFileDirectory { QDir::cleanPath(persistAbsoluteFilePath + "/..") };
|
||||
|
||||
if (!persistFileDirectory.exists()) {
|
||||
qDebug() << "Creating data directory " << persistFileDirectory.absolutePath();
|
||||
|
@ -1212,16 +1219,46 @@ void OctreeServer::domainSettingsRequestComplete() {
|
|||
}
|
||||
|
||||
if (shouldCopy) {
|
||||
qDebug() << "Old persist file found, copying from " << pathToCopyFrom << " to " << absoluteFilePath;
|
||||
qDebug() << "Old persist file found, copying from " << pathToCopyFrom << " to " << persistAbsoluteFilePath;
|
||||
|
||||
QFile::copy(pathToCopyFrom, absoluteFilePath);
|
||||
QFile::copy(pathToCopyFrom, persistAbsoluteFilePath);
|
||||
} else {
|
||||
qDebug() << "No existing persist file found";
|
||||
}
|
||||
}
|
||||
|
||||
auto persistFileDirectory = QFileInfo(persistAbsoluteFilePath).absolutePath();
|
||||
if (_backupDirectoryPath.isEmpty()) {
|
||||
// Use the persist file's directory to store backups
|
||||
_backupDirectoryPath = persistFileDirectory;
|
||||
} else {
|
||||
// The backup directory has been set.
|
||||
// If relative, make it relative to the entities directory in the application data directory
|
||||
// If absolute, no resolution is necessary
|
||||
QDir backupDirectory { _backupDirectoryPath };
|
||||
QString absoluteBackupDirectory;
|
||||
if (backupDirectory.isRelative()) {
|
||||
absoluteBackupDirectory = QDir(ServerPathUtils::getDataFilePath("entities/")).absoluteFilePath(_backupDirectoryPath);
|
||||
absoluteBackupDirectory = QDir(absoluteBackupDirectory).absolutePath();
|
||||
} else {
|
||||
absoluteBackupDirectory = backupDirectory.absolutePath();
|
||||
}
|
||||
backupDirectory = QDir(absoluteBackupDirectory);
|
||||
if (!backupDirectory.exists()) {
|
||||
if (backupDirectory.mkpath(".")) {
|
||||
qDebug() << "Created backup directory";
|
||||
} else {
|
||||
qDebug() << "ERROR creating backup directory, using persist file directory";
|
||||
_backupDirectoryPath = persistFileDirectory;
|
||||
}
|
||||
} else {
|
||||
_backupDirectoryPath = absoluteBackupDirectory;
|
||||
}
|
||||
}
|
||||
qDebug() << "Backups will be stored in: " << _backupDirectoryPath;
|
||||
|
||||
// now set up PersistThread
|
||||
_persistThread = new OctreePersistThread(_tree, absoluteFilePath, _persistInterval,
|
||||
_persistThread = new OctreePersistThread(_tree, persistAbsoluteFilePath, _backupDirectoryPath, _persistInterval,
|
||||
_wantBackup, _settings, _debugTimestampNow, _persistAsFileType);
|
||||
_persistThread->initialize(true);
|
||||
}
|
||||
|
|
|
@ -120,16 +120,16 @@ public:
|
|||
static int howManyThreadsDidHandlePacketSend(quint64 since = 0);
|
||||
static int howManyThreadsDidCallWriteDatagram(quint64 since = 0);
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler);
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler) override;
|
||||
|
||||
virtual void aboutToFinish();
|
||||
virtual void aboutToFinish() override;
|
||||
|
||||
public slots:
|
||||
/// runs the octree server assignment
|
||||
void run();
|
||||
void run() override;
|
||||
virtual void nodeAdded(SharedNodePointer node);
|
||||
virtual void nodeKilled(SharedNodePointer node);
|
||||
void sendStatsPacket();
|
||||
void sendStatsPacket() override;
|
||||
|
||||
private slots:
|
||||
void domainSettingsRequestComplete();
|
||||
|
@ -172,6 +172,7 @@ protected:
|
|||
|
||||
QString _persistFilePath;
|
||||
QString _persistAsFileType;
|
||||
QString _backupDirectoryPath;
|
||||
int _packetsPerClientPerInterval;
|
||||
int _packetsTotalPerInterval;
|
||||
OctreePointer _tree; // this IS a reaveraging tree
|
||||
|
|
|
@ -29,8 +29,8 @@
|
|||
# POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
# ------------------------------------------------------------------------------
|
||||
# Android CMake toolchain file, for use with the Android NDK r5-r10c
|
||||
# Requires cmake 2.6.3 or newer (2.8.5 or newer is recommended).
|
||||
# Android CMake toolchain file, for use with the Android NDK r5-r10d
|
||||
# Requires cmake 2.6.3 or newer (2.8.9 or newer is recommended).
|
||||
# See home page: https://github.com/taka-no-me/android-cmake
|
||||
#
|
||||
# Usage Linux:
|
||||
|
@ -39,12 +39,6 @@
|
|||
# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake ..
|
||||
# $ make -j8
|
||||
#
|
||||
# Usage Linux (using standalone toolchain):
|
||||
# $ export ANDROID_STANDALONE_TOOLCHAIN=/absolute/path/to/android-toolchain
|
||||
# $ mkdir build && cd build
|
||||
# $ cmake -DCMAKE_TOOLCHAIN_FILE=path/to/the/android.toolchain.cmake ..
|
||||
# $ make -j8
|
||||
#
|
||||
# Usage Windows:
|
||||
# You need native port of make to build your project.
|
||||
# Android NDK r7 (and newer) already has make.exe on board.
|
||||
|
@ -63,11 +57,6 @@
|
|||
# ANDROID_NDK=/opt/android-ndk - path to the NDK root.
|
||||
# Can be set as environment variable. Can be set only at first cmake run.
|
||||
#
|
||||
# ANDROID_STANDALONE_TOOLCHAIN=/opt/android-toolchain - path to the
|
||||
# standalone toolchain. This option is not used if full NDK is found
|
||||
# (ignored if ANDROID_NDK is set).
|
||||
# Can be set as environment variable. Can be set only at first cmake run.
|
||||
#
|
||||
# ANDROID_ABI=armeabi-v7a - specifies the target Application Binary
|
||||
# Interface (ABI). This option nearly matches to the APP_ABI variable
|
||||
# used by ndk-build tool from Android NDK.
|
||||
|
@ -123,8 +112,8 @@
|
|||
# * x86_64-clang3.5
|
||||
#
|
||||
# ANDROID_FORCE_ARM_BUILD=OFF - set ON to generate 32-bit ARM instructions
|
||||
# instead of Thumb. Is not available for "x86" (inapplicable) and
|
||||
# "armeabi-v6 with VFP" (is forced to be ON) ABIs.
|
||||
# instead of Thumb. Is not available for "armeabi-v6 with VFP"
|
||||
# (is forced to be ON) ABI.
|
||||
#
|
||||
# ANDROID_NO_UNDEFINED=ON - set ON to show all undefined symbols as linker
|
||||
# errors even if they are not used.
|
||||
|
@ -133,13 +122,6 @@
|
|||
# libraries. Automatically turned for NDK r5x and r6x due to GLESv2
|
||||
# problems.
|
||||
#
|
||||
# LIBRARY_OUTPUT_PATH_ROOT=${CMAKE_SOURCE_DIR} - where to output binary
|
||||
# files. See additional details below.
|
||||
#
|
||||
# ANDROID_SET_OBSOLETE_VARIABLES=ON - if set, then toolchain defines some
|
||||
# obsolete variables which were used by previous versions of this file for
|
||||
# backward compatibility.
|
||||
#
|
||||
# ANDROID_STL=gnustl_static - specify the runtime to use.
|
||||
#
|
||||
# Possible values are:
|
||||
|
@ -172,6 +154,8 @@
|
|||
# Implies -frtti -fno-exceptions.
|
||||
# Available for NDK r7b and newer.
|
||||
# Silently degrades to gnustl_static if not available.
|
||||
# c++_static -> Use the LLVM libc++ runtime as a static library.
|
||||
# c++_shared -> Use the LLVM libc++ runtime as a shared library.
|
||||
#
|
||||
# ANDROID_STL_FORCE_FEATURES=ON - turn rtti and exceptions support based on
|
||||
# chosen runtime. If disabled, then the user is responsible for settings
|
||||
|
@ -200,12 +184,6 @@
|
|||
# will be set true, mutually exclusive. NEON option will be set true
|
||||
# if VFP is set to NEON.
|
||||
#
|
||||
# LIBRARY_OUTPUT_PATH_ROOT should be set in cache to determine where Android
|
||||
# libraries will be installed.
|
||||
# Default is ${CMAKE_SOURCE_DIR}, and the android libs will always be
|
||||
# under the ${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}
|
||||
# (depending on the target ABI). This is convenient for Android packaging.
|
||||
#
|
||||
# ------------------------------------------------------------------------------
|
||||
|
||||
cmake_minimum_required( VERSION 2.6.3 )
|
||||
|
@ -235,22 +213,22 @@ endif()
|
|||
# this one not so much
|
||||
set( CMAKE_SYSTEM_VERSION 1 )
|
||||
|
||||
# rpath makes low sence for Android
|
||||
# rpath makes low sense for Android
|
||||
set( CMAKE_SHARED_LIBRARY_RUNTIME_C_FLAG "" )
|
||||
set( CMAKE_SKIP_RPATH TRUE CACHE BOOL "If set, runtime paths are not added when using shared libraries." )
|
||||
|
||||
# NDK search paths
|
||||
set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r10c -r10b -r10 -r9d -r9c -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" )
|
||||
if(NOT DEFINED ANDROID_NDK_SEARCH_PATHS)
|
||||
set( ANDROID_SUPPORTED_NDK_VERSIONS ${ANDROID_EXTRA_NDK_VERSIONS} -r10d -r10c -r10b -r10 -r9d -r9c -r9b -r9 -r8e -r8d -r8c -r8b -r8 -r7c -r7b -r7 -r6b -r6 -r5c -r5b -r5 "" )
|
||||
if( NOT DEFINED ANDROID_NDK_SEARCH_PATHS )
|
||||
if( CMAKE_HOST_WIN32 )
|
||||
file( TO_CMAKE_PATH "$ENV{PROGRAMFILES}" ANDROID_NDK_SEARCH_PATHS )
|
||||
set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}/android-ndk" "$ENV{SystemDrive}/NVPACK/android-ndk" )
|
||||
set( ANDROID_NDK_SEARCH_PATHS "${ANDROID_NDK_SEARCH_PATHS}" "$ENV{SystemDrive}/NVPACK" )
|
||||
else()
|
||||
file( TO_CMAKE_PATH "$ENV{HOME}" ANDROID_NDK_SEARCH_PATHS )
|
||||
set( ANDROID_NDK_SEARCH_PATHS /opt/android-ndk "${ANDROID_NDK_SEARCH_PATHS}/NVPACK/android-ndk" )
|
||||
set( ANDROID_NDK_SEARCH_PATHS /opt "${ANDROID_NDK_SEARCH_PATHS}/NVPACK" )
|
||||
endif()
|
||||
endif()
|
||||
if(NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH)
|
||||
if( NOT DEFINED ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH )
|
||||
set( ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH /opt/android-toolchain )
|
||||
endif()
|
||||
|
||||
|
@ -272,106 +250,90 @@ set( ANDROID_DEFAULT_NDK_API_LEVEL_mips64 21 )
|
|||
|
||||
|
||||
macro( __LIST_FILTER listvar regex )
|
||||
if( ${listvar} )
|
||||
foreach( __val ${${listvar}} )
|
||||
if( __val MATCHES "${regex}" )
|
||||
list( REMOVE_ITEM ${listvar} "${__val}" )
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
if( ${listvar} )
|
||||
foreach( __val ${${listvar}} )
|
||||
if( __val MATCHES "${regex}" )
|
||||
list( REMOVE_ITEM ${listvar} "${__val}" )
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro( __INIT_VARIABLE var_name )
|
||||
set( __test_path 0 )
|
||||
foreach( __var ${ARGN} )
|
||||
if( __var STREQUAL "PATH" )
|
||||
set( __test_path 1 )
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
if( __test_path AND NOT EXISTS "${${var_name}}" )
|
||||
unset( ${var_name} CACHE )
|
||||
endif()
|
||||
if( "${${var_name}}" STREQUAL "" )
|
||||
set( __values 0 )
|
||||
set( __test_path 0 )
|
||||
foreach( __var ${ARGN} )
|
||||
if( __var STREQUAL "VALUES" )
|
||||
set( __values 1 )
|
||||
elseif( NOT __var STREQUAL "PATH" )
|
||||
set( __obsolete 0 )
|
||||
if( __var MATCHES "^OBSOLETE_.*$" )
|
||||
string( REPLACE "OBSOLETE_" "" __var "${__var}" )
|
||||
set( __obsolete 1 )
|
||||
endif()
|
||||
if( __var MATCHES "^ENV_.*$" )
|
||||
string( REPLACE "ENV_" "" __var "${__var}" )
|
||||
set( __value "$ENV{${__var}}" )
|
||||
elseif( DEFINED ${__var} )
|
||||
set( __value "${${__var}}" )
|
||||
else()
|
||||
if( __values )
|
||||
set( __value "${__var}" )
|
||||
else()
|
||||
set( __value "" )
|
||||
endif()
|
||||
endif()
|
||||
if( NOT "${__value}" STREQUAL "" )
|
||||
if( __test_path )
|
||||
if( EXISTS "${__value}" )
|
||||
file( TO_CMAKE_PATH "${__value}" ${var_name} )
|
||||
if( __obsolete AND NOT _CMAKE_IN_TRY_COMPILE )
|
||||
message( WARNING "Using value of obsolete variable ${__var} as initial value for ${var_name}. Please note, that ${__var} can be completely removed in future versions of the toolchain." )
|
||||
endif()
|
||||
break()
|
||||
endif()
|
||||
else()
|
||||
set( ${var_name} "${__value}" )
|
||||
if( __obsolete AND NOT _CMAKE_IN_TRY_COMPILE )
|
||||
message( WARNING "Using value of obsolete variable ${__var} as initial value for ${var_name}. Please note, that ${__var} can be completely removed in future versions of the toolchain." )
|
||||
endif()
|
||||
if( __var STREQUAL "PATH" )
|
||||
set( __test_path 1 )
|
||||
break()
|
||||
endif()
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
unset( __value )
|
||||
unset( __values )
|
||||
unset( __obsolete )
|
||||
elseif( __test_path )
|
||||
file( TO_CMAKE_PATH "${${var_name}}" ${var_name} )
|
||||
endif()
|
||||
unset( __test_path )
|
||||
|
||||
if( __test_path AND NOT EXISTS "${${var_name}}" )
|
||||
unset( ${var_name} CACHE )
|
||||
endif()
|
||||
|
||||
if( " ${${var_name}}" STREQUAL " " )
|
||||
set( __values 0 )
|
||||
foreach( __var ${ARGN} )
|
||||
if( __var STREQUAL "VALUES" )
|
||||
set( __values 1 )
|
||||
elseif( NOT __var STREQUAL "PATH" )
|
||||
if( __var MATCHES "^ENV_.*$" )
|
||||
string( REPLACE "ENV_" "" __var "${__var}" )
|
||||
set( __value "$ENV{${__var}}" )
|
||||
elseif( DEFINED ${__var} )
|
||||
set( __value "${${__var}}" )
|
||||
elseif( __values )
|
||||
set( __value "${__var}" )
|
||||
else()
|
||||
set( __value "" )
|
||||
endif()
|
||||
|
||||
if( NOT " ${__value}" STREQUAL " " AND (NOT __test_path OR EXISTS "${__value}") )
|
||||
set( ${var_name} "${__value}" )
|
||||
break()
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
unset( __value )
|
||||
unset( __values )
|
||||
endif()
|
||||
|
||||
if( __test_path )
|
||||
file( TO_CMAKE_PATH "${${var_name}}" ${var_name} )
|
||||
endif()
|
||||
unset( __test_path )
|
||||
endmacro()
|
||||
|
||||
macro( __DETECT_NATIVE_API_LEVEL _var _path )
|
||||
SET( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*.*$" )
|
||||
FILE( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" )
|
||||
if( NOT __apiFileContent )
|
||||
message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." )
|
||||
endif()
|
||||
string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" )
|
||||
unset( __apiFileContent )
|
||||
unset( __ndkApiLevelRegex )
|
||||
set( __ndkApiLevelRegex "^[\t ]*#define[\t ]+__ANDROID_API__[\t ]+([0-9]+)[\t ]*.*$" )
|
||||
file( STRINGS ${_path} __apiFileContent REGEX "${__ndkApiLevelRegex}" )
|
||||
if( NOT __apiFileContent )
|
||||
message( SEND_ERROR "Could not get Android native API level. Probably you have specified invalid level value, or your copy of NDK/toolchain is broken." )
|
||||
endif()
|
||||
string( REGEX REPLACE "${__ndkApiLevelRegex}" "\\1" ${_var} "${__apiFileContent}" )
|
||||
unset( __apiFileContent )
|
||||
unset( __ndkApiLevelRegex )
|
||||
endmacro()
|
||||
|
||||
macro( __DETECT_TOOLCHAIN_MACHINE_NAME _var _root )
|
||||
if( EXISTS "${_root}" )
|
||||
file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" )
|
||||
__LIST_FILTER( __gccExePath "^[.].*" )
|
||||
list( LENGTH __gccExePath __gccExePathsCount )
|
||||
if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE )
|
||||
message( WARNING "Could not determine machine name for compiler from ${_root}" )
|
||||
set( ${_var} "" )
|
||||
file( GLOB __gccExePath RELATIVE "${_root}/bin/" "${_root}/bin/*-gcc${TOOL_OS_SUFFIX}" )
|
||||
__LIST_FILTER( __gccExePath "^[.].*" )
|
||||
list( LENGTH __gccExePath __gccExePathsCount )
|
||||
if( NOT __gccExePathsCount EQUAL 1 AND NOT _CMAKE_IN_TRY_COMPILE )
|
||||
message( WARNING "Could not determine machine name for compiler from ${_root}" )
|
||||
set( ${_var} "" )
|
||||
else()
|
||||
get_filename_component( __gccExeName "${__gccExePath}" NAME_WE )
|
||||
string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" )
|
||||
endif()
|
||||
unset( __gccExePath )
|
||||
unset( __gccExePathsCount )
|
||||
unset( __gccExeName )
|
||||
else()
|
||||
get_filename_component( __gccExeName "${__gccExePath}" NAME_WE )
|
||||
string( REPLACE "-gcc" "" ${_var} "${__gccExeName}" )
|
||||
set( ${_var} "" )
|
||||
endif()
|
||||
unset( __gccExePath )
|
||||
unset( __gccExePathsCount )
|
||||
unset( __gccExeName )
|
||||
else()
|
||||
set( ${_var} "" )
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
|
||||
|
@ -419,17 +381,19 @@ if( NOT ANDROID_NDK_HOST_X64 )
|
|||
endif()
|
||||
|
||||
# see if we have path to Android NDK
|
||||
__INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK )
|
||||
if( NOT ANDROID_NDK AND NOT ANDROID_STANDALONE_TOOLCHAIN )
|
||||
__INIT_VARIABLE( ANDROID_NDK PATH ENV_ANDROID_NDK )
|
||||
endif()
|
||||
if( NOT ANDROID_NDK )
|
||||
# see if we have path to Android standalone toolchain
|
||||
__INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN OBSOLETE_ANDROID_NDK_TOOLCHAIN_ROOT OBSOLETE_ENV_ANDROID_NDK_TOOLCHAIN_ROOT )
|
||||
__INIT_VARIABLE( ANDROID_STANDALONE_TOOLCHAIN PATH ENV_ANDROID_STANDALONE_TOOLCHAIN )
|
||||
|
||||
if( NOT ANDROID_STANDALONE_TOOLCHAIN )
|
||||
#try to find Android NDK in one of the the default locations
|
||||
set( __ndkSearchPaths )
|
||||
foreach( __ndkSearchPath ${ANDROID_NDK_SEARCH_PATHS} )
|
||||
foreach( suffix ${ANDROID_SUPPORTED_NDK_VERSIONS} )
|
||||
list( APPEND __ndkSearchPaths "${__ndkSearchPath}${suffix}" )
|
||||
list( APPEND __ndkSearchPaths "${__ndkSearchPath}/android-ndk${suffix}" )
|
||||
endforeach()
|
||||
endforeach()
|
||||
__INIT_VARIABLE( ANDROID_NDK PATH VALUES ${__ndkSearchPaths} )
|
||||
|
@ -487,7 +451,7 @@ else()
|
|||
or
|
||||
export ANDROID_STANDALONE_TOOLCHAIN=~/my-android-toolchain
|
||||
or put the toolchain or NDK in the default path:
|
||||
sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH}
|
||||
sudo ln -s ~/my-android-ndk ${ANDROID_NDK_SEARCH_PATH}/android-ndk
|
||||
sudo ln -s ~/my-android-toolchain ${ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH}" )
|
||||
endif()
|
||||
|
||||
|
@ -636,7 +600,7 @@ if( BUILD_WITH_ANDROID_NDK )
|
|||
endif()
|
||||
if( NOT __availableToolchains )
|
||||
file( GLOB __availableToolchainsLst RELATIVE "${ANDROID_NDK_TOOLCHAINS_PATH}" "${ANDROID_NDK_TOOLCHAINS_PATH}/*" )
|
||||
if( __availableToolchains )
|
||||
if( __availableToolchainsLst )
|
||||
list(SORT __availableToolchainsLst) # we need clang to go after gcc
|
||||
endif()
|
||||
__LIST_FILTER( __availableToolchainsLst "^[.]" )
|
||||
|
@ -669,7 +633,7 @@ if( NOT ANDROID_SUPPORTED_ABIS )
|
|||
endif()
|
||||
|
||||
# choose target ABI
|
||||
__INIT_VARIABLE( ANDROID_ABI OBSOLETE_ARM_TARGET OBSOLETE_ARM_TARGETS VALUES ${ANDROID_SUPPORTED_ABIS} )
|
||||
__INIT_VARIABLE( ANDROID_ABI VALUES ${ANDROID_SUPPORTED_ABIS} )
|
||||
# verify that target ABI is supported
|
||||
list( FIND ANDROID_SUPPORTED_ABIS "${ANDROID_ABI}" __androidAbiIdx )
|
||||
if( __androidAbiIdx EQUAL -1 )
|
||||
|
@ -760,7 +724,7 @@ if( CMAKE_BINARY_DIR AND EXISTS "${CMAKE_BINARY_DIR}${CMAKE_FILES_DIRECTORY}/CMa
|
|||
endif()
|
||||
|
||||
if( ANDROID_ARCH_NAME STREQUAL "arm" AND NOT ARMEABI_V6 )
|
||||
__INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD OBSOLETE_FORCE_ARM VALUES OFF )
|
||||
__INIT_VARIABLE( ANDROID_FORCE_ARM_BUILD VALUES OFF )
|
||||
set( ANDROID_FORCE_ARM_BUILD ${ANDROID_FORCE_ARM_BUILD} CACHE BOOL "Use 32-bit ARM instructions instead of Thumb-1" FORCE )
|
||||
mark_as_advanced( ANDROID_FORCE_ARM_BUILD )
|
||||
else()
|
||||
|
@ -845,6 +809,7 @@ else()
|
|||
unset( __realApiLevel )
|
||||
endif()
|
||||
set( ANDROID_NATIVE_API_LEVEL "${ANDROID_NATIVE_API_LEVEL}" CACHE STRING "Android API level for native code" FORCE )
|
||||
set( CMAKE_ANDROID_API ${ANDROID_NATIVE_API_LEVEL} )
|
||||
if( CMAKE_VERSION VERSION_GREATER "2.8" )
|
||||
list( SORT ANDROID_SUPPORTED_NATIVE_API_LEVELS )
|
||||
set_property( CACHE ANDROID_NATIVE_API_LEVEL PROPERTY STRINGS ${ANDROID_SUPPORTED_NATIVE_API_LEVELS} )
|
||||
|
@ -863,23 +828,14 @@ endif()
|
|||
|
||||
# runtime choice (STL, rtti, exceptions)
|
||||
if( NOT ANDROID_STL )
|
||||
# honor legacy ANDROID_USE_STLPORT
|
||||
if( DEFINED ANDROID_USE_STLPORT )
|
||||
if( ANDROID_USE_STLPORT )
|
||||
set( ANDROID_STL stlport_static )
|
||||
endif()
|
||||
message( WARNING "You are using an obsolete variable ANDROID_USE_STLPORT to select the STL variant. Use -DANDROID_STL=stlport_static instead." )
|
||||
endif()
|
||||
if( NOT ANDROID_STL )
|
||||
set( ANDROID_STL gnustl_static )
|
||||
endif()
|
||||
endif()
|
||||
set( ANDROID_STL "${ANDROID_STL}" CACHE STRING "C++ runtime" )
|
||||
set( ANDROID_STL_FORCE_FEATURES ON CACHE BOOL "automatically configure rtti and exceptions support based on C++ runtime" )
|
||||
mark_as_advanced( ANDROID_STL ANDROID_STL_FORCE_FEATURES )
|
||||
|
||||
if( BUILD_WITH_ANDROID_NDK )
|
||||
if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared)$")
|
||||
if( NOT "${ANDROID_STL}" MATCHES "^(none|system|system_re|gabi\\+\\+_static|gabi\\+\\+_shared|stlport_static|stlport_shared|gnustl_static|gnustl_shared|c\\+\\+_static|c\\+\\+_shared)$")
|
||||
message( FATAL_ERROR "ANDROID_STL is set to invalid value \"${ANDROID_STL}\".
|
||||
The possible values are:
|
||||
none -> Do not configure the runtime.
|
||||
|
@ -891,6 +847,8 @@ The possible values are:
|
|||
stlport_shared -> Use the STLport runtime as a shared library.
|
||||
gnustl_static -> (default) Use the GNU STL as a static library.
|
||||
gnustl_shared -> Use the GNU STL as a shared library.
|
||||
c++_static -> Use the LLVM libc++ runtime as a static library.
|
||||
c++_shared -> Use the LLVM libc++ runtime as a shared library.
|
||||
" )
|
||||
endif()
|
||||
elseif( BUILD_WITH_STANDALONE_TOOLCHAIN )
|
||||
|
@ -1033,7 +991,7 @@ if( BUILD_WITH_ANDROID_NDK )
|
|||
set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_NDK}/sources/cxx-stl/system/include" )
|
||||
elseif( ANDROID_STL MATCHES "gabi" )
|
||||
if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7
|
||||
message( FATAL_ERROR "gabi++ is not awailable in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.")
|
||||
message( FATAL_ERROR "gabi++ is not available in your NDK. You have to upgrade to NDK r7 or newer to use gabi++.")
|
||||
endif()
|
||||
set( ANDROID_RTTI ON )
|
||||
set( ANDROID_EXCEPTIONS OFF )
|
||||
|
@ -1066,12 +1024,40 @@ if( BUILD_WITH_ANDROID_NDK )
|
|||
else()
|
||||
set( __libstl "${ANDROID_NDK}/sources/cxx-stl/gnu-libstdc++" )
|
||||
endif()
|
||||
set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" )
|
||||
set( ANDROID_STL_INCLUDE_DIRS "${__libstl}/include" "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/include" "${__libstl}/include/backward" )
|
||||
if( EXISTS "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" )
|
||||
set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libgnustl_static.a" )
|
||||
else()
|
||||
set( __libstl "${__libstl}/libs/${ANDROID_NDK_ABI_NAME}/libstdc++.a" )
|
||||
endif()
|
||||
elseif( ANDROID_STL MATCHES "c\\+\\+_shared" OR ANDROID_STL MATCHES "c\\+\\+_static" )
|
||||
set( ANDROID_EXCEPTIONS ON )
|
||||
set( ANDROID_RTTI ON )
|
||||
set( ANDROID_CXX_ROOT "${ANDROID_NDK}/sources/cxx-stl/" )
|
||||
set( ANDROID_LLVM_ROOT "${ANDROID_CXX_ROOT}/llvm-libc++" )
|
||||
|
||||
if( X86 )
|
||||
set( ANDROID_ABI_INCLUDE_DIRS "${ANDROID_CXX_ROOT}/gabi++/include" )
|
||||
else()
|
||||
set( ANDROID_ABI_INCLUDE_DIRS "${ANDROID_CXX_ROOT}/llvm-libc++abi/include" )
|
||||
endif()
|
||||
|
||||
set( ANDROID_STL_INCLUDE_DIRS "${ANDROID_LLVM_ROOT}/libcxx/include" "${ANDROID_ABI_INCLUDE_DIRS}" )
|
||||
|
||||
# android support sfiles
|
||||
include_directories ( SYSTEM ${ANDROID_NDK}/sources/android/support/include )
|
||||
|
||||
if(ANDROID_STL MATCHES "c\\+\\+_shared")
|
||||
set ( LLVM_LIBRARY_NAME "libc++_shared.so")
|
||||
else()
|
||||
set ( LLVM_LIBRARY_NAME "libc++_static.a" )
|
||||
endif ()
|
||||
|
||||
if( EXISTS "${ANDROID_LLVM_ROOT}/libs/${ANDROID_NDK_ABI_NAME}/${LLVM_LIBRARY_NAME}" )
|
||||
set( __libstl "${ANDROID_LLVM_ROOT}/libs/${ANDROID_NDK_ABI_NAME}/${LLVM_LIBRARY_NAME}" )
|
||||
else()
|
||||
message( FATAL_ERROR "Could not find libc++ library" )
|
||||
endif()
|
||||
else()
|
||||
message( FATAL_ERROR "Unknown runtime: ${ANDROID_STL}" )
|
||||
endif()
|
||||
|
@ -1144,7 +1130,12 @@ if( NOT CMAKE_C_COMPILER )
|
|||
endif()
|
||||
set( CMAKE_ASM_COMPILER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc${TOOL_OS_SUFFIX}" CACHE PATH "assembler" )
|
||||
set( CMAKE_STRIP "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-strip${TOOL_OS_SUFFIX}" CACHE PATH "strip" )
|
||||
set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" )
|
||||
if( EXISTS "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" )
|
||||
# Use gcc-ar if we have it for better LTO support.
|
||||
set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-gcc-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" )
|
||||
else()
|
||||
set( CMAKE_AR "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ar${TOOL_OS_SUFFIX}" CACHE PATH "archive" )
|
||||
endif()
|
||||
set( CMAKE_LINKER "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-ld${TOOL_OS_SUFFIX}" CACHE PATH "linker" )
|
||||
set( CMAKE_NM "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-nm${TOOL_OS_SUFFIX}" CACHE PATH "nm" )
|
||||
set( CMAKE_OBJCOPY "${ANDROID_TOOLCHAIN_ROOT}/bin/${ANDROID_TOOLCHAIN_MACHINE_NAME}-objcopy${TOOL_OS_SUFFIX}" CACHE PATH "objcopy" )
|
||||
|
@ -1168,7 +1159,7 @@ endif()
|
|||
include( CMakeForceCompiler )
|
||||
CMAKE_FORCE_C_COMPILER( "${CMAKE_C_COMPILER}" GNU )
|
||||
if( ANDROID_COMPILER_IS_CLANG )
|
||||
set( CMAKE_C_COMPILER_ID Clang)
|
||||
set( CMAKE_C_COMPILER_ID Clang )
|
||||
endif()
|
||||
set( CMAKE_C_PLATFORM_ID Linux )
|
||||
if( X86_64 OR MIPS64 OR ARM64_V8A )
|
||||
|
@ -1195,6 +1186,14 @@ set( CMAKE_ASM_COMPILER_FORCED TRUE )
|
|||
set( CMAKE_COMPILER_IS_GNUASM 1)
|
||||
set( CMAKE_ASM_SOURCE_FILE_EXTENSIONS s S asm )
|
||||
|
||||
foreach( lang C CXX ASM )
|
||||
if( ANDROID_COMPILER_IS_CLANG )
|
||||
set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_CLANG_VERSION} )
|
||||
else()
|
||||
set( CMAKE_${lang}_COMPILER_VERSION ${ANDROID_COMPILER_VERSION} )
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# flags and definitions
|
||||
remove_definitions( -DANDROID )
|
||||
add_definitions( -DANDROID )
|
||||
|
@ -1225,14 +1224,14 @@ endif()
|
|||
|
||||
# NDK flags
|
||||
if (ARM64_V8A )
|
||||
set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fpic -ffunction-sections -funwind-tables" )
|
||||
set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" )
|
||||
set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" )
|
||||
set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" )
|
||||
if( NOT ANDROID_COMPILER_IS_CLANG )
|
||||
set( ANDROID_CXX_FLAGS_RELEASE "${ANDROID_CXX_FLAGS_RELEASE} -funswitch-loops -finline-limit=300" )
|
||||
endif()
|
||||
elseif( ARMEABI OR ARMEABI_V7A)
|
||||
set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fpic -funwind-tables" )
|
||||
set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" )
|
||||
if( NOT ANDROID_FORCE_ARM_BUILD AND NOT ARMEABI_V6 )
|
||||
set( ANDROID_CXX_FLAGS_RELEASE "-mthumb -fomit-frame-pointer -fno-strict-aliasing" )
|
||||
set( ANDROID_CXX_FLAGS_DEBUG "-marm -fno-omit-frame-pointer -fno-strict-aliasing" )
|
||||
|
@ -1251,13 +1250,11 @@ elseif( X86 OR X86_64 )
|
|||
set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funwind-tables" )
|
||||
if( NOT ANDROID_COMPILER_IS_CLANG )
|
||||
set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -funswitch-loops -finline-limit=300" )
|
||||
else()
|
||||
set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fPIC" )
|
||||
endif()
|
||||
set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer -fstrict-aliasing" )
|
||||
set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer -fno-strict-aliasing" )
|
||||
elseif( MIPS OR MIPS64 )
|
||||
set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fpic -fno-strict-aliasing -finline-functions -ffunction-sections -funwind-tables -fmessage-length=0" )
|
||||
set( ANDROID_CXX_FLAGS "${ANDROID_CXX_FLAGS} -fno-strict-aliasing -finline-functions -funwind-tables -fmessage-length=0" )
|
||||
set( ANDROID_CXX_FLAGS_RELEASE "-fomit-frame-pointer" )
|
||||
set( ANDROID_CXX_FLAGS_DEBUG "-fno-omit-frame-pointer" )
|
||||
if( NOT ANDROID_COMPILER_IS_CLANG )
|
||||
|
@ -1342,7 +1339,7 @@ if( ANDROID_NDK_RELEASE_NUM LESS 7000 ) # before r7
|
|||
else()
|
||||
__INIT_VARIABLE( ANDROID_SO_UNDEFINED VALUES OFF )
|
||||
endif()
|
||||
__INIT_VARIABLE( ANDROID_NO_UNDEFINED OBSOLETE_NO_UNDEFINED VALUES ON )
|
||||
__INIT_VARIABLE( ANDROID_NO_UNDEFINED VALUES ON )
|
||||
__INIT_VARIABLE( ANDROID_FUNCTION_LEVEL_LINKING VALUES ON )
|
||||
__INIT_VARIABLE( ANDROID_GOLD_LINKER VALUES ON )
|
||||
__INIT_VARIABLE( ANDROID_NOEXECSTACK VALUES ON )
|
||||
|
@ -1350,7 +1347,7 @@ __INIT_VARIABLE( ANDROID_RELRO VALUES ON )
|
|||
|
||||
set( ANDROID_NO_UNDEFINED ${ANDROID_NO_UNDEFINED} CACHE BOOL "Show all undefined symbols as linker errors" )
|
||||
set( ANDROID_SO_UNDEFINED ${ANDROID_SO_UNDEFINED} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" )
|
||||
set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" )
|
||||
set( ANDROID_FUNCTION_LEVEL_LINKING ${ANDROID_FUNCTION_LEVEL_LINKING} CACHE BOOL "Put each function in separate section and enable garbage collection of unused input sections at link time" )
|
||||
set( ANDROID_GOLD_LINKER ${ANDROID_GOLD_LINKER} CACHE BOOL "Enables gold linker" )
|
||||
set( ANDROID_NOEXECSTACK ${ANDROID_NOEXECSTACK} CACHE BOOL "Allows or disallows undefined symbols in shared libraries" )
|
||||
set( ANDROID_RELRO ${ANDROID_RELRO} CACHE BOOL "Enables RELRO - a memory corruption mitigation technique" )
|
||||
|
@ -1452,6 +1449,16 @@ if( MIPS AND BUILD_WITH_ANDROID_NDK AND ANDROID_NDK_RELEASE STREQUAL "r8" )
|
|||
set( CMAKE_EXE_LINKER_FLAGS "-Wl,-T,${ANDROID_NDK_TOOLCHAINS_PATH}/${ANDROID_GCC_TOOLCHAIN_NAME}/mipself.x ${CMAKE_EXE_LINKER_FLAGS}" )
|
||||
endif()
|
||||
|
||||
# pie/pic
|
||||
if( NOT (ANDROID_NATIVE_API_LEVEL LESS 16) AND (NOT DEFINED ANDROID_APP_PIE OR ANDROID_APP_PIE) AND (CMAKE_VERSION VERSION_GREATER 2.8.8) )
|
||||
set( CMAKE_POSITION_INDEPENDENT_CODE TRUE )
|
||||
set( CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fPIE -pie")
|
||||
else()
|
||||
set( CMAKE_POSITION_INDEPENDENT_CODE FALSE )
|
||||
set( CMAKE_CXX_FLAGS "-fpic ${CMAKE_CXX_FLAGS}" )
|
||||
set( CMAKE_C_FLAGS "-fpic ${CMAKE_C_FLAGS}" )
|
||||
endif()
|
||||
|
||||
# configure rtti
|
||||
if( DEFINED ANDROID_RTTI AND ANDROID_STL_FORCE_FEATURES )
|
||||
if( ANDROID_RTTI )
|
||||
|
@ -1515,27 +1522,31 @@ if( ANDROID_EXPLICIT_CRT_LINK )
|
|||
endif()
|
||||
|
||||
# setup output directories
|
||||
set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "root for library output, set this to change where android libs are installed to" )
|
||||
set( CMAKE_INSTALL_PREFIX "${ANDROID_TOOLCHAIN_ROOT}/user" CACHE STRING "path for installing" )
|
||||
|
||||
if(NOT _CMAKE_IN_TRY_COMPILE)
|
||||
if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" )
|
||||
set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" )
|
||||
else()
|
||||
set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" )
|
||||
endif()
|
||||
set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "path for android libs" )
|
||||
if( DEFINED LIBRARY_OUTPUT_PATH_ROOT
|
||||
OR EXISTS "${CMAKE_SOURCE_DIR}/AndroidManifest.xml"
|
||||
OR (EXISTS "${CMAKE_SOURCE_DIR}/../AndroidManifest.xml" AND EXISTS "${CMAKE_SOURCE_DIR}/../jni/") )
|
||||
set( LIBRARY_OUTPUT_PATH_ROOT ${CMAKE_SOURCE_DIR} CACHE PATH "Root for binaries output, set this to change where Android libs are installed to" )
|
||||
if( NOT _CMAKE_IN_TRY_COMPILE )
|
||||
if( EXISTS "${CMAKE_SOURCE_DIR}/jni/CMakeLists.txt" )
|
||||
set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for applications" )
|
||||
else()
|
||||
set( EXECUTABLE_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/bin" CACHE PATH "Output directory for applications" )
|
||||
endif()
|
||||
set( LIBRARY_OUTPUT_PATH "${LIBRARY_OUTPUT_PATH_ROOT}/libs/${ANDROID_NDK_ABI_NAME}" CACHE PATH "Output directory for Android libs" )
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# copy shaed stl library to build directory
|
||||
if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" )
|
||||
get_filename_component( __libstlname "${__libstl}" NAME )
|
||||
execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess )
|
||||
if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}")
|
||||
message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" )
|
||||
endif()
|
||||
unset( __fileCopyProcess )
|
||||
unset( __libstlname )
|
||||
if( NOT _CMAKE_IN_TRY_COMPILE AND __libstl MATCHES "[.]so$" AND DEFINED LIBRARY_OUTPUT_PATH )
|
||||
get_filename_component( __libstlname "${__libstl}" NAME )
|
||||
execute_process( COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${__libstl}" "${LIBRARY_OUTPUT_PATH}/${__libstlname}" RESULT_VARIABLE __fileCopyProcess )
|
||||
if( NOT __fileCopyProcess EQUAL 0 OR NOT EXISTS "${LIBRARY_OUTPUT_PATH}/${__libstlname}")
|
||||
message( SEND_ERROR "Failed copying of ${__libstl} to the ${LIBRARY_OUTPUT_PATH}/${__libstlname}" )
|
||||
endif()
|
||||
unset( __fileCopyProcess )
|
||||
unset( __libstlname )
|
||||
endif()
|
||||
|
||||
|
||||
|
@ -1596,28 +1607,10 @@ macro( find_host_program )
|
|||
endmacro()
|
||||
|
||||
|
||||
macro( ANDROID_GET_ABI_RAWNAME TOOLCHAIN_FLAG VAR )
|
||||
if( "${TOOLCHAIN_FLAG}" STREQUAL "ARMEABI" )
|
||||
set( ${VAR} "armeabi" )
|
||||
elseif( "${TOOLCHAIN_FLAG}" STREQUAL "ARMEABI_V7A" )
|
||||
set( ${VAR} "armeabi-v7a" )
|
||||
elseif( "${TOOLCHAIN_FLAG}" STREQUAL "X86" )
|
||||
set( ${VAR} "x86" )
|
||||
elseif( "${TOOLCHAIN_FLAG}" STREQUAL "MIPS" )
|
||||
set( ${VAR} "mips" )
|
||||
else()
|
||||
set( ${VAR} "unknown" )
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
if (POLICY CMP0054)
|
||||
cmake_policy(SET CMP0054 NEW)
|
||||
endif ()
|
||||
|
||||
# export toolchain settings for the try_compile() command
|
||||
if( NOT PROJECT_NAME STREQUAL "CMAKE_TRY_COMPILE" )
|
||||
if( NOT _CMAKE_IN_TRY_COMPILE )
|
||||
set( __toolchain_config "")
|
||||
foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN ANDROID_SET_OBSOLETE_VARIABLES
|
||||
foreach( __var NDK_CCACHE LIBRARY_OUTPUT_PATH_ROOT ANDROID_FORBID_SYGWIN
|
||||
ANDROID_NDK_HOST_X64
|
||||
ANDROID_NDK
|
||||
ANDROID_NDK_LAYOUT
|
||||
|
@ -1636,9 +1629,10 @@ if( NOT PROJECT_NAME STREQUAL "CMAKE_TRY_COMPILE" )
|
|||
ANDROID_RELRO
|
||||
ANDROID_LIBM_PATH
|
||||
ANDROID_EXPLICIT_CRT_LINK
|
||||
ANDROID_APP_PIE
|
||||
)
|
||||
if( DEFINED ${__var} )
|
||||
if( "${__var}" MATCHES " ")
|
||||
if( ${__var} MATCHES " ")
|
||||
set( __toolchain_config "${__toolchain_config}set( ${__var} \"${${__var}}\" CACHE INTERNAL \"\" )\n" )
|
||||
else()
|
||||
set( __toolchain_config "${__toolchain_config}set( ${__var} ${${__var}} CACHE INTERNAL \"\" )\n" )
|
||||
|
@ -1663,16 +1657,6 @@ if( CMAKE_GENERATOR MATCHES "Ninja" AND CMAKE_HOST_WIN32 )
|
|||
endif()
|
||||
|
||||
|
||||
# set some obsolete variables for backward compatibility
|
||||
set( ANDROID_SET_OBSOLETE_VARIABLES ON CACHE BOOL "Define obsolete Andrid-specific cmake variables" )
|
||||
mark_as_advanced( ANDROID_SET_OBSOLETE_VARIABLES )
|
||||
if( ANDROID_SET_OBSOLETE_VARIABLES )
|
||||
set( ANDROID_API_LEVEL ${ANDROID_NATIVE_API_LEVEL} )
|
||||
set( ARM_TARGET "${ANDROID_ABI}" )
|
||||
set( ARMEABI_NDK_NAME "${ANDROID_NDK_ABI_NAME}" )
|
||||
endif()
|
||||
|
||||
|
||||
# Variables controlling behavior or set by cmake toolchain:
|
||||
# ANDROID_ABI : "armeabi-v7a" (default), "armeabi", "armeabi-v7a with NEON", "armeabi-v7a with VFPV3", "armeabi-v6 with VFP", "x86", "mips", "arm64-v8a", "x86_64", "mips64"
|
||||
# ANDROID_NATIVE_API_LEVEL : 3,4,5,8,9,14,15,16,17,18,19,21 (depends on NDK version)
|
||||
|
@ -1686,22 +1670,15 @@ endif()
|
|||
# ANDROID_RELRO : ON/OFF
|
||||
# ANDROID_FORCE_ARM_BUILD : ON/OFF
|
||||
# ANDROID_STL_FORCE_FEATURES : ON/OFF
|
||||
# ANDROID_SET_OBSOLETE_VARIABLES : ON/OFF
|
||||
# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product/<product_name>/obj/lib/libm.so) to workaround unresolved `sincos`
|
||||
# Can be set only at the first run:
|
||||
# ANDROID_NDK
|
||||
# ANDROID_STANDALONE_TOOLCHAIN
|
||||
# ANDROID_NDK : path to your NDK install
|
||||
# NDK_CCACHE : path to your ccache executable
|
||||
# ANDROID_TOOLCHAIN_NAME : the NDK name of compiler toolchain
|
||||
# ANDROID_NDK_HOST_X64 : try to use x86_64 toolchain (default for x64 host systems)
|
||||
# ANDROID_NDK_LAYOUT : the inner NDK structure (RELEASE, LINARO, ANDROID)
|
||||
# LIBRARY_OUTPUT_PATH_ROOT : <any valid path>
|
||||
# NDK_CCACHE : <path to your ccache executable>
|
||||
# Obsolete:
|
||||
# ANDROID_API_LEVEL : superseded by ANDROID_NATIVE_API_LEVEL
|
||||
# ARM_TARGET : superseded by ANDROID_ABI
|
||||
# ARM_TARGETS : superseded by ANDROID_ABI (can be set only)
|
||||
# ANDROID_NDK_TOOLCHAIN_ROOT : superseded by ANDROID_STANDALONE_TOOLCHAIN (can be set only)
|
||||
# ANDROID_USE_STLPORT : superseded by ANDROID_STL=stlport_static
|
||||
# ANDROID_LEVEL : superseded by ANDROID_NATIVE_API_LEVEL (completely removed)
|
||||
# ANDROID_STANDALONE_TOOLCHAIN
|
||||
#
|
||||
# Primary read-only variables:
|
||||
# ANDROID : always TRUE
|
||||
|
@ -1715,19 +1692,16 @@ endif()
|
|||
# X86_64 : TRUE if configured for x86_64
|
||||
# MIPS : TRUE if configured for mips
|
||||
# MIPS64 : TRUE if configured for mips64
|
||||
# BUILD_ANDROID : always TRUE
|
||||
# BUILD_WITH_ANDROID_NDK : TRUE if NDK is used
|
||||
# BUILD_WITH_STANDALONE_TOOLCHAIN : TRUE if standalone toolchain is used
|
||||
# ANDROID_NDK_HOST_SYSTEM_NAME : "windows", "linux-x86" or "darwin-x86" depending on host platform
|
||||
# ANDROID_NDK_ABI_NAME : "armeabi", "armeabi-v7a", "x86", "mips", "arm64-v8a", "x86_64", "mips64" depending on ANDROID_ABI
|
||||
# ANDROID_NDK_RELEASE : from r5 to r10c; set only for NDK
|
||||
# ANDROID_NDK_RELEASE : from r5 to r10d; set only for NDK
|
||||
# ANDROID_NDK_RELEASE_NUM : numeric ANDROID_NDK_RELEASE version (1000*major+minor)
|
||||
# ANDROID_ARCH_NAME : "arm", "x86", "mips", "arm64", "x86_64", "mips64" depending on ANDROID_ABI
|
||||
# ANDROID_SYSROOT : path to the compiler sysroot
|
||||
# TOOL_OS_SUFFIX : "" or ".exe" depending on host platform
|
||||
# ANDROID_COMPILER_IS_CLANG : TRUE if clang compiler is used
|
||||
# Obsolete:
|
||||
# ARMEABI_NDK_NAME : superseded by ANDROID_NDK_ABI_NAME
|
||||
#
|
||||
# Secondary (less stable) read-only variables:
|
||||
# ANDROID_COMPILER_VERSION : GCC version used (not Clang version)
|
||||
|
@ -1742,12 +1716,10 @@ endif()
|
|||
# ANDROID_RTTI : if rtti is enabled by the runtime
|
||||
# ANDROID_EXCEPTIONS : if exceptions are enabled by the runtime
|
||||
# ANDROID_GCC_TOOLCHAIN_NAME : read-only, differs from ANDROID_TOOLCHAIN_NAME only if clang is used
|
||||
# ANDROID_LIBM_PATH : path to libm.so (set to something like $(TOP)/out/target/product/<product_name>/obj/lib/libm.so) to workaround unresolved `sincos`
|
||||
#
|
||||
# Defaults:
|
||||
# ANDROID_DEFAULT_NDK_API_LEVEL
|
||||
# ANDROID_DEFAULT_NDK_API_LEVEL_${ARCH}
|
||||
# ANDROID_NDK_SEARCH_PATHS
|
||||
# ANDROID_STANDALONE_TOOLCHAIN_SEARCH_PATH
|
||||
# ANDROID_SUPPORTED_ABIS_${ARCH}
|
||||
# ANDROID_SUPPORTED_NDK_VERSIONS
|
||||
# ANDROID_SUPPORTED_NDK_VERSIONS
|
||||
|
|
9
cmake/externals/LibOVR/CMakeLists.txt
vendored
9
cmake/externals/LibOVR/CMakeLists.txt
vendored
|
@ -20,8 +20,8 @@ if (WIN32)
|
|||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.3.0_public.zip
|
||||
URL_MD5 a2dcf695e0f03a70fdd1ed7480585e82
|
||||
URL https://hifi-public.s3.amazonaws.com/dependencies/ovr_sdk_win_1.8.0_public.zip
|
||||
URL_MD5 bea17e04acc1dd8cf7cabefa1b28cc3c
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
@ -29,8 +29,8 @@ if (WIN32)
|
|||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(LIBOVR_DIR ${SOURCE_DIR}/OculusSDK/LibOVR)
|
||||
message("LIBOVR dir ${SOURCE_DIR}")
|
||||
set(LIBOVR_DIR ${SOURCE_DIR}/LibOVR)
|
||||
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
|
||||
set(LIBOVR_LIB_DIR ${LIBOVR_DIR}/Lib/Windows/x64/Release/VS2013 CACHE TYPE INTERNAL)
|
||||
else()
|
||||
|
@ -38,6 +38,7 @@ if (WIN32)
|
|||
endif()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${LIBOVR_DIR}/Include CACHE TYPE INTERNAL)
|
||||
message("LIBOVR include dir ${${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS}")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${LIBOVR_LIB_DIR}/LibOVR.lib CACHE TYPE INTERNAL)
|
||||
|
||||
elseif(APPLE)
|
||||
|
|
3
cmake/externals/glew/CMakeLists.txt
vendored
3
cmake/externals/glew/CMakeLists.txt
vendored
|
@ -15,7 +15,6 @@ ExternalProject_Add(
|
|||
LOG_BUILD 1
|
||||
)
|
||||
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
|
@ -32,4 +31,4 @@ elseif (WIN32)
|
|||
endif ()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glew_d.${LIB_EXT} CACHE FILEPATH "Path to glew debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glew.${LIB_EXT} CACHE FILEPATH "Path to glew release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glew.${LIB_EXT} CACHE FILEPATH "Path to glew release library")
|
||||
|
|
21
cmake/externals/gli/CMakeLists.txt
vendored
Normal file
21
cmake/externals/gli/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
set(EXTERNAL_NAME gli)
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://hifi-public.s3.amazonaws.com/dependencies/gli-0.8.1.0.zip
|
||||
URL_MD5 00c990f59c12bbf367956ef399d6f798
|
||||
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR} CACHE PATH "List of gli include directories")
|
4
cmake/externals/glm/CMakeLists.txt
vendored
4
cmake/externals/glm/CMakeLists.txt
vendored
|
@ -3,8 +3,8 @@ set(EXTERNAL_NAME glm)
|
|||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.5.4.zip
|
||||
URL_MD5 fab76fc982b256b46208e5c750ed456a
|
||||
URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.zip
|
||||
URL_MD5 579ac77a3110befa3244d68c0ceb7281
|
||||
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
|
||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR>
|
||||
LOG_DOWNLOAD 1
|
||||
|
|
74
cmake/externals/hifiAudioCodec/CMakeLists.txt
vendored
74
cmake/externals/hifiAudioCodec/CMakeLists.txt
vendored
|
@ -5,39 +5,43 @@ set(EXTERNAL_NAME hifiAudioCodec)
|
|||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (WIN32 OR APPLE)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-1.zip
|
||||
URL_MD5 23ec3fe51eaa155ea159a4971856fc13
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
elseif(NOT ANDROID)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux.zip
|
||||
URL_MD5 7d37914a18aa4de971d2f45dd3043bde
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
endif()
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL)
|
||||
elseif(APPLE)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
|
||||
elseif(NOT ANDROID)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
|
||||
if (NOT ANDROID)
|
||||
|
||||
if (WIN32 OR APPLE)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-1.zip
|
||||
URL_MD5 23ec3fe51eaa155ea159a4971856fc13
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
else ()
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://s3.amazonaws.com/hifi-public/dependencies/codecSDK-linux.zip
|
||||
URL_MD5 7d37914a18aa4de971d2f45dd3043bde
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
endif()
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE TYPE INTERNAL)
|
||||
|
||||
if (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/audio.lib CACHE TYPE INTERNAL)
|
||||
elseif(APPLE)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
|
||||
elseif(NOT ANDROID)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${SOURCE_DIR}/Release/libaudio.a CACHE TYPE INTERNAL)
|
||||
endif()
|
||||
|
||||
endif()
|
||||
|
|
21
cmake/externals/oglplus/CMakeLists.txt
vendored
21
cmake/externals/oglplus/CMakeLists.txt
vendored
|
@ -1,21 +0,0 @@
|
|||
set(EXTERNAL_NAME oglplus)
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/oglplus-0.63.0.zip
|
||||
URL_MD5 de984ab245b185b45c87415c0e052135
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include ${SOURCE_DIR}/implement CACHE TYPE INTERNAL)
|
||||
|
4
cmake/externals/openvr/CMakeLists.txt
vendored
4
cmake/externals/openvr/CMakeLists.txt
vendored
|
@ -7,8 +7,8 @@ string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
|||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://github.com/ValveSoftware/openvr/archive/v0.9.19.zip
|
||||
URL_MD5 843f9dde488584d8af1f3ecf2252b4e0
|
||||
URL https://github.com/ValveSoftware/openvr/archive/v1.0.3.zip
|
||||
URL_MD5 b484b12901917cc739e40389583c8b0d
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
|
|
55
cmake/externals/quazip/CMakeLists.txt
vendored
Normal file
55
cmake/externals/quazip/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
set(EXTERNAL_NAME quazip)
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
cmake_policy(SET CMP0046 OLD)
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
if (WIN32)
|
||||
# windows shell does not like backslashes expanded on the command line,
|
||||
# so convert all backslashes in the QT path to forward slashes
|
||||
string(REPLACE \\ / QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH})
|
||||
elseif ($ENV{QT_CMAKE_PREFIX_PATH})
|
||||
set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH})
|
||||
endif ()
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://s3-us-west-1.amazonaws.com/hifi-production/dependencies/quazip-0.7.2.zip
|
||||
URL_MD5 2955176048a31262c09259ca8d309d19
|
||||
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
|
||||
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_PREFIX_PATH=${QT_CMAKE_PREFIX_PATH} -DCMAKE_INSTALL_NAME_DIR:PATH=<INSTALL_DIR>/lib -DZLIB_ROOT=${ZLIB_ROOT} -DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
|
||||
add_dependencies(quazip zlib)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES
|
||||
FOLDER "hidden/externals"
|
||||
INSTALL_NAME_DIR ${INSTALL_DIR}/lib
|
||||
BUILD_WITH_INSTALL_RPATH True)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIR ${INSTALL_DIR}/include CACHE PATH "List of QuaZip include directories")
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${${EXTERNAL_NAME_UPPER}_INCLUDE_DIR} CACHE PATH "List of QuaZip include directories")
|
||||
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${INSTALL_DIR}/lib CACHE FILEPATH "Location of QuaZip DLL")
|
||||
|
||||
if (APPLE)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.1.0.0.dylib CACHE FILEPATH "Location of QuaZip release library")
|
||||
elseif (WIN32)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/quazip5.lib CACHE FILEPATH "Location of QuaZip release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/quazip5d.lib CACHE FILEPATH "Location of QuaZip release library")
|
||||
else ()
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libquazip5.so CACHE FILEPATH "Location of QuaZip release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libquazip5d.so CACHE FILEPATH "Location of QuaZip release library")
|
||||
endif ()
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(${EXTERNAL_NAME_UPPER})
|
||||
|
||||
# Force selected libraries into the cache
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE FILEPATH "Location of QuaZip libraries")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARIES ${${EXTERNAL_NAME_UPPER}_LIBRARIES} CACHE FILEPATH "Location of QuaZip libraries")
|
7
cmake/externals/sdl2/CMakeLists.txt
vendored
7
cmake/externals/sdl2/CMakeLists.txt
vendored
|
@ -46,7 +46,7 @@ else ()
|
|||
if (ANDROID)
|
||||
set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19")
|
||||
endif ()
|
||||
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://www.libsdl.org/release/SDL2-2.0.3.tar.gz
|
||||
|
@ -61,7 +61,6 @@ endif ()
|
|||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
|
||||
if (APPLE)
|
||||
|
||||
# NOOP
|
||||
|
@ -78,9 +77,9 @@ elseif (WIN32)
|
|||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_TEMP ${SOURCE_DIR}/lib/x86/SDL2.lib CACHE FILEPATH "Path to SDL2 library")
|
||||
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${SOURCE_DIR}/lib/x86 CACHE PATH "Location of SDL2 DLL")
|
||||
endif()
|
||||
|
||||
|
||||
add_paths_to_fixup_libs(${${EXTERNAL_NAME_UPPER}_DLL_PATH})
|
||||
|
||||
|
||||
else ()
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
|
28
cmake/externals/tbb/CMakeLists.txt
vendored
28
cmake/externals/tbb/CMakeLists.txt
vendored
|
@ -3,9 +3,9 @@ set(EXTERNAL_NAME tbb)
|
|||
include(ExternalProject)
|
||||
|
||||
if (ANDROID)
|
||||
|
||||
|
||||
find_program(NDK_BUILD_COMMAND NAMES ndk-build DOC "Path to the ndk-build command")
|
||||
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz
|
||||
|
@ -20,7 +20,7 @@ if (ANDROID)
|
|||
)
|
||||
elseif (APPLE)
|
||||
find_program(MAKE_COMMAND NAMES make DOC "Path to the make command")
|
||||
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/tbb43_20150316oss_src.tgz
|
||||
|
@ -37,11 +37,11 @@ else ()
|
|||
if (WIN32)
|
||||
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_win.zip)
|
||||
set(DOWNLOAD_MD5 d250d40bb93b255f75bcbb19e976a440)
|
||||
else ()
|
||||
else ()
|
||||
set(DOWNLOAD_URL http://s3.amazonaws.com/hifi-public/dependencies/tbb43_20150316oss_lin.tgz)
|
||||
set(DOWNLOAD_MD5 7830ba2bc62438325fba2ec0c95367a5)
|
||||
endif ()
|
||||
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL ${DOWNLOAD_URL}
|
||||
|
@ -60,11 +60,11 @@ ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
|||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (APPLE)
|
||||
if (APPLE)
|
||||
set(_TBB_LIB_DIR "${SOURCE_DIR}/lib")
|
||||
set(_LIB_PREFIX "lib")
|
||||
set(_LIB_EXT "dylib")
|
||||
|
||||
|
||||
ExternalProject_Add_Step(
|
||||
${EXTERNAL_NAME}
|
||||
change-install-name
|
||||
|
@ -74,7 +74,7 @@ if (APPLE)
|
|||
WORKING_DIRECTORY <SOURCE_DIR>
|
||||
LOG 1
|
||||
)
|
||||
|
||||
|
||||
elseif (WIN32)
|
||||
if ("${CMAKE_SIZEOF_VOID_P}" EQUAL "8")
|
||||
set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/intel64/vc12")
|
||||
|
@ -91,18 +91,18 @@ elseif (ANDROID)
|
|||
elseif (UNIX)
|
||||
set(_LIB_PREFIX "lib")
|
||||
set(_LIB_EXT "so")
|
||||
|
||||
|
||||
if(CMAKE_SIZEOF_VOID_P EQUAL 8)
|
||||
set(_TBB_ARCH_DIR "intel64")
|
||||
else()
|
||||
set(_TBB_ARCH_DIR "ia32")
|
||||
endif()
|
||||
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_C_COMPILER} -dumpversion
|
||||
OUTPUT_VARIABLE GCC_VERSION
|
||||
)
|
||||
|
||||
|
||||
if (GCC_VERSION VERSION_GREATER 4.4 OR GCC_VERSION VERSION_EQUAL 4.4)
|
||||
set(_TBB_LIB_DIR "${SOURCE_DIR}/lib/${_TBB_ARCH_DIR}/gcc4.4")
|
||||
elseif (GCC_VERSION VERSION_GREATER 4.1 OR GCC_VERSION VERSION_EQUAL 4.1)
|
||||
|
@ -110,9 +110,9 @@ elseif (UNIX)
|
|||
else ()
|
||||
message(STATUS "Could not find a compatible version of Threading Building Blocks library for your compiler.")
|
||||
endif ()
|
||||
|
||||
|
||||
endif ()
|
||||
|
||||
|
||||
endif ()
|
||||
|
||||
if (DEFINED _TBB_LIB_DIR)
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${_TBB_LIB_DIR}/${_LIB_PREFIX}tbb_debug.${_LIB_EXT} CACHE FILEPATH "TBB debug library location")
|
||||
|
|
24
cmake/externals/wasapi/CMakeLists.txt
vendored
Normal file
24
cmake/externals/wasapi/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,24 @@
|
|||
if (WIN32)
|
||||
|
||||
set(EXTERNAL_NAME wasapi)
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
include(ExternalProject)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi3.zip
|
||||
URL_MD5 1a2433f80a788a54c70f505ff4f43ac1
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_DLL_PATH ${SOURCE_DIR} CACHE FILEPATH "Location of wasapi DLL")
|
||||
|
||||
endif()
|
|
@ -1,23 +0,0 @@
|
|||
#
|
||||
# AutoMTC.cmake
|
||||
#
|
||||
# Created by Andrzej Kapolka on 12/31/13.
|
||||
# Copyright 2013 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
macro(AUTO_MTC)
|
||||
set(AUTOMTC_SRC ${TARGET_NAME}_automtc.cpp)
|
||||
|
||||
file(GLOB INCLUDE_FILES src/*.h)
|
||||
|
||||
if (NOT ANDROID)
|
||||
set(MTC_EXECUTABLE mtc)
|
||||
else ()
|
||||
set(MTC_EXECUTABLE $ENV{MTC_PATH}/mtc)
|
||||
endif ()
|
||||
|
||||
add_custom_command(OUTPUT ${AUTOMTC_SRC} COMMAND ${MTC_EXECUTABLE} -o ${AUTOMTC_SRC} ${INCLUDE_FILES} DEPENDS ${MTC_EXECUTABLE} ${INCLUDE_FILES})
|
||||
endmacro()
|
|
@ -1,92 +1,108 @@
|
|||
#
|
||||
#
|
||||
# AutoScribeShader.cmake
|
||||
#
|
||||
#
|
||||
# Created by Sam Gateau on 12/17/14.
|
||||
# Copyright 2014 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
#
|
||||
|
||||
function(AUTOSCRIBE_SHADER SHADER_FILE)
|
||||
|
||||
# Grab include files
|
||||
foreach(includeFile ${ARGN})
|
||||
list(APPEND SHADER_INCLUDE_FILES ${includeFile})
|
||||
endforeach()
|
||||
# Grab include files
|
||||
foreach(includeFile ${ARGN})
|
||||
list(APPEND SHADER_INCLUDE_FILES ${includeFile})
|
||||
endforeach()
|
||||
|
||||
foreach(SHADER_INCLUDE ${SHADER_INCLUDE_FILES})
|
||||
get_filename_component(INCLUDE_DIR ${SHADER_INCLUDE} PATH)
|
||||
list(APPEND SHADER_INCLUDES_PATHS ${INCLUDE_DIR})
|
||||
endforeach()
|
||||
foreach(SHADER_INCLUDE ${SHADER_INCLUDE_FILES})
|
||||
get_filename_component(INCLUDE_DIR ${SHADER_INCLUDE} PATH)
|
||||
list(APPEND SHADER_INCLUDES_PATHS ${INCLUDE_DIR})
|
||||
endforeach()
|
||||
|
||||
#Extract the unique include shader paths
|
||||
set(INCLUDES ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES})
|
||||
#message(${TARGET_NAME} Hifi for includes ${INCLUDES})
|
||||
foreach(EXTRA_SHADER_INCLUDE ${INCLUDES})
|
||||
list(APPEND SHADER_INCLUDES_PATHS ${EXTRA_SHADER_INCLUDE})
|
||||
endforeach()
|
||||
|
||||
#Extract the unique include shader paths
|
||||
set(INCLUDES ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES})
|
||||
#message(Hifi for includes ${INCLUDES})
|
||||
foreach(EXTRA_SHADER_INCLUDE ${INCLUDES})
|
||||
list(APPEND SHADER_INCLUDES_PATHS ${EXTRA_SHADER_INCLUDE})
|
||||
endforeach()
|
||||
list(REMOVE_DUPLICATES SHADER_INCLUDES_PATHS)
|
||||
#message(ready for includes ${SHADER_INCLUDES_PATHS})
|
||||
|
||||
list(REMOVE_DUPLICATES SHADER_INCLUDES_PATHS)
|
||||
#message(ready for includes ${SHADER_INCLUDES_PATHS})
|
||||
# make the scribe include arguments
|
||||
set(SCRIBE_INCLUDES)
|
||||
foreach(INCLUDE_PATH ${SHADER_INCLUDES_PATHS})
|
||||
set(SCRIBE_INCLUDES ${SCRIBE_INCLUDES} -I ${INCLUDE_PATH}/)
|
||||
endforeach()
|
||||
|
||||
# make the scribe include arguments
|
||||
set(SCRIBE_INCLUDES)
|
||||
foreach(INCLUDE_PATH ${SHADER_INCLUDES_PATHS})
|
||||
set(SCRIBE_INCLUDES ${SCRIBE_INCLUDES} -I ${INCLUDE_PATH}/)
|
||||
endforeach()
|
||||
# Define the final name of the generated shader file
|
||||
get_filename_component(SHADER_TARGET ${SHADER_FILE} NAME_WE)
|
||||
get_filename_component(SHADER_EXT ${SHADER_FILE} EXT)
|
||||
if(SHADER_EXT STREQUAL .slv)
|
||||
set(SHADER_TARGET ${SHADER_TARGET}_vert.h)
|
||||
elseif(${SHADER_EXT} STREQUAL .slf)
|
||||
set(SHADER_TARGET ${SHADER_TARGET}_frag.h)
|
||||
elseif(${SHADER_EXT} STREQUAL .slg)
|
||||
set(SHADER_TARGET ${SHADER_TARGET}_geom.h)
|
||||
endif()
|
||||
|
||||
# Define the final name of the generated shader file
|
||||
get_filename_component(SHADER_TARGET ${SHADER_FILE} NAME_WE)
|
||||
get_filename_component(SHADER_EXT ${SHADER_FILE} EXT)
|
||||
if(SHADER_EXT STREQUAL .slv)
|
||||
set(SHADER_TARGET ${SHADER_TARGET}_vert.h)
|
||||
elseif(${SHADER_EXT} STREQUAL .slf)
|
||||
set(SHADER_TARGET ${SHADER_TARGET}_frag.h)
|
||||
elseif(${SHADER_EXT} STREQUAL .slg)
|
||||
set(SHADER_TARGET ${SHADER_TARGET}_geom.h)
|
||||
endif()
|
||||
set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_TARGET}")
|
||||
|
||||
set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_TARGET}")
|
||||
# Target dependant Custom rule on the SHADER_FILE
|
||||
if (APPLE)
|
||||
set(GLPROFILE MAC_GL)
|
||||
set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
|
||||
# Target dependant Custom rule on the SHADER_FILE
|
||||
if (APPLE)
|
||||
set(GLPROFILE MAC_GL)
|
||||
set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE})
|
||||
elseif (ANDROID)
|
||||
set(GLPROFILE LINUX_GL)
|
||||
set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
|
||||
add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE})
|
||||
elseif (UNIX)
|
||||
set(GLPROFILE LINUX_GL)
|
||||
set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
# for an android build, we can't use the scribe that cmake would normally produce as a target,
|
||||
# since it's unrunnable by the cross-compiling build machine
|
||||
|
||||
add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE})
|
||||
else ()
|
||||
set(GLPROFILE PC_GL)
|
||||
set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
# so, we require the compiling user to point us at a compiled executable version for their native toolchain
|
||||
find_program(NATIVE_SCRIBE scribe PATHS ${SCRIBE_PATH} ENV SCRIBE_PATH)
|
||||
|
||||
add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE})
|
||||
endif()
|
||||
if (NOT NATIVE_SCRIBE)
|
||||
message(FATAL_ERROR "The High Fidelity scribe tool is required for shader pre-processing. \
|
||||
Please compile scribe using your native toolchain and set SCRIBE_PATH to the path containing the scribe executable in your ENV.\
|
||||
")
|
||||
endif ()
|
||||
|
||||
#output the generated file name
|
||||
set(AUTOSCRIBE_SHADER_RETURN ${SHADER_TARGET} PARENT_SCOPE)
|
||||
add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND ${NATIVE_SCRIBE} ${SCRIBE_ARGS} DEPENDS ${SHADER_INCLUDE_FILES} ${SHADER_FILE})
|
||||
elseif (UNIX)
|
||||
set(GLPROFILE LINUX_GL)
|
||||
set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
|
||||
file(GLOB INCLUDE_FILES ${SHADER_TARGET})
|
||||
add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE})
|
||||
else ()
|
||||
set(GLPROFILE PC_GL)
|
||||
set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
|
||||
add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE})
|
||||
endif()
|
||||
|
||||
#output the generated file name
|
||||
set(AUTOSCRIBE_SHADER_RETURN ${SHADER_TARGET} PARENT_SCOPE)
|
||||
|
||||
file(GLOB INCLUDE_FILES ${SHADER_TARGET})
|
||||
|
||||
endfunction()
|
||||
|
||||
|
||||
macro(AUTOSCRIBE_SHADER_LIB)
|
||||
set(HIFI_LIBRARIES_SHADER_INCLUDE_FILES "")
|
||||
file(RELATIVE_PATH RELATIVE_LIBRARY_DIR_PATH ${CMAKE_CURRENT_SOURCE_DIR} "${HIFI_LIBRARY_DIR}")
|
||||
foreach(HIFI_LIBRARY ${ARGN})
|
||||
foreach(HIFI_LIBRARY ${ARGN})
|
||||
#if (NOT TARGET ${HIFI_LIBRARY})
|
||||
# file(GLOB_RECURSE HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${RELATIVE_LIBRARY_DIR_PATH}/${HIFI_LIBRARY}/src/)
|
||||
#endif ()
|
||||
|
||||
|
||||
#file(GLOB_RECURSE HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src/*.slh)
|
||||
list(APPEND HIFI_LIBRARIES_SHADER_INCLUDE_FILES ${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src)
|
||||
endforeach()
|
||||
#message(${HIFI_LIBRARIES_SHADER_INCLUDE_FILES})
|
||||
#message("${TARGET_NAME} ${HIFI_LIBRARIES_SHADER_INCLUDE_FILES}")
|
||||
|
||||
file(GLOB_RECURSE SHADER_INCLUDE_FILES src/*.slh)
|
||||
file(GLOB_RECURSE SHADER_SOURCE_FILES src/*.slv src/*.slf src/*.slg)
|
||||
|
@ -95,13 +111,14 @@ macro(AUTOSCRIBE_SHADER_LIB)
|
|||
set(SHADERS_DIR "${CMAKE_CURRENT_BINARY_DIR}/shaders/${TARGET_NAME}")
|
||||
file(MAKE_DIRECTORY ${SHADERS_DIR})
|
||||
|
||||
#message(${SHADER_INCLUDE_FILES})
|
||||
#message("${TARGET_NAME} ${SHADER_INCLUDE_FILES}")
|
||||
set(AUTOSCRIBE_SHADER_SRC "")
|
||||
foreach(SHADER_FILE ${SHADER_SOURCE_FILES})
|
||||
AUTOSCRIBE_SHADER(${SHADER_FILE} ${SHADER_INCLUDE_FILES})
|
||||
file(TO_CMAKE_PATH "${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE)
|
||||
list(APPEND AUTOSCRIBE_SHADER_SRC ${AUTOSCRIBE_GENERATED_FILE})
|
||||
AUTOSCRIBE_SHADER(${SHADER_FILE} ${SHADER_INCLUDE_FILES})
|
||||
file(TO_CMAKE_PATH "${AUTOSCRIBE_SHADER_RETURN}" AUTOSCRIBE_GENERATED_FILE)
|
||||
list(APPEND AUTOSCRIBE_SHADER_SRC ${AUTOSCRIBE_GENERATED_FILE})
|
||||
endforeach()
|
||||
#message(${AUTOSCRIBE_SHADER_SRC})
|
||||
#message(${TARGET_NAME} ${AUTOSCRIBE_SHADER_SRC})
|
||||
|
||||
if (WIN32)
|
||||
source_group("Shaders" FILES ${SHADER_INCLUDE_FILES})
|
||||
|
|
|
@ -16,26 +16,6 @@ macro(fixup_interface)
|
|||
string(REPLACE " " "\\ " ESCAPED_INSTALL_PATH ${INTERFACE_INSTALL_DIR})
|
||||
set(_INTERFACE_INSTALL_PATH "${ESCAPED_INSTALL_PATH}/${ESCAPED_BUNDLE_NAME}.app")
|
||||
|
||||
# install QtWebProcess from Qt to the application bundle
|
||||
# since it is missed by macdeployqt
|
||||
# https://bugreports.qt.io/browse/QTBUG-35211
|
||||
set(LIBEXEC_PATH "${_INTERFACE_INSTALL_PATH}/Contents/libexec")
|
||||
install(
|
||||
PROGRAMS "${QT_DIR}/libexec/QtWebProcess"
|
||||
DESTINATION ${LIBEXEC_PATH}
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
|
||||
set(QTWEBPROCESS_PATH "\${CMAKE_INSTALL_PREFIX}/${LIBEXEC_PATH}")
|
||||
|
||||
# we also need a qt.conf in the directory of QtWebProcess
|
||||
install(CODE "
|
||||
file(WRITE ${QTWEBPROCESS_PATH}/qt.conf
|
||||
\"[Paths]\nPlugins = ../PlugIns\nImports = ../Resources/qml\nQml2Imports = ../Resources/qml\"
|
||||
)"
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
|
||||
find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS "${QT_DIR}/bin" NO_DEFAULT_PATH)
|
||||
|
||||
if (NOT MACDEPLOYQT_COMMAND AND (PRODUCTION_BUILD OR PR_BUILD))
|
||||
|
@ -49,7 +29,6 @@ macro(fixup_interface)
|
|||
execute_process(COMMAND ${MACDEPLOYQT_COMMAND}\
|
||||
\${CMAKE_INSTALL_PREFIX}/${_INTERFACE_INSTALL_PATH}/\
|
||||
-verbose=2 -qmldir=${CMAKE_SOURCE_DIR}/interface/resources/qml/\
|
||||
-executable=\${CMAKE_INSTALL_PREFIX}/${_INTERFACE_INSTALL_PATH}/Contents/libexec/QtWebProcess\
|
||||
)"
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
|
|
|
@ -59,7 +59,12 @@ macro(install_beside_console)
|
|||
set(EXECUTABLE_NEEDING_FIXUP "\${CMAKE_INSTALL_PREFIX}/${COMPONENT_INSTALL_DIR}/${TARGET_NAME}")
|
||||
string(REPLACE " " "\\ " ESCAPED_EXECUTABLE_NAME ${EXECUTABLE_NEEDING_FIXUP})
|
||||
|
||||
# configure Info.plist for COMPONENT_APP
|
||||
install(CODE "
|
||||
set(MACOSX_BUNDLE_EXECUTABLE_NAME domain-server)
|
||||
set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.server-components)
|
||||
set(MACOSX_BUNDLE_BUNDLE_NAME Sandbox\\ Components)
|
||||
configure_file(${HF_CMAKE_DIR}/templates/MacOSXBundleSandboxComponentsInfo.plist.in ${ESCAPED_BUNDLE_NAME}/Contents/Info.plist)
|
||||
execute_process(COMMAND ${MACDEPLOYQT_COMMAND} ${ESCAPED_BUNDLE_NAME} -verbose=2 -executable=${ESCAPED_EXECUTABLE_NAME})"
|
||||
COMPONENT ${SERVER_COMPONENT}
|
||||
)
|
||||
|
|
|
@ -14,7 +14,7 @@ endif ()
|
|||
|
||||
if (HIFI_MEMORY_DEBUGGING)
|
||||
if (UNIX)
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
||||
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -U_FORTIFY_SOURCE -fno-stack-protector -fno-omit-frame-pointer")
|
||||
SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static-libasan -static-libstdc++ -fsanitize=address")
|
||||
endif (UNIX)
|
||||
endif ()
|
||||
|
|
|
@ -41,5 +41,28 @@ macro(PACKAGE_LIBRARIES_FOR_DEPLOYMENT)
|
|||
POST_BUILD
|
||||
COMMAND CMD /C "SET PATH=%PATH%;${QT_DIR}/bin && ${WINDEPLOYQT_COMMAND} ${EXTRA_DEPLOY_OPTIONS} $<$<OR:$<CONFIG:Release>,$<CONFIG:MinSizeRel>,$<CONFIG:RelWithDebInfo>>:--release> $<TARGET_FILE:${TARGET_NAME}>"
|
||||
)
|
||||
|
||||
set(QTAUDIO_PATH $<TARGET_FILE_DIR:${TARGET_NAME}>/audio)
|
||||
|
||||
if (DEPLOY_PACKAGE)
|
||||
# copy qtaudio_wasapi.dll alongside qtaudio_windows.dll, and let the installer resolve
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.pdb ${QTAUDIO_PATH} )
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.pdb ${QTAUDIO_PATH} )
|
||||
)
|
||||
elseif (${CMAKE_SYSTEM_VERSION} VERSION_LESS 6.2)
|
||||
# continue using qtaudio_windows.dll on Windows 7
|
||||
else ()
|
||||
# replace qtaudio_windows.dll with qtaudio_wasapi.dll on Windows 8/8.1/10
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windows.dll ( ${CMAKE_COMMAND} -E remove ${QTAUDIO_PATH}/qtaudio_windows.dll && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapi.pdb ${QTAUDIO_PATH} )
|
||||
COMMAND if exist ${QTAUDIO_PATH}/qtaudio_windowsd.dll ( ${CMAKE_COMMAND} -E remove ${QTAUDIO_PATH}/qtaudio_windowsd.dll && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.dll ${QTAUDIO_PATH} && ${CMAKE_COMMAND} -E copy ${WASAPI_DLL_PATH}/qtaudio_wasapid.pdb ${QTAUDIO_PATH} )
|
||||
)
|
||||
endif ()
|
||||
|
||||
endif ()
|
||||
endmacro()
|
||||
|
|
|
@ -18,6 +18,13 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
|
||||
set(RELEASE_TYPE $ENV{RELEASE_TYPE})
|
||||
set(RELEASE_NUMBER $ENV{RELEASE_NUMBER})
|
||||
string(TOLOWER "$ENV{BRANCH}" BUILD_BRANCH)
|
||||
set(BUILD_GLOBAL_SERVICES "DEVELOPMENT")
|
||||
set(USE_STABLE_GLOBAL_SERVICES 0)
|
||||
|
||||
message(STATUS "The BUILD_BRANCH variable is: ${BUILD_BRANCH}")
|
||||
message(STATUS "The BRANCH environment variable is: $ENV{BRANCH}")
|
||||
message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}")
|
||||
|
||||
if (RELEASE_TYPE STREQUAL "PRODUCTION")
|
||||
set(DEPLOY_PACKAGE TRUE)
|
||||
|
@ -31,6 +38,14 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
# add definition for this release type
|
||||
add_definitions(-DPRODUCTION_BUILD)
|
||||
|
||||
# if the build is a PRODUCTION_BUILD from the "stable" branch
|
||||
# then use the STABLE gobal services
|
||||
if (BUILD_BRANCH STREQUAL "stable")
|
||||
message(STATUS "The RELEASE_TYPE is PRODUCTION and the BUILD_BRANCH is stable...")
|
||||
set(BUILD_GLOBAL_SERVICES "STABLE")
|
||||
set(USE_STABLE_GLOBAL_SERVICES 1)
|
||||
endif()
|
||||
|
||||
elseif (RELEASE_TYPE STREQUAL "PR")
|
||||
set(DEPLOY_PACKAGE TRUE)
|
||||
set(PR_BUILD 1)
|
||||
|
@ -132,6 +147,10 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
set(CLIENT_COMPONENT client)
|
||||
set(SERVER_COMPONENT server)
|
||||
|
||||
# print out some results for testing this new build feature
|
||||
message(STATUS "The BUILD_GLOBAL_SERVICES variable is: ${BUILD_GLOBAL_SERVICES}")
|
||||
message(STATUS "The USE_STABLE_GLOBAL_SERVICES variable is: ${USE_STABLE_GLOBAL_SERVICES}")
|
||||
|
||||
# create a header file our targets can use to find out the application version
|
||||
file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/includes")
|
||||
configure_file("${HF_CMAKE_DIR}/templates/BuildInfo.h.in" "${CMAKE_BINARY_DIR}/includes/BuildInfo.h")
|
||||
|
|
|
@ -1,16 +1,16 @@
|
|||
#
|
||||
#
|
||||
# SetupHifiLibrary.cmake
|
||||
#
|
||||
#
|
||||
# Copyright 2013 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
#
|
||||
|
||||
macro(SETUP_HIFI_LIBRARY)
|
||||
|
||||
|
||||
project(${TARGET_NAME})
|
||||
|
||||
|
||||
# grab the implementation and header files
|
||||
file(GLOB_RECURSE LIB_SRCS "src/*.h" "src/*.cpp" "src/*.c")
|
||||
list(APPEND ${TARGET_NAME}_SRCS ${LIB_SRCS})
|
||||
|
@ -34,19 +34,19 @@ macro(SETUP_HIFI_LIBRARY)
|
|||
set_source_files_properties(${SRC} PROPERTIES COMPILE_FLAGS "-mavx2 -mfma")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
|
||||
setup_memory_debugger()
|
||||
|
||||
# create a library and set the property so it can be referenced later
|
||||
if (${${TARGET_NAME}_SHARED})
|
||||
add_library(${TARGET_NAME} SHARED ${LIB_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC} ${QT_RESOURCES_FILE})
|
||||
add_library(${TARGET_NAME} SHARED ${LIB_SRCS} ${AUTOSCRIBE_SHADER_LIB_SRC} ${QT_RESOURCES_FILE})
|
||||
else ()
|
||||
add_library(${TARGET_NAME} ${LIB_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC} ${QT_RESOURCES_FILE})
|
||||
add_library(${TARGET_NAME} ${LIB_SRCS} ${AUTOSCRIBE_SHADER_LIB_SRC} ${QT_RESOURCES_FILE})
|
||||
endif ()
|
||||
|
||||
|
||||
set(${TARGET_NAME}_DEPENDENCY_QT_MODULES ${ARGN})
|
||||
list(APPEND ${TARGET_NAME}_DEPENDENCY_QT_MODULES Core)
|
||||
|
||||
|
||||
# find these Qt modules and link them to our own target
|
||||
find_package(Qt5 COMPONENTS ${${TARGET_NAME}_DEPENDENCY_QT_MODULES} REQUIRED)
|
||||
|
||||
|
@ -54,11 +54,12 @@ macro(SETUP_HIFI_LIBRARY)
|
|||
target_link_libraries(${TARGET_NAME} Qt5::${QT_MODULE})
|
||||
endforeach()
|
||||
|
||||
# Don't make scribed shaders cumulative
|
||||
# Don't make scribed shaders or QT resource files cumulative
|
||||
set(AUTOSCRIBE_SHADER_LIB_SRC "")
|
||||
set(QT_RESOURCES_FILE "")
|
||||
|
||||
target_glm()
|
||||
|
||||
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES FOLDER "Libraries")
|
||||
|
||||
endmacro(SETUP_HIFI_LIBRARY)
|
||||
|
||||
endmacro(SETUP_HIFI_LIBRARY)
|
||||
|
|
|
@ -17,6 +17,12 @@ macro(SETUP_HIFI_PLUGIN)
|
|||
set(PLUGIN_PATH "plugins")
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
# produce PDB files for plugins as well
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zi")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} /DEBUG")
|
||||
endif()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME MATCHES "Linux" OR CMAKE_GENERATOR STREQUAL "Unix Makefiles")
|
||||
set(PLUGIN_FULL_PATH "${CMAKE_BINARY_DIR}/interface/${PLUGIN_PATH}/")
|
||||
else()
|
||||
|
|
|
@ -22,7 +22,7 @@ macro(SETUP_HIFI_PROJECT)
|
|||
endif ()
|
||||
endforeach()
|
||||
|
||||
add_executable(${TARGET_NAME} ${TARGET_SRCS} ${AUTOMTC_SRC} ${AUTOSCRIBE_SHADER_LIB_SRC})
|
||||
add_executable(${TARGET_NAME} ${TARGET_SRCS} ${AUTOSCRIBE_SHADER_LIB_SRC})
|
||||
|
||||
# include the generated application version header
|
||||
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes")
|
||||
|
|
12
cmake/macros/TargetGli.cmake
Normal file
12
cmake/macros/TargetGli.cmake
Normal file
|
@ -0,0 +1,12 @@
|
|||
#
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
# Created by Bradley Austin Davis on 2015/10/10
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_GLI)
|
||||
add_dependency_external_projects(gli)
|
||||
find_package(GLI REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLI_INCLUDE_DIRS})
|
||||
endmacro()
|
|
@ -1,21 +0,0 @@
|
|||
#
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
# Created by Bradley Austin Davis on 2015/10/10
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_OGLPLUS)
|
||||
# our OGL plus setup requires glew
|
||||
target_glew()
|
||||
|
||||
# our OGL plus setup requires boostconfig
|
||||
add_dependency_external_projects(boostconfig)
|
||||
find_package(BoostConfig REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${BOOSTCONFIG_INCLUDE_DIRS})
|
||||
|
||||
|
||||
add_dependency_external_projects(oglplus)
|
||||
find_package(OGLPLUS REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${OGLPLUS_INCLUDE_DIRS})
|
||||
endmacro()
|
|
@ -6,6 +6,7 @@
|
|||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_OPENGL)
|
||||
add_definitions(-DGLEW_STATIC)
|
||||
if (APPLE)
|
||||
# link in required OS X frameworks and include the right GL headers
|
||||
find_library(OpenGL OpenGL)
|
||||
|
|
16
cmake/macros/TargetQuazip.cmake
Normal file
16
cmake/macros/TargetQuazip.cmake
Normal file
|
@ -0,0 +1,16 @@
|
|||
#
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
# Created by Leonardo Murillo on 2015/11/20
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_QUAZIP)
|
||||
add_dependency_external_projects(quazip)
|
||||
find_package(QuaZip REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${QUAZIP_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${QUAZIP_LIBRARIES})
|
||||
if (WIN32)
|
||||
add_paths_to_fixup_libs(${QUAZIP_DLL_PATH})
|
||||
endif ()
|
||||
endmacro()
|
26
cmake/modules/FindGLI.cmake
Normal file
26
cmake/modules/FindGLI.cmake
Normal file
|
@ -0,0 +1,26 @@
|
|||
#
|
||||
# FindGLI.cmake
|
||||
#
|
||||
# Try to find GLI include path.
|
||||
# Once done this will define
|
||||
#
|
||||
# GLI_INCLUDE_DIRS
|
||||
#
|
||||
# Created on 2016/09/03 by Bradley Austin Davis
|
||||
# Copyright 2013-2016 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
# setup hints for GLI search
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("gli")
|
||||
|
||||
# locate header
|
||||
find_path(GLI_INCLUDE_DIRS "gli/gli.hpp" HINTS ${GLI_SEARCH_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(GLI DEFAULT_MSG GLI_INCLUDE_DIRS)
|
||||
|
||||
mark_as_advanced(GLI_INCLUDE_DIRS GLI_SEARCH_DIRS)
|
|
@ -1,24 +0,0 @@
|
|||
#
|
||||
# Try to find OGLPLUS include path.
|
||||
# Once done this will define
|
||||
#
|
||||
# OGLPLUS_INCLUDE_DIRS
|
||||
#
|
||||
# Created by Bradley Austin Davis on 2015/05/22
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
# setup hints for OGLPLUS search
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("oglplus")
|
||||
|
||||
# locate header
|
||||
find_path(OGLPLUS_INCLUDE_DIRS "oglplus/fwd.hpp" HINTS ${OGLPLUS_SEARCH_DIRS})
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(OGLPLUS DEFAULT_MSG OGLPLUS_INCLUDE_DIRS)
|
||||
|
||||
mark_as_advanced(OGLPLUS_INCLUDE_DIRS OGLPLUS_SEARCH_DIRS)
|
29
cmake/modules/FindQuaZip.cmake
Normal file
29
cmake/modules/FindQuaZip.cmake
Normal file
|
@ -0,0 +1,29 @@
|
|||
#
|
||||
# FindQuaZip.h
|
||||
# StackManagerQt/cmake/modules
|
||||
#
|
||||
# Created by Mohammed Nafees.
|
||||
# Copyright (c) 2014 High Fidelity. All rights reserved.
|
||||
#
|
||||
|
||||
# QUAZIP_FOUND - QuaZip library was found
|
||||
# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir
|
||||
# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR)
|
||||
# QUAZIP_LIBRARIES - List of QuaZip libraries
|
||||
# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
|
||||
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("quazip")
|
||||
|
||||
if (WIN32)
|
||||
find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES include/quazip HINTS ${QUAZIP_SEARCH_DIRS})
|
||||
elseif (APPLE)
|
||||
find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES include/quazip HINTS ${QUAZIP_SEARCH_DIRS})
|
||||
else ()
|
||||
find_path(QUAZIP_INCLUDE_DIRS quazip.h PATH_SUFFIXES quazip HINTS ${QUAZIP_SEARCH_DIRS})
|
||||
endif ()
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_INCLUDE_DIRS)
|
||||
|
||||
mark_as_advanced(QUAZIP_INCLUDE_DIRS QUAZIP_SEARCH_DIRS)
|
|
@ -9,7 +9,12 @@
|
|||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
#define USE_STABLE_GLOBAL_SERVICES @USE_STABLE_GLOBAL_SERVICES@
|
||||
|
||||
namespace BuildInfo {
|
||||
const QString MODIFIED_ORGANIZATION = "@BUILD_ORGANIZATION@";
|
||||
const QString VERSION = "@BUILD_VERSION@";
|
||||
const QString BUILD_BRANCH = "@BUILD_BRANCH@";
|
||||
const QString BUILD_GLOBAL_SERVICES = "@BUILD_GLOBAL_SERVICES@";
|
||||
}
|
||||
|
||||
|
|
36
cmake/templates/MacOSXBundleSandboxComponentsInfo.plist.in
Normal file
36
cmake/templates/MacOSXBundleSandboxComponentsInfo.plist.in
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>
|
||||
<key>CFBundleGetInfoString</key>
|
||||
<string>${MACOSX_BUNDLE_INFO_STRING}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleLongVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
|
||||
<key>CSResourcesFileMapped</key>
|
||||
<true/>
|
||||
<key>LSRequiresCarbon</key>
|
||||
<true/>
|
||||
<key>NSHumanReadableCopyright</key>
|
||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||
</dict>
|
||||
</plist>
|
|
@ -23,6 +23,11 @@
|
|||
;Default installation folder
|
||||
InstallDir "@CPACK_NSIS_INSTALL_ROOT@\@CPACK_PACKAGE_INSTALL_DIRECTORY@"
|
||||
|
||||
;--------------------------------
|
||||
;Include WinVer to get Windows version
|
||||
|
||||
!include "WinVer.nsh"
|
||||
|
||||
;--------------------------------
|
||||
;General
|
||||
; leverage the UAC NSIS plugin to promote uninstaller to elevated privileges
|
||||
|
@ -130,6 +135,11 @@ Var AR_RegFlags
|
|||
SectionSetFlags ${${SecName}} $AR_SecFlags
|
||||
|
||||
"default_${SecName}:"
|
||||
; The client is always selected by default
|
||||
${If} ${SecName} == @CLIENT_COMPONENT_NAME@
|
||||
SectionSetFlags ${${SecName}} 17
|
||||
${EndIf}
|
||||
|
||||
!insertmacro LoadSectionSelectedIntoVar ${SecName} ${SecName}_selected
|
||||
!macroend
|
||||
|
||||
|
@ -243,6 +253,12 @@ FunctionEnd
|
|||
|
||||
;--------------------------------
|
||||
; Installation types
|
||||
|
||||
Section "-Previous Install Cleanup"
|
||||
; Remove the resources folder so we don't end up including removed QML files
|
||||
RMDir /r "$INSTDIR\resources"
|
||||
SectionEnd
|
||||
|
||||
@CPACK_NSIS_INSTALLATION_TYPES@
|
||||
|
||||
;--------------------------------
|
||||
|
@ -555,7 +571,9 @@ Function HandlePostInstallOptions
|
|||
; both launches use the explorer trick in case the user has elevated permissions for the installer
|
||||
; it won't be possible to use this approach if either application should be launched with a command line param
|
||||
${If} ${SectionIsSelected} ${@SERVER_COMPONENT_NAME@}
|
||||
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@"'
|
||||
; create shortcut with ARGUMENTS
|
||||
CreateShortCut "$TEMP\SandboxShortcut.lnk" "$INSTDIR\@CONSOLE_INSTALL_SUBDIR@\@CONSOLE_WIN_EXEC_NAME@" "-- --launchInterface"
|
||||
Exec '"$WINDIR\explorer.exe" "$TEMP\SandboxShortcut.lnk"'
|
||||
${Else}
|
||||
Exec '"$WINDIR\explorer.exe" "$INSTDIR\@INTERFACE_WIN_EXEC_NAME@"'
|
||||
${EndIf}
|
||||
|
@ -589,6 +607,17 @@ Section "-Core installation"
|
|||
Delete "$INSTDIR\version"
|
||||
Delete "$INSTDIR\xinput1_3.dll"
|
||||
|
||||
; The installer includes two different Qt audio plugins.
|
||||
; On Windows 8 and above, only qtaudio_wasapi.dll should be installed.
|
||||
; On Windows 7 and below, only qtaudio_windows.dll should be installed.
|
||||
${If} ${AtLeastWin8}
|
||||
Delete "$INSTDIR\audio\qtaudio_windows.dll"
|
||||
Delete "$INSTDIR\audio\qtaudio_windows.pdb"
|
||||
${Else}
|
||||
Delete "$INSTDIR\audio\qtaudio_wasapi.dll"
|
||||
Delete "$INSTDIR\audio\qtaudio_wasapi.pdb"
|
||||
${EndIf}
|
||||
|
||||
; Delete old desktop shortcuts before they were renamed during Sandbox rename
|
||||
Delete "$DESKTOP\@PRE_SANDBOX_INTERFACE_SHORTCUT_NAME@.lnk"
|
||||
Delete "$DESKTOP\@PRE_SANDBOX_CONSOLE_SHORTCUT_NAME@.lnk"
|
||||
|
|
22
cmake/templates/VersionInfo.rc.in
Normal file
22
cmake/templates/VersionInfo.rc.in
Normal file
|
@ -0,0 +1,22 @@
|
|||
// Language and character set information as described at
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa381049(v=vs.85).aspx
|
||||
#define US_ENGLISH_UNICODE "040904B0"
|
||||
|
||||
// More information about the format of this file can be found at
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa381058(v=vs.85).aspx
|
||||
1 VERSIONINFO
|
||||
BEGIN
|
||||
BLOCK "StringFileInfo"
|
||||
BEGIN
|
||||
BLOCK US_ENGLISH_UNICODE
|
||||
BEGIN
|
||||
VALUE "CompanyName", "@BUILD_ORGANIZATION@"
|
||||
VALUE "FileDescription", "@APP_FULL_NAME@"
|
||||
VALUE "FileVersion", "@BUILD_VERSION@"
|
||||
VALUE "InternalName", "@TARGET_NAME@"
|
||||
VALUE "OriginalFilename", "@TARGET_NAME@.exe"
|
||||
VALUE "ProductName", "@APP_FULL_NAME@"
|
||||
VALUE "ProductVersion", "@BUILD_VERSION@"
|
||||
END
|
||||
END
|
||||
END
|
|
@ -9,6 +9,11 @@ endif ()
|
|||
# setup the project and link required Qt modules
|
||||
setup_hifi_project(Network)
|
||||
|
||||
# Fix up the rpath so macdeployqt works
|
||||
if (APPLE)
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks")
|
||||
endif ()
|
||||
|
||||
# TODO: find a solution that will handle web file changes in resources on windows without a re-build.
|
||||
# Currently the resources are only copied on post-build. If one is changed but the domain-server is not, they will
|
||||
# not be re-copied. This is worked-around on OS X/UNIX by using a symlink.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": 1.5,
|
||||
"version": 1.7,
|
||||
"settings": [
|
||||
{
|
||||
"name": "metaverse",
|
||||
|
@ -380,22 +380,30 @@
|
|||
"default": "0",
|
||||
"advanced": false
|
||||
},
|
||||
{
|
||||
"name": "maximum_user_capacity_redirect_location",
|
||||
"label": "Redirect to Location on Maximum Capacity",
|
||||
"help": "Is there another domain, you'd like to redirect clients to when the maximum number of avatars are connected.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": false
|
||||
},
|
||||
{
|
||||
"name": "standard_permissions",
|
||||
"type": "table",
|
||||
"label": "Domain-Wide User Permissions",
|
||||
"help": "Indicate which users or groups can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"help": "Indicate which types of users can have which <a data-toggle='tooltip' data-html=true title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>domain-wide permissions</a>.",
|
||||
"caption": "Standard Permissions",
|
||||
"can_add_new_rows": false,
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "User / Group",
|
||||
"label": "Type of User",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"span": 6
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -409,7 +417,7 @@
|
|||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": true
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
|
@ -445,6 +453,13 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
],
|
||||
|
||||
|
@ -452,33 +467,62 @@
|
|||
"non-deletable-row-values": ["localhost", "anonymous", "logged-in"]
|
||||
},
|
||||
{
|
||||
"name": "permissions",
|
||||
"name": "group_permissions",
|
||||
"type": "table",
|
||||
"caption": "Permissions for Specific Users",
|
||||
"can_add_new_rows": true,
|
||||
"caption": "Permissions for Users in Groups",
|
||||
"categorize_by_key": "permissions_id",
|
||||
"can_add_new_categories": true,
|
||||
"can_add_new_rows": false,
|
||||
"new_category_placeholder": "Add Group",
|
||||
"new_category_message": "Save and reload to see ranks",
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "User / Group",
|
||||
"label": "Rank",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level permissions that might otherwise apply to that user. Additionally, if more than one parameter is applicable to a given user, the permissions given to that user will be the sum of all applicable parameters. For example, let’s say only localhost users can connect and only logged in users can lock and unlock entities. If a user is both logged in and on localhost then they will be able to both connect and lock/unlock entities.</p>'>?</a>",
|
||||
"span": 6
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in, as well as permissions from the previous section. Group permissions are only granted if the user doesn’t have their own row in the per-account section, below.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": ""
|
||||
"label": "Group Name",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_id",
|
||||
"label": "Rank ID",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_order",
|
||||
"label": "Rank Order",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_name",
|
||||
"label": "",
|
||||
"readonly": true
|
||||
},
|
||||
{
|
||||
"name": "group_id",
|
||||
"label": "Group ID",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": true
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
|
@ -514,6 +558,257 @@
|
|||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "group_forbiddens",
|
||||
"type": "table",
|
||||
"caption": "Permissions Denied to Users in Groups",
|
||||
"categorize_by_key": "permissions_id",
|
||||
"can_add_new_categories": true,
|
||||
"can_add_new_rows": false,
|
||||
"new_category_placeholder": "Add Blacklist Group",
|
||||
"new_category_message": "Save and reload to see ranks",
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "Rank",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users in specific groups can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users in specific groups can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users in specific groups can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether user in specific groups can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Permissions granted to a specific user will be a union of the permissions granted to the groups they are in. Group permissions are only granted if the user doesn’t have their own row in the per-account section, below.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": "Group Name",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_id",
|
||||
"label": "Rank ID",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_order",
|
||||
"label": "Rank Order",
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "rank_name",
|
||||
"label": "",
|
||||
"readonly": true
|
||||
},
|
||||
{
|
||||
"name": "group_id",
|
||||
"label": "Group ID",
|
||||
"readonly": true,
|
||||
"hidden": true
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
"label": "Lock / Unlock",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez",
|
||||
"label": "Rez",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp",
|
||||
"label": "Rez Temporary",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect_past_max_capacity",
|
||||
"label": "Ignore Max Capacity",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "ip_permissions",
|
||||
"type": "table",
|
||||
"caption": "Permissions for Users from IP Addresses",
|
||||
"can_add_new_rows": true,
|
||||
"groups": [
|
||||
{
|
||||
"label": "IP Address",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide IP Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users from specific IPs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific IPs can change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users from specific IPs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users from specific IPs can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users from specific IPs can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific IP will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). IP address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": ""
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
"label": "Lock / Unlock",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez",
|
||||
"label": "Rez",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp",
|
||||
"label": "Rez Temporary",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect_past_max_capacity",
|
||||
"label": "Ignore Max Capacity",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "permissions",
|
||||
"type": "table",
|
||||
"caption": "Permissions for Specific Users",
|
||||
"can_add_new_rows": true,
|
||||
|
||||
"groups": [
|
||||
{
|
||||
"label": "User",
|
||||
"span": 1
|
||||
},
|
||||
{
|
||||
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide User Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether a user can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether a user change the “locked” property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether a user can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain’s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether a user can connect even if the domain has reached or exceeded its maximum allowed agents.</li></ul><p>Note that permissions assigned to a specific user will supersede any parameter-level or group permissions that might otherwise apply to that user.</p>'>?</a>",
|
||||
"span": 7
|
||||
}
|
||||
],
|
||||
|
||||
"columns": [
|
||||
{
|
||||
"name": "permissions_id",
|
||||
"label": ""
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect",
|
||||
"label": "Connect",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_adjust_locks",
|
||||
"label": "Lock / Unlock",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez",
|
||||
"label": "Rez",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_rez_tmp",
|
||||
"label": "Rez Temporary",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_write_to_asset_server",
|
||||
"label": "Write Assets",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_connect_past_max_capacity",
|
||||
"label": "Ignore Max Capacity",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
},
|
||||
{
|
||||
"name": "id_can_kick",
|
||||
"label": "Kick Users",
|
||||
"type": "checkbox",
|
||||
"editable": true,
|
||||
"default": false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -568,15 +863,6 @@
|
|||
"help": "The path to the directory assets are stored in.<br/>If this path is relative, it will be relative to the application data directory.<br/>If you change this path you will need to manually copy any existing assets from the previous directory.",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "max_bandwidth",
|
||||
"type": "double",
|
||||
"label": "Max Bandwidth Per User",
|
||||
"help": "The maximum upstream bandwidth each user can use (in Mb/s).",
|
||||
"placeholder": "10.0",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -738,65 +1024,41 @@
|
|||
"name": "dynamic_jitter_buffer",
|
||||
"type": "checkbox",
|
||||
"label": "Dynamic Jitter Buffers",
|
||||
"help": "Dynamically buffer client audio based on perceived jitter in packet receipt timing",
|
||||
"help": "Dynamically buffer inbound audio streams based on perceived jitter in packet receipt timing.",
|
||||
"default": true,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "static_desired_jitter_buffer_frames",
|
||||
"label": "Static Desired Jitter Buffer Frames",
|
||||
"help": "If dynamic jitter buffers is disabled, this determines the target number of frames maintained by the AudioMixer's jitter buffers",
|
||||
"help": "If dynamic jitter buffers is disabled, this determines the size of the jitter buffers of inbound audio streams in the mixer. Higher numbers introduce more latency.",
|
||||
"placeholder": "1",
|
||||
"default": "1",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "max_frames_over_desired",
|
||||
"label": "Max Frames Over Desired",
|
||||
"help": "The highest number of frames an AudioMixer's ringbuffer can exceed the desired jitter buffer frames by",
|
||||
"placeholder": "10",
|
||||
"default": "10",
|
||||
"advanced": true
|
||||
"name": "max_frames_over_desired",
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "use_stdev_for_desired_calc",
|
||||
"type": "checkbox",
|
||||
"label": "Stdev for Desired Jitter Frames Calc",
|
||||
"help": "Use Philip's method (stdev of timegaps) to calculate desired jitter frames (otherwise Fred's max timegap method is used)",
|
||||
"default": false,
|
||||
"advanced": true
|
||||
"name": "window_starve_threshold",
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "window_starve_threshold",
|
||||
"label": "Window Starve Threshold",
|
||||
"help": "If this many starves occur in an N-second window (N is the number in the next field), then the desired jitter frames will be re-evaluated using Window A.",
|
||||
"placeholder": "3",
|
||||
"default": "3",
|
||||
"advanced": true
|
||||
"name": "window_seconds_for_desired_calc_on_too_many_starves",
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "window_seconds_for_desired_calc_on_too_many_starves",
|
||||
"label": "Timegaps Window (A) Seconds",
|
||||
"help": "Window A contains a history of timegaps. Its max timegap is used to re-evaluate the desired jitter frames when too many starves occur within it.",
|
||||
"placeholder": "50",
|
||||
"default": "50",
|
||||
"advanced": true
|
||||
"name": "window_seconds_for_desired_reduction",
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "window_seconds_for_desired_reduction",
|
||||
"label": "Timegaps Window (B) Seconds",
|
||||
"help": "Window B contains a history of timegaps. Its max timegap is used as a ceiling for the desired jitter frames value.",
|
||||
"placeholder": "10",
|
||||
"default": "10",
|
||||
"advanced": true
|
||||
"name": "use_stdev_for_desired_calc",
|
||||
"deprecated": true
|
||||
},
|
||||
{
|
||||
"name": "repetition_with_fade",
|
||||
"type": "checkbox",
|
||||
"label": "Repetition with Fade",
|
||||
"help": "Dropped frames and mixing during starves repeat the last frame, eventually fading to silence",
|
||||
"default": false,
|
||||
"advanced": true
|
||||
"name": "repetition_with_fade",
|
||||
"deprecated": true
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -821,6 +1083,14 @@
|
|||
"default": "models.json.gz",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "backupDirectoryPath",
|
||||
"label": "Entities Backup Directory Path",
|
||||
"help": "The path to the directory to store backups in.<br/>If this path is relative it will be relative to the application data directory.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "persistInterval",
|
||||
"label": "Save Check Interval",
|
||||
|
|
|
@ -3,6 +3,10 @@ body {
|
|||
padding-bottom: 30px;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.table-lead .lead-line {
|
||||
background-color: black;
|
||||
}
|
||||
|
@ -20,7 +24,9 @@ body {
|
|||
top: 40px;
|
||||
}
|
||||
|
||||
.table .value-row td, .table .inputs td {
|
||||
.table .value-row td,
|
||||
.table .value-category td,
|
||||
.table .inputs td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
|
@ -31,6 +37,31 @@ body {
|
|||
margin-right: auto;
|
||||
}
|
||||
|
||||
.value-category:not(.inputs) {
|
||||
font-weight: bold;
|
||||
background: #f5f5f5;
|
||||
}
|
||||
|
||||
.table .value-category [message]::after {
|
||||
content: attr(message);
|
||||
font-style: italic;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
.table .value-row.contracted,
|
||||
.table .inputs.contracted {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.toggle-category {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toggle-category-icon {
|
||||
padding: 4px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.glyphicon-remove {
|
||||
font-size: 24px;
|
||||
}
|
||||
|
@ -44,16 +75,7 @@ span.port {
|
|||
color: #666666;
|
||||
}
|
||||
|
||||
.locked {
|
||||
color: #428bca;
|
||||
}
|
||||
|
||||
.locked-table {
|
||||
cursor: not-allowed;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
.advanced-setting {
|
||||
.advanced-setting, .deprecated-setting {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -133,7 +155,7 @@ table .headers + .headers td {
|
|||
color: #222;
|
||||
}
|
||||
|
||||
table[name="security.standard_permissions"] .headers td + td, table[name="security.permissions"] .headers td + td {
|
||||
#security table .headers td + td {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
<ul class="nav nav-pills nav-stacked">
|
||||
</ul>
|
||||
|
||||
<button id="advanced-toggle-button" hidden=true class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
<button id="advanced-toggle-button" class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
<button class="btn btn-success save-button">Save and restart</button>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -40,7 +40,8 @@
|
|||
|
||||
<script id="panels-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% split_settings = _.partition(group.settings, function(value, index) { return !value.advanced }) %>
|
||||
<% var settings = _.partition(group.settings, function(value, index) { return !value.deprecated })[0] %>
|
||||
<% split_settings = _.partition(settings, function(value, index) { return !value.advanced }) %>
|
||||
<% isAdvanced = _.isEmpty(split_settings[0]) %>
|
||||
<% if (isAdvanced) { %>
|
||||
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
|
||||
|
@ -57,15 +58,13 @@
|
|||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, false,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<%= getFormGroup(keypath, setting, values, false) %>
|
||||
<% }); %>
|
||||
<% if (!_.isEmpty(split_settings[1])) { %>
|
||||
<% $("#advanced-toggle-button").show() %>
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, true,
|
||||
(_.has(locked, group.name) && _.has(locked[group.name], setting.name))) %>
|
||||
<%= getFormGroup(keypath, setting, values, true) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
</div>
|
||||
|
|
|
@ -2,13 +2,24 @@ var Settings = {
|
|||
showAdvanced: false,
|
||||
METAVERSE_URL: 'https://metaverse.highfidelity.com',
|
||||
ADVANCED_CLASS: 'advanced-setting',
|
||||
DEPRECATED_CLASS: 'deprecated-setting',
|
||||
TRIGGER_CHANGE_CLASS: 'trigger-change',
|
||||
DATA_ROW_CLASS: 'value-row',
|
||||
DATA_COL_CLASS: 'value-col',
|
||||
DATA_CATEGORY_CLASS: 'value-category',
|
||||
ADD_ROW_BUTTON_CLASS: 'add-row',
|
||||
ADD_ROW_SPAN_CLASSES: 'glyphicon glyphicon-plus add-row',
|
||||
DEL_ROW_BUTTON_CLASS: 'del-row',
|
||||
DEL_ROW_SPAN_CLASSES: 'glyphicon glyphicon-remove del-row',
|
||||
ADD_CATEGORY_BUTTON_CLASS: 'add-category',
|
||||
ADD_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-plus add-category',
|
||||
TOGGLE_CATEGORY_COLUMN_CLASS: 'toggle-category',
|
||||
TOGGLE_CATEGORY_SPAN_CLASS: 'toggle-category-icon',
|
||||
TOGGLE_CATEGORY_SPAN_CLASSES: 'glyphicon toggle-category-icon',
|
||||
TOGGLE_CATEGORY_EXPANDED_CLASS: 'glyphicon-triangle-bottom',
|
||||
TOGGLE_CATEGORY_CONTRACTED_CLASS: 'glyphicon-triangle-right',
|
||||
DEL_CATEGORY_BUTTON_CLASS: 'del-category',
|
||||
DEL_CATEGORY_SPAN_CLASSES: 'glyphicon glyphicon-remove del-category',
|
||||
MOVE_UP_BUTTON_CLASS: 'move-up',
|
||||
MOVE_UP_SPAN_CLASSES: 'glyphicon glyphicon-chevron-up move-up',
|
||||
MOVE_DOWN_BUTTON_CLASS: 'move-down',
|
||||
|
@ -31,11 +42,14 @@ var Settings = {
|
|||
};
|
||||
|
||||
var viewHelpers = {
|
||||
getFormGroup: function(keypath, setting, values, isAdvanced, isLocked) {
|
||||
form_group = "<div class='form-group " + (isAdvanced ? Settings.ADVANCED_CLASS : "") + "' data-keypath='" + keypath + "'>";
|
||||
getFormGroup: function(keypath, setting, values, isAdvanced) {
|
||||
form_group = "<div class='form-group " +
|
||||
(isAdvanced ? Settings.ADVANCED_CLASS : "") + " " +
|
||||
(setting.deprecated ? Settings.DEPRECATED_CLASS : "" ) + "' " +
|
||||
"data-keypath='" + keypath + "'>";
|
||||
setting_value = _(values).valueForKeyPath(keypath);
|
||||
|
||||
if (typeof setting_value == 'undefined' || setting_value === null) {
|
||||
if (_.isUndefined(setting_value) || _.isNull(setting_value)) {
|
||||
if (_.has(setting, 'default')) {
|
||||
setting_value = setting.default;
|
||||
} else {
|
||||
|
@ -44,16 +58,13 @@ var viewHelpers = {
|
|||
}
|
||||
|
||||
label_class = 'control-label';
|
||||
if (isLocked) {
|
||||
label_class += ' locked';
|
||||
}
|
||||
|
||||
function common_attrs(extra_classes) {
|
||||
extra_classes = (typeof extra_classes !== 'undefined' ? extra_classes : "");
|
||||
extra_classes = (!_.isUndefined(extra_classes) ? extra_classes : "");
|
||||
return " class='" + (setting.type !== 'checkbox' ? 'form-control' : '')
|
||||
+ " " + Settings.TRIGGER_CHANGE_CLASS + " " + extra_classes + "' data-short-name='"
|
||||
+ setting.name + "' name='" + keypath + "' "
|
||||
+ "id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath) + "'";
|
||||
+ "id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "'";
|
||||
}
|
||||
|
||||
if (setting.type === 'checkbox') {
|
||||
|
@ -61,9 +72,8 @@ var viewHelpers = {
|
|||
form_group += "<label class='" + label_class + "'>" + setting.label + "</label>"
|
||||
}
|
||||
|
||||
form_group += "<div class='toggle-checkbox-container" + (isLocked ? " disabled" : "") + "'>"
|
||||
form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "")
|
||||
form_group += (isLocked ? " disabled" : "") + "/>"
|
||||
form_group += "<div class='toggle-checkbox-container'>"
|
||||
form_group += "<input type='checkbox'" + common_attrs('toggle-checkbox') + (setting_value ? "checked" : "") + "/>"
|
||||
|
||||
if (setting.help) {
|
||||
form_group += "<span class='help-block checkbox-help'>" + setting.help + "</span>";
|
||||
|
@ -78,7 +88,7 @@ var viewHelpers = {
|
|||
}
|
||||
|
||||
if (input_type === 'table') {
|
||||
form_group += makeTable(setting, keypath, setting_value, isLocked)
|
||||
form_group += makeTable(setting, keypath, setting_value)
|
||||
} else {
|
||||
if (input_type === 'select') {
|
||||
form_group += "<select class='form-control' data-hidden-input='" + keypath + "'>'"
|
||||
|
@ -97,12 +107,10 @@ var viewHelpers = {
|
|||
|
||||
if (setting.href) {
|
||||
form_group += "<a href='" + setting.href + "'style='display: block;' role='button'"
|
||||
+ (isLocked ? " disabled" : "")
|
||||
+ common_attrs("btn " + setting.classes) + " target='_blank'>"
|
||||
+ setting.button_label + "</a>";
|
||||
} else {
|
||||
form_group += "<button " + common_attrs("btn " + setting.classes)
|
||||
+ (isLocked ? " disabled" : "") + ">"
|
||||
form_group += "<button " + common_attrs("btn " + setting.classes) + ">"
|
||||
+ setting.button_label + "</button>";
|
||||
}
|
||||
|
||||
|
@ -114,7 +122,7 @@ var viewHelpers = {
|
|||
|
||||
form_group += "<input type='" + input_type + "'" + common_attrs() +
|
||||
"placeholder='" + (_.has(setting, 'placeholder') ? setting.placeholder : "") +
|
||||
"' value='" + setting_value + "'" + (isLocked ? " disabled" : "") + "/>"
|
||||
"' value='" + setting_value + "'/>"
|
||||
}
|
||||
|
||||
form_group += "<span class='help-block'>" + setting.help + "</span>"
|
||||
|
@ -162,19 +170,31 @@ $(document).ready(function(){
|
|||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_ROW_BUTTON_CLASS, function(){
|
||||
addTableRow(this);
|
||||
addTableRow($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_ROW_BUTTON_CLASS, function(){
|
||||
deleteTableRow(this);
|
||||
deleteTableRow($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.ADD_CATEGORY_BUTTON_CLASS, function(){
|
||||
addTableCategory($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.DEL_CATEGORY_BUTTON_CLASS, function(){
|
||||
deleteTableCategory($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.TOGGLE_CATEGORY_COLUMN_CLASS, function(){
|
||||
toggleTableCategory($(this).closest('tr'));
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_UP_BUTTON_CLASS, function(){
|
||||
moveTableRow(this, true);
|
||||
moveTableRow($(this).closest('tr'), true);
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('click', '.' + Settings.MOVE_DOWN_BUTTON_CLASS, function(){
|
||||
moveTableRow(this, false);
|
||||
moveTableRow($(this).closest('tr'), false);
|
||||
});
|
||||
|
||||
$('#' + Settings.FORM_ID).on('keyup', function(e){
|
||||
|
@ -196,10 +216,11 @@ $(document).ready(function(){
|
|||
}
|
||||
|
||||
if (sibling.hasClass(Settings.ADD_DEL_BUTTONS_CLASS)) {
|
||||
sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click()
|
||||
sibling.find('.' + Settings.ADD_ROW_BUTTON_CLASS).click();
|
||||
sibling.find("." + Settings.ADD_CATEGORY_BUTTON_CLASS).click();
|
||||
|
||||
// set focus to the first input in the new row
|
||||
$target.closest('table').find('tr.inputs input:first').focus()
|
||||
$target.closest('table').find('tr.inputs input:first').focus();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -436,10 +457,8 @@ function setupHFAccountButton() {
|
|||
$("[data-keypath='metaverse.automatic_networking']").hide();
|
||||
}
|
||||
|
||||
var tokenLocked = _(Settings.data).valueForKeyPath("locked.metaverse.access_token");
|
||||
|
||||
// use the existing getFormGroup helper to ask for a button
|
||||
var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values, false, tokenLocked);
|
||||
var buttonGroup = viewHelpers.getFormGroup('', buttonSetting, Settings.data.values);
|
||||
|
||||
// add the button group to the top of the metaverse panel
|
||||
$('#metaverse .panel-body').prepend(buttonGroup);
|
||||
|
@ -629,7 +648,7 @@ function setupPlacesTable() {
|
|||
label: 'Places',
|
||||
html_id: Settings.PLACES_TABLE_ID,
|
||||
help: "The following places currently point to this domain.</br>To point places to this domain, "
|
||||
+ " go to the <a href='https://metaverse.highfidelity.com/user/places'>My Places</a> "
|
||||
+ " go to the <a href='" + Settings.METAVERSE_URL + "/user/places'>My Places</a> "
|
||||
+ "page in your High Fidelity Metaverse account.",
|
||||
read_only: true,
|
||||
columns: [
|
||||
|
@ -650,7 +669,7 @@ function setupPlacesTable() {
|
|||
}
|
||||
|
||||
// get a table for the places
|
||||
var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values, false, false);
|
||||
var placesTableGroup = viewHelpers.getFormGroup('', placesTableSetting, Settings.data.values);
|
||||
|
||||
// append the places table in the right place
|
||||
$('#places_paths .panel-body').prepend(placesTableGroup);
|
||||
|
@ -771,7 +790,7 @@ function chooseFromHighFidelityDomains(clickedButton) {
|
|||
modal_buttons["success"] = {
|
||||
label: 'Create new domain',
|
||||
callback: function() {
|
||||
window.open("https://metaverse.highfidelity.com/user/domains", '_blank');
|
||||
window.open(Settings.METAVERSE_URL + "/user/domains", '_blank');
|
||||
}
|
||||
}
|
||||
modal_body = "<p>You do not have any domains in your High Fidelity account." +
|
||||
|
@ -850,10 +869,8 @@ function reloadSettings(callback) {
|
|||
Settings.data = data;
|
||||
Settings.initialValues = form2js('settings-form', ".", false, cleanupFormValues, true);
|
||||
|
||||
if (!_.has(data["locked"], "metaverse") && !_.has(data["locked"]["metaverse"], "id")) {
|
||||
// append the domain selection modal, as long as it's not locked
|
||||
appendDomainIDButtons();
|
||||
}
|
||||
// append the domain selection modal
|
||||
appendDomainIDButtons();
|
||||
|
||||
// call our method to setup the HF account button
|
||||
setupHFAccountButton();
|
||||
|
@ -866,12 +883,6 @@ function reloadSettings(callback) {
|
|||
|
||||
$('[data-toggle="tooltip"]').tooltip();
|
||||
|
||||
// add tooltip to locked settings
|
||||
$('label.locked').tooltip({
|
||||
placement: 'right',
|
||||
title: 'This setting is in the master config file and cannot be changed'
|
||||
});
|
||||
|
||||
// call the callback now that settings are loaded
|
||||
callback(true);
|
||||
}).fail(function() {
|
||||
|
@ -920,9 +931,10 @@ $('body').on('click', '.save-button', function(e){
|
|||
return false;
|
||||
});
|
||||
|
||||
function makeTable(setting, keypath, setting_value, isLocked) {
|
||||
function makeTable(setting, keypath, setting_value) {
|
||||
var isArray = !_.has(setting, 'key');
|
||||
var isHash = !isArray;
|
||||
var categoryKey = setting.categorize_by_key;
|
||||
var isCategorized = !!categoryKey && isArray;
|
||||
|
||||
if (!isArray && setting.can_order) {
|
||||
setting.can_order = false;
|
||||
|
@ -937,9 +949,10 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
var nonDeletableRowKey = setting["non-deletable-row-key"];
|
||||
var nonDeletableRowValues = setting["non-deletable-row-values"];
|
||||
|
||||
html += "<table class='table table-bordered " + (isLocked ? "locked-table" : "") + "' data-short-name='" + setting.name
|
||||
+ "' name='" + keypath + "' id='" + (typeof setting.html_id !== 'undefined' ? setting.html_id : keypath)
|
||||
+ "' data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
|
||||
html += "<table class='table table-bordered' " +
|
||||
"data-short-name='" + setting.name + "' name='" + keypath + "' " +
|
||||
"id='" + (!_.isUndefined(setting.html_id) ? setting.html_id : keypath) + "' " +
|
||||
"data-setting-type='" + (isArray ? 'array' : 'hash') + "'>";
|
||||
|
||||
if (setting.caption) {
|
||||
html += "<caption>" + setting.caption + "</caption>"
|
||||
|
@ -951,7 +964,7 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
_.each(setting.groups, function (group) {
|
||||
html += "<td colspan='" + group.span + "'><strong>" + group.label + "</strong></td>"
|
||||
})
|
||||
if (!isLocked && !setting.read_only) {
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_order) {
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
|
||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>";
|
||||
|
@ -972,24 +985,43 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
html += "<td class='key'><strong>" + setting.key.label + "</strong></td>" // Key
|
||||
}
|
||||
|
||||
var numVisibleColumns = 0;
|
||||
_.each(setting.columns, function(col) {
|
||||
html += "<td class='data " + (col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data
|
||||
if (!col.hidden) numVisibleColumns++;
|
||||
html += "<td " + (col.hidden ? "style='display: none;'" : "") + "class='data " +
|
||||
(col.class ? col.class : '') + "'><strong>" + col.label + "</strong></td>" // Data
|
||||
})
|
||||
|
||||
if (!isLocked && !setting.read_only) {
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_order) {
|
||||
numVisibleColumns++;
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES +
|
||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-sort'></a></td>";
|
||||
}
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>"
|
||||
numVisibleColumns++;
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'></td></tr>";
|
||||
}
|
||||
|
||||
// populate rows in the table from existing values
|
||||
var row_num = 1;
|
||||
|
||||
if (keypath.length > 0 && _.size(setting_value) > 0) {
|
||||
var rowIsObject = setting.columns.length > 1;
|
||||
|
||||
_.each(setting_value, function(row, rowIndexOrName) {
|
||||
html += "<tr class='" + Settings.DATA_ROW_CLASS + "'" + (isArray ? "" : "name='" + keypath + "." + rowIndexOrName + "'") + ">"
|
||||
var categoryPair = {};
|
||||
var categoryValue = "";
|
||||
if (isCategorized) {
|
||||
categoryValue = rowIsObject ? row[categoryKey] : row;
|
||||
categoryPair[categoryKey] = categoryValue;
|
||||
if (_.findIndex(setting_value, categoryPair) === rowIndexOrName) {
|
||||
html += makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, setting.can_add_new_categories, "");
|
||||
}
|
||||
}
|
||||
|
||||
html += "<tr class='" + Settings.DATA_ROW_CLASS + "' " +
|
||||
(isCategorized ? ("data-category='" + categoryValue + "'") : "") + " " +
|
||||
(isArray ? "" : "name='" + keypath + "." + rowIndexOrName + "'") + ">";
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'>" + row_num + "</td>"
|
||||
|
@ -1003,8 +1035,8 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|
||||
_.each(setting.columns, function(col) {
|
||||
|
||||
var colValue, colName;
|
||||
if (isArray) {
|
||||
rowIsObject = setting.columns.length > 1;
|
||||
colValue = rowIsObject ? row[col.name] : row;
|
||||
colName = keypath + "[" + rowIndexOrName + "]" + (rowIsObject ? "." + col.name : "");
|
||||
} else {
|
||||
|
@ -1016,22 +1048,30 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|| (nonDeletableRowKey === col.name && nonDeletableRowValues.indexOf(colValue) !== -1);
|
||||
|
||||
if (isArray && col.type === "checkbox" && col.editable) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||
+ "name='" + colName + "'" + (colValue ? " checked" : "") + " /></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='checkbox' class='form-control table-checkbox' " +
|
||||
"name='" + colName + "'" + (colValue ? " checked" : "") + "/>" +
|
||||
"</td>";
|
||||
} else if (isArray && col.type === "time" && col.editable) {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='time' class='form-control table-time' "
|
||||
+ "name='" + colName + "' value='" + (colValue || col.default || "00:00") + "' /></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='time' class='form-control table-time' name='" + colName + "' " +
|
||||
"value='" + (colValue || col.default || "00:00") + "'/>" +
|
||||
"</td>";
|
||||
} else {
|
||||
// Use a hidden input so that the values are posted.
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "' name='" + colName + "'>"
|
||||
+ colValue + "<input type='hidden' name='" + colName + "' value='" + colValue + "'/></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "' " + (col.hidden ? "style='display: none;'" : "") +
|
||||
"name='" + colName + "'>" +
|
||||
colValue +
|
||||
"<input type='hidden' name='" + colName + "' value='" + colValue + "'/>" +
|
||||
"</td>";
|
||||
}
|
||||
|
||||
})
|
||||
});
|
||||
|
||||
if (!isLocked && !setting.read_only) {
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_order) {
|
||||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES+
|
||||
"'><a href='javascript:void(0);' class='" + Settings.MOVE_UP_SPAN_CLASSES + "'></a>"
|
||||
|
@ -1047,24 +1087,53 @@ function makeTable(setting, keypath, setting_value, isLocked) {
|
|||
|
||||
html += "</tr>"
|
||||
|
||||
if (isCategorized && setting.can_add_new_rows && _.findLastIndex(setting_value, categoryPair) === rowIndexOrName) {
|
||||
html += makeTableInputs(setting, categoryPair, categoryValue);
|
||||
}
|
||||
|
||||
row_num++
|
||||
});
|
||||
}
|
||||
|
||||
// populate inputs in the table for new values
|
||||
if (!isLocked && !setting.read_only && setting.can_add_new_rows) {
|
||||
html += makeTableInputs(setting)
|
||||
if (!setting.read_only) {
|
||||
if (setting.can_add_new_categories) {
|
||||
html += makeTableCategoryInput(setting, numVisibleColumns);
|
||||
}
|
||||
if (setting.can_add_new_rows || setting.can_add_new_categories) {
|
||||
html += makeTableInputs(setting, {}, "");
|
||||
}
|
||||
}
|
||||
html += "</table>"
|
||||
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeTableInputs(setting) {
|
||||
var html = "<tr class='inputs'>"
|
||||
function makeTableCategoryHeader(categoryKey, categoryValue, numVisibleColumns, canRemove, message) {
|
||||
var html =
|
||||
"<tr class='" + Settings.DATA_CATEGORY_CLASS + "' data-key='" + categoryKey + "' data-category='" + categoryValue + "'>" +
|
||||
"<td colspan='" + (numVisibleColumns - 1) + "' class='" + Settings.TOGGLE_CATEGORY_COLUMN_CLASS + "'>" +
|
||||
"<span class='" + Settings.TOGGLE_CATEGORY_SPAN_CLASSES + " " + Settings.TOGGLE_CATEGORY_EXPANDED_CLASS + "'></span>" +
|
||||
"<span message='" + message + "'>" + categoryValue + "</span>" +
|
||||
"</td>" +
|
||||
((canRemove) ? (
|
||||
"<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'>" +
|
||||
"<a href='javascript:void(0);' class='" + Settings.DEL_CATEGORY_SPAN_CLASSES + "'></a>" +
|
||||
"</td>"
|
||||
) : (
|
||||
"<td></td>"
|
||||
)) +
|
||||
"</tr>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function makeTableInputs(setting, initialValues, categoryValue) {
|
||||
var html = "<tr class='inputs'" + (setting.can_add_new_categories && !categoryValue ? " hidden" : "") + " " +
|
||||
(categoryValue ? ("data-category='" + categoryValue + "'") : "") + " " +
|
||||
(setting.categorize_by_key ? ("data-keep-field='" + setting.categorize_by_key + "'") : "") + ">";
|
||||
|
||||
if (setting.numbered === true) {
|
||||
html += "<td class='numbered'></td>"
|
||||
html += "<td class='numbered'></td>";
|
||||
}
|
||||
|
||||
if (setting.key) {
|
||||
|
@ -1074,15 +1143,21 @@ function makeTableInputs(setting) {
|
|||
}
|
||||
|
||||
_.each(setting.columns, function(col) {
|
||||
var defaultValue = _.has(initialValues, col.name) ? initialValues[col.name] : col.default;
|
||||
if (col.type === "checkbox") {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>"
|
||||
+ "<input type='checkbox' class='form-control table-checkbox' "
|
||||
+ "name='" + col.name + "'" + (col.default ? " checked" : "") + "/></td>";
|
||||
html +=
|
||||
"<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>" +
|
||||
"<input type='checkbox' class='form-control table-checkbox' " +
|
||||
"name='" + col.name + "'" + (defaultValue ? " checked" : "") + "/>" +
|
||||
"</td>";
|
||||
} else {
|
||||
html += "<td class='" + Settings.DATA_COL_CLASS + "'name='" + col.name + "'>\
|
||||
<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "'\
|
||||
value='" + (col.default ? col.default : "") + "' data-default='" + (col.default ? col.default : "") + "'>\
|
||||
</td>"
|
||||
html +=
|
||||
"<td " + (col.hidden ? "style='display: none;'" : "") + " class='" + Settings.DATA_COL_CLASS + "' " +
|
||||
"name='" + col.name + "'>" +
|
||||
"<input type='text' class='form-control' placeholder='" + (col.placeholder ? col.placeholder : "") + "' " +
|
||||
"value='" + (defaultValue || "") + "' data-default='" + (defaultValue || "") + "'" +
|
||||
(col.readonly ? " readonly" : "") + ">" +
|
||||
"</td>";
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -1090,12 +1165,30 @@ function makeTableInputs(setting) {
|
|||
html += "<td class='" + Settings.REORDER_BUTTONS_CLASSES + "'></td>"
|
||||
}
|
||||
html += "<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES +
|
||||
"'><a href='javascript:void(0);' class='glyphicon glyphicon-plus " + Settings.ADD_ROW_BUTTON_CLASS + "'></a></td>"
|
||||
"'><a href='javascript:void(0);' class='" + Settings.ADD_ROW_SPAN_CLASSES + "'></a></td>"
|
||||
html += "</tr>"
|
||||
|
||||
return html
|
||||
}
|
||||
|
||||
function makeTableCategoryInput(setting, numVisibleColumns) {
|
||||
var canAddRows = setting.can_add_new_rows;
|
||||
var categoryKey = setting.categorize_by_key;
|
||||
var placeholder = setting.new_category_placeholder || "";
|
||||
var message = setting.new_category_message || "";
|
||||
var html =
|
||||
"<tr class='" + Settings.DATA_CATEGORY_CLASS + " inputs' data-can-add-rows='" + canAddRows + "' " +
|
||||
"data-key='" + categoryKey + "' data-message='" + message + "'>" +
|
||||
"<td colspan='" + (numVisibleColumns - 1) + "'>" +
|
||||
"<input type='text' class='form-control' placeholder='" + placeholder + "'/>" +
|
||||
"</td>" +
|
||||
"<td class='" + Settings.ADD_DEL_BUTTONS_CLASSES + "'>" +
|
||||
"<a href='javascript:void(0);' class='" + Settings.ADD_CATEGORY_SPAN_CLASSES + "'></a>" +
|
||||
"</td>" +
|
||||
"</tr>";
|
||||
return html;
|
||||
}
|
||||
|
||||
function badgeSidebarForDifferences(changedElement) {
|
||||
// figure out which group this input is in
|
||||
var panelParentID = changedElement.closest('.panel').attr('id');
|
||||
|
@ -1134,13 +1227,12 @@ function badgeSidebarForDifferences(changedElement) {
|
|||
$("a[href='#" + panelParentID + "'] .badge").html(badgeValue);
|
||||
}
|
||||
|
||||
function addTableRow(add_glyphicon) {
|
||||
var row = $(add_glyphicon).closest('tr')
|
||||
function addTableRow(row) {
|
||||
var table = row.parents('table');
|
||||
var isArray = table.data('setting-type') === 'array';
|
||||
var keepField = row.data("keep-field");
|
||||
|
||||
var table = row.parents('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS)
|
||||
var columns = row.parent().children('.' + Settings.DATA_ROW_CLASS);
|
||||
|
||||
if (!isArray) {
|
||||
// Check key spaces
|
||||
|
@ -1257,10 +1349,12 @@ function addTableRow(add_glyphicon) {
|
|||
} else {
|
||||
console.log("Unknown table element")
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
input_clone.find('input').each(function(){
|
||||
$(this).val($(this).attr('data-default'));
|
||||
input_clone.children('td').each(function () {
|
||||
if ($(this).attr("name") !== keepField) {
|
||||
$(this).find("input").val($(this).attr('data-default'));
|
||||
}
|
||||
});
|
||||
|
||||
if (isArray) {
|
||||
|
@ -1272,44 +1366,132 @@ function addTableRow(add_glyphicon) {
|
|||
|
||||
badgeSidebarForDifferences($(table))
|
||||
|
||||
row.parent().append(input_clone)
|
||||
row.after(input_clone)
|
||||
}
|
||||
|
||||
function deleteTableRow(delete_glyphicon) {
|
||||
var row = $(delete_glyphicon).closest('tr')
|
||||
function deleteTableRow($row) {
|
||||
var $table = $row.closest('table');
|
||||
var categoryName = $row.data("category");
|
||||
var isArray = $table.data('setting-type') === 'array';
|
||||
|
||||
var table = $(row).closest('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
|
||||
row.empty();
|
||||
$row.empty();
|
||||
|
||||
if (!isArray) {
|
||||
row.html("<input type='hidden' class='form-control' name='"
|
||||
+ row.attr('name') + "' data-changed='true' value=''>");
|
||||
$row.html("<input type='hidden' class='form-control' name='" + $row.attr('name') + "' data-changed='true' value=''>");
|
||||
} else {
|
||||
if (table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
|
||||
updateDataChangedForSiblingRows(row)
|
||||
if ($table.find('.' + Settings.DATA_ROW_CLASS + "[data-category='" + categoryName + "']").length <= 1) {
|
||||
// This is the last row of the category, so delete the header
|
||||
$table.find('.' + Settings.DATA_CATEGORY_CLASS + "[data-category='" + categoryName + "']").remove();
|
||||
}
|
||||
|
||||
if ($table.find('.' + Settings.DATA_ROW_CLASS).length > 1) {
|
||||
updateDataChangedForSiblingRows($row);
|
||||
|
||||
// this isn't the last row - we can just remove it
|
||||
row.remove()
|
||||
$row.remove();
|
||||
} else {
|
||||
// this is the last row, we can't remove it completely since we need to post an empty array
|
||||
|
||||
row.removeClass(Settings.DATA_ROW_CLASS).removeClass(Settings.NEW_ROW_CLASS)
|
||||
row.addClass('empty-array-row')
|
||||
|
||||
row.html("<input type='hidden' class='form-control' name='" + table.attr("name").replace('[]', '')
|
||||
+ "' data-changed='true' value=''>");
|
||||
$row
|
||||
.removeClass(Settings.DATA_ROW_CLASS)
|
||||
.removeClass(Settings.NEW_ROW_CLASS)
|
||||
.removeAttr("data-category")
|
||||
.addClass('empty-array-row')
|
||||
.html("<input type='hidden' class='form-control' name='" + $table.attr("name").replace('[]', '') + "' " +
|
||||
"data-changed='true' value=''>");
|
||||
}
|
||||
}
|
||||
|
||||
// we need to fire a change event on one of the remaining inputs so that the sidebar badge is updated
|
||||
badgeSidebarForDifferences($(table))
|
||||
badgeSidebarForDifferences($table);
|
||||
}
|
||||
|
||||
function moveTableRow(move_glyphicon, move_up) {
|
||||
var row = $(move_glyphicon).closest('tr')
|
||||
function addTableCategory($categoryInputRow) {
|
||||
var $input = $categoryInputRow.find("input").first();
|
||||
var categoryValue = $input.prop("value");
|
||||
if (!categoryValue || $categoryInputRow.closest("table").find("tr[data-category='" + categoryValue + "']").length !== 0) {
|
||||
$categoryInputRow.addClass("has-warning");
|
||||
|
||||
setTimeout(function () {
|
||||
$categoryInputRow.removeClass("has-warning");
|
||||
}, 400);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var $rowInput = $categoryInputRow.next(".inputs").clone();
|
||||
if (!$rowInput) {
|
||||
console.error("Error cloning inputs");
|
||||
}
|
||||
|
||||
var canAddRows = $categoryInputRow.data("can-add-rows");
|
||||
var message = $categoryInputRow.data("message");
|
||||
var categoryKey = $categoryInputRow.data("key");
|
||||
var width = 0;
|
||||
$categoryInputRow
|
||||
.children("td")
|
||||
.each(function () {
|
||||
width += $(this).prop("colSpan") || 1;
|
||||
});
|
||||
|
||||
$input
|
||||
.prop("value", "")
|
||||
.focus();
|
||||
|
||||
$rowInput.find("td[name='" + categoryKey + "'] > input").first()
|
||||
.prop("value", categoryValue);
|
||||
$rowInput
|
||||
.attr("data-category", categoryValue)
|
||||
.addClass(Settings.NEW_ROW_CLASS);
|
||||
|
||||
var $newCategoryRow = $(makeTableCategoryHeader(categoryKey, categoryValue, width, true, " - " + message));
|
||||
$newCategoryRow.addClass(Settings.NEW_ROW_CLASS);
|
||||
|
||||
$categoryInputRow
|
||||
.before($newCategoryRow)
|
||||
.before($rowInput);
|
||||
|
||||
if (canAddRows) {
|
||||
$rowInput.removeAttr("hidden");
|
||||
} else {
|
||||
addTableRow($rowInput);
|
||||
}
|
||||
}
|
||||
|
||||
function deleteTableCategory($categoryHeaderRow) {
|
||||
var categoryName = $categoryHeaderRow.data("category");
|
||||
|
||||
$categoryHeaderRow
|
||||
.closest("table")
|
||||
.find("tr[data-category='" + categoryName + "']")
|
||||
.each(function () {
|
||||
if ($(this).hasClass(Settings.DATA_ROW_CLASS)) {
|
||||
deleteTableRow($(this));
|
||||
} else {
|
||||
$(this).remove();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function toggleTableCategory($categoryHeaderRow) {
|
||||
var $icon = $categoryHeaderRow.find("." + Settings.TOGGLE_CATEGORY_SPAN_CLASS).first();
|
||||
var categoryName = $categoryHeaderRow.data("category");
|
||||
var wasExpanded = $icon.hasClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS);
|
||||
if (wasExpanded) {
|
||||
$icon
|
||||
.addClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS)
|
||||
.removeClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS);
|
||||
} else {
|
||||
$icon
|
||||
.addClass(Settings.TOGGLE_CATEGORY_EXPANDED_CLASS)
|
||||
.removeClass(Settings.TOGGLE_CATEGORY_CONTRACTED_CLASS);
|
||||
}
|
||||
$categoryHeaderRow
|
||||
.closest("table")
|
||||
.find("tr[data-category='" + categoryName + "']")
|
||||
.toggleClass("contracted", wasExpanded);
|
||||
}
|
||||
|
||||
function moveTableRow(row, move_up) {
|
||||
var table = $(row).closest('table')
|
||||
var isArray = table.data('setting-type') === 'array'
|
||||
if (!isArray) {
|
||||
|
|
|
@ -120,6 +120,102 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
|
|||
}
|
||||
}
|
||||
|
||||
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress) {
|
||||
NodePermissions userPerms;
|
||||
|
||||
userPerms.setAll(false);
|
||||
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: is local user, so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
|
||||
if (verifiedUsername.isEmpty()) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: unverified or no username for" << userPerms.getID() << ", so:" << userPerms;
|
||||
#endif
|
||||
|
||||
if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
|
||||
// this user comes from an IP we have in our permissions table, apply those permissions
|
||||
userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: specific IP matches, so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
} else {
|
||||
if (_server->_settingsManager.havePermissionsForName(verifiedUsername)) {
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(verifiedUsername);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: specific user matches, so:" << userPerms;
|
||||
#endif
|
||||
} else if (_server->_settingsManager.hasPermissionsForIP(senderAddress)) {
|
||||
// this user comes from an IP we have in our permissions table, apply those permissions
|
||||
userPerms = _server->_settingsManager.getPermissionsForIP(senderAddress);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: specific IP matches, so:" << userPerms;
|
||||
#endif
|
||||
} else {
|
||||
// they are logged into metaverse, but we don't have specific permissions for them.
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user is logged-into metaverse, so:" << userPerms;
|
||||
#endif
|
||||
|
||||
// if this user is a friend of the domain-owner, give them friend's permissions
|
||||
if (_domainOwnerFriends.contains(verifiedUsername)) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameFriends);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user is friends with domain-owner, so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
|
||||
// if this user is a known member of a group, give them the implied permissions
|
||||
foreach (QUuid groupID, _server->_settingsManager.getGroupIDs()) {
|
||||
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
|
||||
if (rankID != QUuid()) {
|
||||
userPerms |= _server->_settingsManager.getPermissionsForGroup(groupID, rankID);
|
||||
|
||||
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user " << verifiedUsername << "is in group:" << groupID << " rank:"
|
||||
<< rank.name << "so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
// if this user is a known member of a blacklist group, remove the implied permissions
|
||||
foreach (QUuid groupID, _server->_settingsManager.getBlacklistGroupIDs()) {
|
||||
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
|
||||
if (rankID != QUuid()) {
|
||||
QUuid rankID = _server->_settingsManager.isGroupMember(verifiedUsername, groupID);
|
||||
if (rankID != QUuid()) {
|
||||
userPerms &= ~_server->_settingsManager.getForbiddensForGroup(groupID, rankID);
|
||||
|
||||
GroupRank rank = _server->_settingsManager.getGroupRank(groupID, rankID);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: user is in blacklist group:" << groupID << " rank:" << rank.name
|
||||
<< "so:" << userPerms;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userPerms.setID(verifiedUsername);
|
||||
userPerms.setVerifiedUserName(verifiedUsername);
|
||||
}
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "| user-permissions: final:" << userPerms;
|
||||
#endif
|
||||
return userPerms;
|
||||
}
|
||||
|
||||
void DomainGatekeeper::updateNodePermissions() {
|
||||
// If the permissions were changed on the domain-server webpage (and nothing else was), a restart isn't required --
|
||||
// we reprocess the permissions map and update the nodes here. The node list is frequently sent out to all
|
||||
|
@ -129,40 +225,35 @@ void DomainGatekeeper::updateNodePermissions() {
|
|||
|
||||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
limitedNodeList->eachNode([this, limitedNodeList, &nodesToKill](const SharedNodePointer& node){
|
||||
QString username = node->getPermissions().getUserName();
|
||||
NodePermissions userPerms(username);
|
||||
// the id and the username in NodePermissions will often be the same, but id is set before
|
||||
// authentication and verifiedUsername is only set once they user's key has been confirmed.
|
||||
QString verifiedUsername = node->getPermissions().getVerifiedUserName();
|
||||
NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0));
|
||||
|
||||
if (node->getPermissions().isAssignment) {
|
||||
// this node is an assignment-client
|
||||
userPerms.isAssignment = true;
|
||||
userPerms.canAdjustLocks = true;
|
||||
userPerms.canRezPermanentEntities = true;
|
||||
userPerms.canRezTemporaryEntities = true;
|
||||
userPerms.permissions |= NodePermissions::Permission::canConnectToDomain;
|
||||
userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
|
||||
} else {
|
||||
// this node is an agent
|
||||
userPerms.setAll(false);
|
||||
|
||||
const QHostAddress& addr = node->getLocalSocket().getAddress();
|
||||
bool isLocalUser = (addr == limitedNodeList->getLocalSockAddr().getAddress() ||
|
||||
addr == QHostAddress::LocalHost);
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
}
|
||||
|
||||
if (username.isEmpty()) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
} else {
|
||||
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||
} else {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
}
|
||||
}
|
||||
// at this point we don't have a sending socket for packets from this node - assume it is the active socket
|
||||
// or the public socket if we haven't activated a socket for the node yet
|
||||
HifiSockAddr connectingAddr = node->getActiveSocket() ? *node->getActiveSocket() : node->getPublicSocket();
|
||||
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress());
|
||||
}
|
||||
|
||||
node->setPermissions(userPerms);
|
||||
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
qDebug() << "node" << node->getUUID() << "no longer has permission to connect.";
|
||||
// hang up on this node
|
||||
nodesToKill << node;
|
||||
|
@ -215,17 +306,20 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
|
|||
// cleanup the PendingAssignedNodeData for this assignment now that it's connecting
|
||||
_pendingAssignedNodes.erase(it);
|
||||
|
||||
// always allow assignment clients to create and destroy entities
|
||||
NodePermissions userPerms;
|
||||
userPerms.isAssignment = true;
|
||||
userPerms.canAdjustLocks = true;
|
||||
userPerms.canRezPermanentEntities = true;
|
||||
userPerms.canRezTemporaryEntities = true;
|
||||
userPerms.permissions |= NodePermissions::Permission::canConnectToDomain;
|
||||
// always allow assignment clients to create and destroy entities
|
||||
userPerms.permissions |= NodePermissions::Permission::canAdjustLocks;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezPermanentEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canRezTemporaryEntities;
|
||||
userPerms.permissions |= NodePermissions::Permission::canWriteToAssetServer;
|
||||
newNode->setPermissions(userPerms);
|
||||
return newNode;
|
||||
}
|
||||
|
||||
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
|
||||
const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location";
|
||||
|
||||
SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection,
|
||||
const QString& username,
|
||||
|
@ -234,64 +328,66 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
// start with empty permissions
|
||||
NodePermissions userPerms(username);
|
||||
NodePermissions userPerms(NodePermissionsKey(username, 0));
|
||||
userPerms.setAll(false);
|
||||
|
||||
// check if this user is on our local machine - if this is true set permissions to those for a "localhost" connection
|
||||
QHostAddress senderHostAddress = nodeConnection.senderSockAddr.getAddress();
|
||||
bool isLocalUser =
|
||||
(senderHostAddress == limitedNodeList->getLocalSockAddr().getAddress() || senderHostAddress == QHostAddress::LocalHost);
|
||||
if (isLocalUser) {
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLocalhost);
|
||||
qDebug() << "user-permissions: is local user, so:" << userPerms;
|
||||
}
|
||||
|
||||
if (!username.isEmpty() && usernameSignature.isEmpty()) {
|
||||
// user is attempting to prove their identity to us, but we don't have enough information
|
||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||
// ask for their public key right now to make sure we have it
|
||||
requestUserPublicKey(username);
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
QString verifiedUsername; // if this remains empty, consider this an anonymous connection attempt
|
||||
if (!username.isEmpty()) {
|
||||
if (usernameSignature.isEmpty()) {
|
||||
// user is attempting to prove their identity to us, but we don't have enough information
|
||||
sendConnectionTokenPacket(username, nodeConnection.senderSockAddr);
|
||||
// ask for their public key right now to make sure we have it
|
||||
requestUserPublicKey(username);
|
||||
getGroupMemberships(username); // optimistically get started on group memberships
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login because we have no username-signature:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
}
|
||||
|
||||
if (username.isEmpty()) {
|
||||
// they didn't tell us who they are
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
qDebug() << "user-permissions: no username, so:" << userPerms;
|
||||
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||
// they are sent us a username and the signature verifies it
|
||||
if (_server->_settingsManager.havePermissionsForName(username)) {
|
||||
// we have specific permissions for this user.
|
||||
userPerms = _server->_settingsManager.getPermissionsForName(username);
|
||||
qDebug() << "user-permissions: specific user matches, so:" << userPerms;
|
||||
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
|
||||
// they sent us a username and the signature verifies it
|
||||
getGroupMemberships(username);
|
||||
verifiedUsername = username;
|
||||
} else {
|
||||
// they are logged into metaverse, but we don't have specific permissions for them.
|
||||
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn);
|
||||
qDebug() << "user-permissions: user is logged in, so:" << userPerms;
|
||||
}
|
||||
userPerms.setUserName(username);
|
||||
} else {
|
||||
// they sent us a username, but it didn't check out
|
||||
requestUserPublicKey(username);
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
// they sent us a username, but it didn't check out
|
||||
requestUserPublicKey(username);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login because signature verification failed:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "user-permissions: final:" << userPerms;
|
||||
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress());
|
||||
|
||||
if (!userPerms.canConnectToDomain) {
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
|
||||
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.",
|
||||
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorized);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login due to permissions:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
|
||||
if (!userPerms.canConnectPastMaxCapacity && !isWithinMaxCapacity()) {
|
||||
if (!userPerms.can(NodePermissions::Permission::canConnectPastMaxCapacity) && !isWithinMaxCapacity()) {
|
||||
// we can't allow this user to connect because we are at max capacity
|
||||
QString redirectOnMaxCapacity;
|
||||
const QVariant* redirectOnMaxCapacityVariant =
|
||||
valueForKeyPath(_server->_settingsManager.getSettingsMap(), MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION);
|
||||
if (redirectOnMaxCapacityVariant && redirectOnMaxCapacityVariant->canConvert<QString>()) {
|
||||
redirectOnMaxCapacity = redirectOnMaxCapacityVariant->toString();
|
||||
qDebug() << "Redirection domain:" << redirectOnMaxCapacity;
|
||||
}
|
||||
|
||||
sendConnectionDeniedPacket("Too many connected users.", nodeConnection.senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason::TooManyUsers);
|
||||
DomainHandler::ConnectionRefusedReason::TooManyUsers, redirectOnMaxCapacity);
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "stalling login due to max capacity:" << username;
|
||||
#endif
|
||||
return SharedNodePointer();
|
||||
}
|
||||
|
||||
|
@ -305,10 +401,8 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
// we have a node that already has these exact sockets - this occurs if a node
|
||||
// is unable to connect to the domain
|
||||
hintNodeID = node->getUUID();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
|
@ -328,6 +422,10 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
|
|||
nodeData->addOverrideForKey(USERNAME_UUID_REPLACEMENT_STATS_KEY,
|
||||
uuidStringWithoutCurlyBraces(newNode->getUUID()), username);
|
||||
|
||||
#ifdef WANT_DEBUG
|
||||
qDebug() << "accepting login:" << username;
|
||||
#endif
|
||||
|
||||
return newNode;
|
||||
}
|
||||
|
||||
|
@ -365,11 +463,11 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
|
|||
bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
||||
const QByteArray& usernameSignature,
|
||||
const HifiSockAddr& senderSockAddr) {
|
||||
|
||||
// it's possible this user can be allowed to connect, but we need to check their username signature
|
||||
QByteArray publicKeyArray = _userPublicKeys.value(username);
|
||||
auto lowerUsername = username.toLower();
|
||||
QByteArray publicKeyArray = _userPublicKeys.value(lowerUsername);
|
||||
|
||||
const QUuid& connectionToken = _connectionTokenHash.value(username.toLower());
|
||||
const QUuid& connectionToken = _connectionTokenHash.value(lowerUsername);
|
||||
|
||||
if (!publicKeyArray.isEmpty() && !connectionToken.isNull()) {
|
||||
// if we do have a public key for the user, check for a signature match
|
||||
|
@ -379,8 +477,8 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
|||
// first load up the public key into an RSA struct
|
||||
RSA* rsaPublicKey = d2i_RSA_PUBKEY(NULL, &publicKeyData, publicKeyArray.size());
|
||||
|
||||
QByteArray lowercaseUsername = username.toLower().toUtf8();
|
||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsername.append(connectionToken.toRfc4122()),
|
||||
QByteArray lowercaseUsernameUTF8 = lowerUsername.toUtf8();
|
||||
QByteArray usernameWithToken = QCryptographicHash::hash(lowercaseUsernameUTF8.append(connectionToken.toRfc4122()),
|
||||
QCryptographicHash::Sha256);
|
||||
|
||||
if (rsaPublicKey) {
|
||||
|
@ -422,9 +520,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
|
|||
}
|
||||
} else {
|
||||
if (!senderSockAddr.isNull()) {
|
||||
qDebug() << "Insufficient data to decrypt username signature - denying connection.";
|
||||
sendConnectionDeniedPacket("Insufficient data", senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason::LoginError);
|
||||
qDebug() << "Insufficient data to decrypt username signature - delaying connection.";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -471,10 +567,20 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
|||
return;
|
||||
}
|
||||
|
||||
QString lowerUsername = username.toLower();
|
||||
if (_inFlightPublicKeyRequests.contains(lowerUsername)) {
|
||||
// public-key request for this username is already flight, not rerequesting
|
||||
return;
|
||||
}
|
||||
_inFlightPublicKeyRequests += lowerUsername;
|
||||
|
||||
// even if we have a public key for them right now, request a new one in case it has just changed
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "publicKeyJSONCallback";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "publicKeyJSONErrorCallback";
|
||||
|
||||
|
||||
const QString USER_PUBLIC_KEY_PATH = "api/v1/users/%1/public_key";
|
||||
|
||||
|
@ -485,28 +591,37 @@ void DomainGatekeeper::requestUserPublicKey(const QString& username) {
|
|||
QNetworkAccessManager::GetOperation, callbackParams);
|
||||
}
|
||||
|
||||
QString extractUsernameFromPublicKeyRequest(QNetworkReply& requestReply) {
|
||||
// extract the username from the request url
|
||||
QString username;
|
||||
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
||||
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||
username = usernameRegex.cap(1);
|
||||
}
|
||||
return username.toLower();
|
||||
}
|
||||
|
||||
void DomainGatekeeper::publicKeyJSONCallback(QNetworkReply& requestReply) {
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
QString username = extractUsernameFromPublicKeyRequest(requestReply);
|
||||
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
// figure out which user this is for
|
||||
if (jsonObject["status"].toString() == "success" && !username.isEmpty()) {
|
||||
// pull the public key as a QByteArray from this response
|
||||
const QString JSON_DATA_KEY = "data";
|
||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||
|
||||
const QString PUBLIC_KEY_URL_REGEX_STRING = "api\\/v1\\/users\\/([A-Za-z0-9_\\.]+)\\/public_key";
|
||||
QRegExp usernameRegex(PUBLIC_KEY_URL_REGEX_STRING);
|
||||
|
||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||
QString username = usernameRegex.cap(1);
|
||||
|
||||
qDebug() << "Storing a public key for user" << username;
|
||||
|
||||
// pull the public key as a QByteArray from this response
|
||||
const QString JSON_DATA_KEY = "data";
|
||||
const QString JSON_PUBLIC_KEY_KEY = "public_key";
|
||||
|
||||
_userPublicKeys[username] =
|
||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
}
|
||||
_userPublicKeys[username.toLower()] =
|
||||
QByteArray::fromBase64(jsonObject[JSON_DATA_KEY].toObject()[JSON_PUBLIC_KEY_KEY].toString().toUtf8());
|
||||
}
|
||||
|
||||
_inFlightPublicKeyRequests.remove(username);
|
||||
}
|
||||
|
||||
void DomainGatekeeper::publicKeyJSONErrorCallback(QNetworkReply& requestReply) {
|
||||
qDebug() << "publicKey api call failed:" << requestReply.error();
|
||||
QString username = extractUsernameFromPublicKeyRequest(requestReply);
|
||||
_inFlightPublicKeyRequests.remove(username);
|
||||
}
|
||||
|
||||
void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr& senderSockAddr) {
|
||||
|
@ -519,22 +634,30 @@ void DomainGatekeeper::sendProtocolMismatchConnectionDenial(const HifiSockAddr&
|
|||
}
|
||||
|
||||
void DomainGatekeeper::sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason reasonCode) {
|
||||
DomainHandler::ConnectionRefusedReason reasonCode,
|
||||
QString extraInfo) {
|
||||
// this is an agent and we've decided we won't let them connect - send them a packet to deny connection
|
||||
QByteArray utfString = reason.toUtf8();
|
||||
quint16 payloadSize = utfString.size();
|
||||
QByteArray utfReasonString = reason.toUtf8();
|
||||
quint16 reasonSize = utfReasonString.size();
|
||||
|
||||
QByteArray utfExtraInfo = extraInfo.toUtf8();
|
||||
quint16 extraInfoSize = utfExtraInfo.size();
|
||||
|
||||
// setup the DomainConnectionDenied packet
|
||||
auto connectionDeniedPacket = NLPacket::create(PacketType::DomainConnectionDenied,
|
||||
payloadSize + sizeof(payloadSize) + sizeof(uint8_t));
|
||||
sizeof(uint8_t) + // reasonCode
|
||||
reasonSize + sizeof(reasonSize) +
|
||||
extraInfoSize + sizeof(extraInfoSize));
|
||||
|
||||
// pack in the reason the connection was denied (the client displays this)
|
||||
if (payloadSize > 0) {
|
||||
uint8_t reasonCodeWire = (uint8_t)reasonCode;
|
||||
connectionDeniedPacket->writePrimitive(reasonCodeWire);
|
||||
connectionDeniedPacket->writePrimitive(payloadSize);
|
||||
connectionDeniedPacket->write(utfString);
|
||||
}
|
||||
uint8_t reasonCodeWire = (uint8_t)reasonCode;
|
||||
connectionDeniedPacket->writePrimitive(reasonCodeWire);
|
||||
connectionDeniedPacket->writePrimitive(reasonSize);
|
||||
connectionDeniedPacket->write(utfReasonString);
|
||||
|
||||
// write the extra info as well
|
||||
connectionDeniedPacket->writePrimitive(extraInfoSize);
|
||||
connectionDeniedPacket->write(utfExtraInfo);
|
||||
|
||||
// send the packet off
|
||||
DependencyManager::get<LimitedNodeList>()->sendPacket(std::move(connectionDeniedPacket), senderSockAddr);
|
||||
|
@ -645,3 +768,167 @@ void DomainGatekeeper::processICEPingReplyPacket(QSharedPointer<ReceivedMessage>
|
|||
sendingPeer->activateMatchingOrNewSymmetricSocket(message->getSenderSockAddr());
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getGroupMemberships(const QString& username) {
|
||||
// loop through the groups mentioned on the settings page and ask if this user is in each. The replies
|
||||
// will be received asynchronously and permissions will be updated as the answers come in.
|
||||
|
||||
QJsonObject json;
|
||||
QSet<QString> groupIDSet;
|
||||
foreach (QUuid groupID, _server->_settingsManager.getGroupIDs() + _server->_settingsManager.getBlacklistGroupIDs()) {
|
||||
groupIDSet += groupID.toString().mid(1,36);
|
||||
}
|
||||
|
||||
if (groupIDSet.isEmpty()) {
|
||||
// if no groups are in the permissions settings, don't ask who is in which groups.
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonArray groupIDs = QJsonArray::fromStringList(groupIDSet.toList());
|
||||
json["groups"] = groupIDs;
|
||||
|
||||
|
||||
// if we've already asked, wait for the answer before asking again
|
||||
QString lowerUsername = username.toLower();
|
||||
if (_inFlightGroupMembershipsRequests.contains(lowerUsername)) {
|
||||
// public-key request for this username is already flight, not rerequesting
|
||||
return;
|
||||
}
|
||||
_inFlightGroupMembershipsRequests += lowerUsername;
|
||||
|
||||
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "getIsGroupMemberJSONCallback";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "getIsGroupMemberErrorCallback";
|
||||
|
||||
const QString GET_IS_GROUP_MEMBER_PATH = "api/v1/groups/members/%2";
|
||||
DependencyManager::get<AccountManager>()->sendRequest(GET_IS_GROUP_MEMBER_PATH.arg(username),
|
||||
AccountManagerAuth::Required,
|
||||
QNetworkAccessManager::PostOperation, callbackParams,
|
||||
QJsonDocument(json).toJson());
|
||||
|
||||
}
|
||||
|
||||
QString extractUsernameFromGroupMembershipsReply(QNetworkReply& requestReply) {
|
||||
// extract the username from the request url
|
||||
QString username;
|
||||
const QString GROUP_MEMBERSHIPS_URL_REGEX_STRING = "api\\/v1\\/groups\\/members\\/([A-Za-z0-9_\\.]+)";
|
||||
QRegExp usernameRegex(GROUP_MEMBERSHIPS_URL_REGEX_STRING);
|
||||
if (usernameRegex.indexIn(requestReply.url().toString()) != -1) {
|
||||
username = usernameRegex.cap(1);
|
||||
}
|
||||
return username.toLower();
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getIsGroupMemberJSONCallback(QNetworkReply& requestReply) {
|
||||
// {
|
||||
// "data":{
|
||||
// "username":"sethalves",
|
||||
// "groups":{
|
||||
// "fd55479a-265d-4990-854e-3d04214ad1b0":{
|
||||
// "name":"Blerg Blah",
|
||||
// "rank":{
|
||||
// "name":"admin",
|
||||
// "order":1
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// },
|
||||
// "status":"success"
|
||||
// }
|
||||
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
QJsonObject data = jsonObject["data"].toObject();
|
||||
QJsonObject groups = data["groups"].toObject();
|
||||
QString username = data["username"].toString();
|
||||
_server->_settingsManager.clearGroupMemberships(username);
|
||||
foreach (auto groupID, groups.keys()) {
|
||||
QJsonObject group = groups[groupID].toObject();
|
||||
QJsonObject rank = group["rank"].toObject();
|
||||
QUuid rankID = QUuid(rank["id"].toString());
|
||||
_server->_settingsManager.recordGroupMembership(username, groupID, rankID);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "getIsGroupMember api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
|
||||
_inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply));
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply& requestReply) {
|
||||
qDebug() << "getIsGroupMember api call failed:" << requestReply.error();
|
||||
_inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply));
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getDomainOwnerFriendsList() {
|
||||
JSONCallbackParameters callbackParams;
|
||||
callbackParams.jsonCallbackReceiver = this;
|
||||
callbackParams.jsonCallbackMethod = "getDomainOwnerFriendsListJSONCallback";
|
||||
callbackParams.errorCallbackReceiver = this;
|
||||
callbackParams.errorCallbackMethod = "getDomainOwnerFriendsListErrorCallback";
|
||||
|
||||
const QString GET_FRIENDS_LIST_PATH = "api/v1/user/friends";
|
||||
DependencyManager::get<AccountManager>()->sendRequest(GET_FRIENDS_LIST_PATH, AccountManagerAuth::Required,
|
||||
QNetworkAccessManager::GetOperation, callbackParams, QByteArray(),
|
||||
NULL, QVariantMap());
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply) {
|
||||
// {
|
||||
// status: "success",
|
||||
// data: {
|
||||
// friends: [
|
||||
// "chris",
|
||||
// "freidrica",
|
||||
// "G",
|
||||
// "huffman",
|
||||
// "leo",
|
||||
// "philip",
|
||||
// "ryan",
|
||||
// "sam",
|
||||
// "ZappoMan"
|
||||
// ]
|
||||
// }
|
||||
// }
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
if (jsonObject["status"].toString() == "success") {
|
||||
_domainOwnerFriends.clear();
|
||||
QJsonArray friends = jsonObject["data"].toObject()["friends"].toArray();
|
||||
for (int i = 0; i < friends.size(); i++) {
|
||||
_domainOwnerFriends += friends.at(i).toString();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "getDomainOwnerFriendsList api call returned:" << QJsonDocument(jsonObject).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
}
|
||||
|
||||
void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply) {
|
||||
qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply.error();
|
||||
}
|
||||
|
||||
void DomainGatekeeper::refreshGroupsCache() {
|
||||
// if agents are connected to this domain, refresh our cached information about groups and memberships in such.
|
||||
getDomainOwnerFriendsList();
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
nodeList->eachNode([&](const SharedNodePointer& node) {
|
||||
if (!node->getPermissions().isAssignment) {
|
||||
// this node is an agent
|
||||
const QString& verifiedUserName = node->getPermissions().getVerifiedUserName();
|
||||
if (!verifiedUserName.isEmpty()) {
|
||||
getGroupMemberships(verifiedUserName);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_server->_settingsManager.apiRefreshGroupInformation();
|
||||
|
||||
updateNodePermissions();
|
||||
|
||||
#if WANT_DEBUG
|
||||
_server->_settingsManager.debugDumpGroupsState();
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -51,7 +51,16 @@ public slots:
|
|||
void processICEPeerInformationPacket(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void publicKeyJSONCallback(QNetworkReply& requestReply);
|
||||
|
||||
void publicKeyJSONErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
void getIsGroupMemberJSONCallback(QNetworkReply& requestReply);
|
||||
void getIsGroupMemberErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
void getDomainOwnerFriendsListJSONCallback(QNetworkReply& requestReply);
|
||||
void getDomainOwnerFriendsListErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
void refreshGroupsCache();
|
||||
|
||||
signals:
|
||||
void killNode(SharedNodePointer node);
|
||||
void connectedNode(SharedNodePointer node);
|
||||
|
@ -79,7 +88,8 @@ private:
|
|||
|
||||
void sendConnectionTokenPacket(const QString& username, const HifiSockAddr& senderSockAddr);
|
||||
static void sendConnectionDeniedPacket(const QString& reason, const HifiSockAddr& senderSockAddr,
|
||||
DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown);
|
||||
DomainHandler::ConnectionRefusedReason reasonCode = DomainHandler::ConnectionRefusedReason::Unknown,
|
||||
QString extraInfo = QString());
|
||||
|
||||
void pingPunchForConnectingPeer(const SharedNetworkPeer& peer);
|
||||
|
||||
|
@ -93,6 +103,15 @@ private:
|
|||
|
||||
QHash<QString, QUuid> _connectionTokenHash;
|
||||
QHash<QString, QByteArray> _userPublicKeys;
|
||||
QSet<QString> _inFlightPublicKeyRequests; // keep track of which we've already asked for
|
||||
QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner
|
||||
QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for
|
||||
|
||||
NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress);
|
||||
|
||||
void getGroupMemberships(const QString& username);
|
||||
// void getIsGroupMember(const QString& username, const QUuid groupID);
|
||||
void getDomainOwnerFriendsList();
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -184,10 +184,10 @@ void DomainMetadata::securityChanged(bool send) {
|
|||
QString restriction;
|
||||
|
||||
const auto& settingsManager = static_cast<DomainServer*>(parent())->_settingsManager;
|
||||
bool hasAnonymousAccess =
|
||||
settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).canConnectToDomain;
|
||||
bool hasHifiAccess =
|
||||
settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).canConnectToDomain;
|
||||
bool hasAnonymousAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous).can(
|
||||
NodePermissions::Permission::canConnectToDomain);
|
||||
bool hasHifiAccess = settingsManager.getStandardPermissionsForName(NodePermissions::standardNameLoggedIn).can(
|
||||
NodePermissions::Permission::canConnectToDomain);
|
||||
if (hasAnonymousAccess) {
|
||||
restriction = hasHifiAccess ? RESTRICTION_OPEN : RESTRICTION_ANON;
|
||||
} else if (hasHifiAccess) {
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include <QStandardPaths>
|
||||
#include <QTimer>
|
||||
#include <QUrlQuery>
|
||||
#include <QCommandLineParser>
|
||||
|
||||
#include <AccountManager.h>
|
||||
#include <BuildInfo.h>
|
||||
|
@ -38,13 +39,18 @@
|
|||
#include <UUID.h>
|
||||
#include <LogHandler.h>
|
||||
#include <ServerPathUtils.h>
|
||||
#include <NumericalConstants.h>
|
||||
|
||||
#include "DomainServerNodeData.h"
|
||||
#include "NodeConnectionData.h"
|
||||
|
||||
int const DomainServer::EXIT_CODE_REBOOT = 234923;
|
||||
|
||||
#if USE_STABLE_GLOBAL_SERVICES
|
||||
const QString ICE_SERVER_DEFAULT_HOSTNAME = "ice.highfidelity.com";
|
||||
#else
|
||||
const QString ICE_SERVER_DEFAULT_HOSTNAME = "dev-ice.highfidelity.com";
|
||||
#endif
|
||||
|
||||
DomainServer::DomainServer(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv),
|
||||
|
@ -61,8 +67,11 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
_webAuthenticationStateSet(),
|
||||
_cookieSessionHash(),
|
||||
_automaticNetworkingSetting(),
|
||||
_settingsManager()
|
||||
_settingsManager(),
|
||||
_iceServerAddr(ICE_SERVER_DEFAULT_HOSTNAME),
|
||||
_iceServerPort(ICE_SERVER_DEFAULT_PORT)
|
||||
{
|
||||
parseCommandLine();
|
||||
qInstallMessageHandler(LogHandler::verboseMessageHandler);
|
||||
|
||||
LogUtils::init();
|
||||
|
@ -78,6 +87,14 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
|
||||
qDebug() << "Setting up domain-server";
|
||||
|
||||
qDebug() << "[VERSION] Build sequence:" << qPrintable(applicationVersion());
|
||||
qDebug() << "[VERSION] MODIFIED_ORGANIZATION:" << BuildInfo::MODIFIED_ORGANIZATION;
|
||||
qDebug() << "[VERSION] VERSION:" << BuildInfo::VERSION;
|
||||
qDebug() << "[VERSION] BUILD_BRANCH:" << BuildInfo::BUILD_BRANCH;
|
||||
qDebug() << "[VERSION] BUILD_GLOBAL_SERVICES:" << BuildInfo::BUILD_GLOBAL_SERVICES;
|
||||
qDebug() << "[VERSION] We will be using this default ICE server:" << ICE_SERVER_DEFAULT_HOSTNAME;
|
||||
|
||||
|
||||
// make sure we have a fresh AccountManager instance
|
||||
// (need this since domain-server can restart itself and maintain static variables)
|
||||
DependencyManager::set<AccountManager>();
|
||||
|
@ -106,15 +123,28 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
connect(&_settingsManager, &DomainServerSettingsManager::updateNodePermissions,
|
||||
&_gatekeeper, &DomainGatekeeper::updateNodePermissions);
|
||||
|
||||
setupGroupCacheRefresh();
|
||||
|
||||
// if we were given a certificate/private key or oauth credentials they must succeed
|
||||
if (!(optionallyReadX509KeyAndCertificate() && optionallySetupOAuth())) {
|
||||
return;
|
||||
}
|
||||
|
||||
_settingsManager.apiRefreshGroupInformation();
|
||||
|
||||
setupNodeListAndAssignments();
|
||||
setupAutomaticNetworking();
|
||||
if (!getID().isNull()) {
|
||||
|
||||
if (_type != NonMetaverse) {
|
||||
// if we have a metaverse domain, we'll use an access token for API calls
|
||||
resetAccountManagerAccessToken();
|
||||
|
||||
setupAutomaticNetworking();
|
||||
}
|
||||
|
||||
if (!getID().isNull() && _type != NonMetaverse) {
|
||||
// setup periodic heartbeats to metaverse API
|
||||
setupHeartbeatToMetaverse();
|
||||
|
||||
// send the first heartbeat immediately
|
||||
sendHeartbeatToMetaverse();
|
||||
}
|
||||
|
@ -133,6 +163,53 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
qDebug() << "domain-server is running";
|
||||
}
|
||||
|
||||
void DomainServer::parseCommandLine() {
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription("High Fidelity Domain Server");
|
||||
parser.addHelpOption();
|
||||
|
||||
const QCommandLineOption iceServerAddressOption("i", "ice-server address", "IP:PORT or HOSTNAME:PORT");
|
||||
parser.addOption(iceServerAddressOption);
|
||||
|
||||
const QCommandLineOption domainIDOption("d", "domain-server uuid");
|
||||
parser.addOption(domainIDOption);
|
||||
|
||||
const QCommandLineOption getTempNameOption("get-temp-name", "Request a temporary domain-name");
|
||||
parser.addOption(getTempNameOption);
|
||||
|
||||
const QCommandLineOption masterConfigOption("master-config", "Deprecated config-file option");
|
||||
parser.addOption(masterConfigOption);
|
||||
|
||||
|
||||
if (!parser.parse(QCoreApplication::arguments())) {
|
||||
qWarning() << parser.errorText() << endl;
|
||||
parser.showHelp();
|
||||
Q_UNREACHABLE();
|
||||
}
|
||||
|
||||
if (parser.isSet(iceServerAddressOption)) {
|
||||
// parse the IP and port combination for this target
|
||||
QString hostnamePortString = parser.value(iceServerAddressOption);
|
||||
|
||||
_iceServerAddr = hostnamePortString.left(hostnamePortString.indexOf(':'));
|
||||
_iceServerPort = (quint16) hostnamePortString.mid(hostnamePortString.indexOf(':') + 1).toUInt();
|
||||
if (_iceServerPort == 0) {
|
||||
_iceServerPort = ICE_SERVER_DEFAULT_PORT;
|
||||
}
|
||||
|
||||
if (_iceServerAddr.isEmpty()) {
|
||||
qWarning() << "Could not parse an IP address and port combination from" << hostnamePortString;
|
||||
QMetaObject::invokeMethod(this, "quit", Qt::QueuedConnection);
|
||||
}
|
||||
}
|
||||
|
||||
if (parser.isSet(domainIDOption)) {
|
||||
_overridingDomainID = QUuid(parser.value(domainIDOption));
|
||||
_overrideDomainID = true;
|
||||
qDebug() << "domain-server ID is" << _overridingDomainID;
|
||||
}
|
||||
}
|
||||
|
||||
DomainServer::~DomainServer() {
|
||||
// destroy the LimitedNodeList before the DomainServer QCoreApplication is down
|
||||
DependencyManager::destroy<LimitedNodeList>();
|
||||
|
@ -140,7 +217,7 @@ DomainServer::~DomainServer() {
|
|||
|
||||
void DomainServer::queuedQuit(QString quitMessage, int exitCode) {
|
||||
if (!quitMessage.isEmpty()) {
|
||||
qCritical() << qPrintable(quitMessage);
|
||||
qWarning() << qPrintable(quitMessage);
|
||||
}
|
||||
|
||||
QCoreApplication::exit(exitCode);
|
||||
|
@ -281,7 +358,7 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
|||
|
||||
auto domainObject = jsonObject[DATA_KEY].toObject()[DOMAIN_KEY].toObject();
|
||||
if (!domainObject.isEmpty()) {
|
||||
auto id = domainObject[ID_KEY].toString();
|
||||
auto id = _overrideDomainID ? _overridingDomainID.toString() : domainObject[ID_KEY].toString();
|
||||
auto name = domainObject[NAME_KEY].toString();
|
||||
auto key = domainObject[KEY_KEY].toString();
|
||||
|
||||
|
@ -296,16 +373,22 @@ void DomainServer::handleTempDomainSuccess(QNetworkReply& requestReply) {
|
|||
// store the new ID and auto networking setting on disk
|
||||
_settingsManager.persistToFile();
|
||||
|
||||
// change our domain ID immediately
|
||||
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
||||
|
||||
// store the new token to the account info
|
||||
auto accountManager = DependencyManager::get<AccountManager>();
|
||||
accountManager->setTemporaryDomain(id, key);
|
||||
|
||||
// change our domain ID immediately
|
||||
DependencyManager::get<LimitedNodeList>()->setSessionUUID(QUuid { id });
|
||||
|
||||
// change our type to reflect that we are a temporary domain now
|
||||
_type = MetaverseTemporaryDomain;
|
||||
|
||||
// update our heartbeats to use the correct id
|
||||
setupICEHeartbeatForFullNetworking();
|
||||
setupHeartbeatToMetaverse();
|
||||
|
||||
// if we have a current ICE server address, update it in the API for the new temporary domain
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
} else {
|
||||
qWarning() << "There were problems parsing the API response containing a temporary domain name. Please try again"
|
||||
<< "via domain-server relaunch or from the domain-server settings.";
|
||||
|
@ -348,11 +431,11 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port";
|
||||
|
||||
QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION);
|
||||
unsigned short domainServerPort = (unsigned short) localPortValue.toUInt();
|
||||
int domainServerPort = localPortValue.toInt();
|
||||
|
||||
QVariantMap& settingsMap = _settingsManager.getSettingsMap();
|
||||
|
||||
unsigned short domainServerDTLSPort = 0;
|
||||
int domainServerDTLSPort = INVALID_PORT;
|
||||
|
||||
if (_isUsingDTLS) {
|
||||
domainServerDTLSPort = DEFAULT_DOMAIN_SERVER_DTLS_PORT;
|
||||
|
@ -383,14 +466,30 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
quint16 localHttpsPort = DOMAIN_SERVER_HTTPS_PORT;
|
||||
nodeList->putLocalPortIntoSharedMemory(DOMAIN_SERVER_LOCAL_HTTPS_PORT_SMEM_KEY, this, localHttpsPort);
|
||||
|
||||
|
||||
// set our LimitedNodeList UUID to match the UUID from our config
|
||||
// nodes will currently use this to add resources to data-web that relate to our domain
|
||||
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
|
||||
if (idValueVariant) {
|
||||
nodeList->setSessionUUID(idValueVariant->toString());
|
||||
bool isMetaverseDomain = false;
|
||||
if (_overrideDomainID) {
|
||||
nodeList->setSessionUUID(_overridingDomainID);
|
||||
isMetaverseDomain = true; // assume metaverse domain
|
||||
} else {
|
||||
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
|
||||
const QVariant* idValueVariant = valueForKeyPath(settingsMap, METAVERSE_DOMAIN_ID_KEY_PATH);
|
||||
if (idValueVariant) {
|
||||
nodeList->setSessionUUID(idValueVariant->toString());
|
||||
isMetaverseDomain = true; // if we have an ID, we'll assume we're a metaverse domain
|
||||
} else {
|
||||
nodeList->setSessionUUID(QUuid::createUuid()); // Use random UUID
|
||||
}
|
||||
}
|
||||
|
||||
if (isMetaverseDomain) {
|
||||
// see if we think we're a temp domain (we have an API key) or a full domain
|
||||
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||
if (temporaryDomainKey.isEmpty()) {
|
||||
_type = MetaverseDomain;
|
||||
} else {
|
||||
_type = MetaverseTemporaryDomain;
|
||||
}
|
||||
}
|
||||
|
||||
connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded);
|
||||
|
@ -406,6 +505,7 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
|
||||
// NodeList won't be available to the settings manager when it is created, so call registerListener here
|
||||
packetReceiver.registerListener(PacketType::DomainSettingsRequest, &_settingsManager, "processSettingsRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::NodeKickRequest, &_settingsManager, "processNodeKickRequestPacket");
|
||||
|
||||
// register the gatekeeper for the packets it needs to receive
|
||||
packetReceiver.registerListener(PacketType::DomainConnectRequest, &_gatekeeper, "processConnectRequestPacket");
|
||||
|
@ -419,7 +519,7 @@ void DomainServer::setupNodeListAndAssignments() {
|
|||
// add whatever static assignments that have been parsed to the queue
|
||||
addStaticAssignmentsToQueue();
|
||||
|
||||
// set a custum packetVersionMatch as the verify packet operator for the udt::Socket
|
||||
// set a custom packetVersionMatch as the verify packet operator for the udt::Socket
|
||||
nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch);
|
||||
}
|
||||
|
||||
|
@ -438,8 +538,8 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
|||
if (accessTokenVariant && accessTokenVariant->canConvert(QMetaType::QString)) {
|
||||
accessToken = accessTokenVariant->toString();
|
||||
} else {
|
||||
qDebug() << "A domain-server feature that requires authentication is enabled but no access token is present.";
|
||||
qDebug() << "Set an access token via the web interface, in your user or master config"
|
||||
qWarning() << "No access token is present. Some operations that use the metaverse API will fail.";
|
||||
qDebug() << "Set an access token via the web interface, in your user config"
|
||||
<< "at keypath metaverse.access_token or in your ENV at key DOMAIN_SERVER_ACCESS_TOKEN";
|
||||
|
||||
// clear any existing access token from AccountManager
|
||||
|
@ -449,7 +549,7 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
|||
}
|
||||
} else {
|
||||
qDebug() << "Using access token from DOMAIN_SERVER_ACCESS_TOKEN in env. This overrides any access token present"
|
||||
<< " in the user or master config.";
|
||||
<< " in the user config.";
|
||||
}
|
||||
|
||||
// give this access token to the AccountManager
|
||||
|
@ -471,42 +571,45 @@ bool DomainServer::resetAccountManagerAccessToken() {
|
|||
}
|
||||
|
||||
void DomainServer::setupAutomaticNetworking() {
|
||||
qDebug() << "Updating automatic networking setting in domain-server to" << _automaticNetworkingSetting;
|
||||
|
||||
resetAccountManagerAccessToken();
|
||||
|
||||
_automaticNetworkingSetting =
|
||||
_settingsManager.valueOrDefaultValueForKeyPath(METAVERSE_AUTOMATIC_NETWORKING_KEY_PATH).toString();
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
const QUuid& domainID = getID();
|
||||
qDebug() << "Configuring automatic networking in domain-server as" << _automaticNetworkingSetting;
|
||||
|
||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
setupICEHeartbeatForFullNetworking();
|
||||
}
|
||||
if (_automaticNetworkingSetting != DISABLED_AUTOMATIC_NETWORKING_VALUE) {
|
||||
const QUuid& domainID = getID();
|
||||
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
||||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
if (_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
setupICEHeartbeatForFullNetworking();
|
||||
}
|
||||
|
||||
if (!domainID.isNull()) {
|
||||
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE ||
|
||||
_automaticNetworkingSetting == FULL_AUTOMATIC_NETWORKING_VALUE) {
|
||||
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
|
||||
// send any public socket changes to the data server so nodes can find us at our new IP
|
||||
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
|
||||
this, &DomainServer::performIPAddressUpdate);
|
||||
if (!domainID.isNull()) {
|
||||
qDebug() << "domain-server" << _automaticNetworkingSetting << "automatic networking enabled for ID"
|
||||
<< uuidStringWithoutCurlyBraces(domainID) << "via" << _oauthProviderURL.toString();
|
||||
|
||||
// have the LNL enable public socket updating via STUN
|
||||
nodeList->startSTUNPublicSocketUpdate();
|
||||
if (_automaticNetworkingSetting == IP_ONLY_AUTOMATIC_NETWORKING_VALUE) {
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
|
||||
// send any public socket changes to the data server so nodes can find us at our new IP
|
||||
connect(nodeList.data(), &LimitedNodeList::publicSockAddrChanged,
|
||||
this, &DomainServer::performIPAddressUpdate);
|
||||
|
||||
// have the LNL enable public socket updating via STUN
|
||||
nodeList->startSTUNPublicSocketUpdate();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||
<< "Please add an ID to your config file or via the web interface.";
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Cannot enable domain-server automatic networking without a domain ID."
|
||||
<< "Please add an ID to your config file or via the web interface.";
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void DomainServer::setupHeartbeatToMetaverse() {
|
||||
|
@ -559,12 +662,11 @@ void DomainServer::setupICEHeartbeatForFullNetworking() {
|
|||
|
||||
void DomainServer::updateICEServerAddresses() {
|
||||
if (_iceAddressLookupID == -1) {
|
||||
_iceAddressLookupID = QHostInfo::lookupHost(ICE_SERVER_DEFAULT_HOSTNAME, this, SLOT(handleICEHostInfo(QHostInfo)));
|
||||
_iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo)));
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::parseAssignmentConfigs(QSet<Assignment::Type>& excludedTypes) {
|
||||
// check for configs from the command line, these take precedence
|
||||
const QString ASSIGNMENT_CONFIG_REGEX_STRING = "config-([\\d]+)";
|
||||
QRegExp assignmentConfigRegex(ASSIGNMENT_CONFIG_REGEX_STRING);
|
||||
|
||||
|
@ -1086,8 +1188,8 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
|||
// add the versions
|
||||
static const QString VERSION_KEY = "version";
|
||||
domainObject[VERSION_KEY] = BuildInfo::VERSION;
|
||||
static const QString PROTOCOL_KEY = "protocol";
|
||||
domainObject[PROTOCOL_KEY] = protocolVersionsSignatureBase64();
|
||||
static const QString PROTOCOL_VERSION_KEY = "protocol";
|
||||
domainObject[PROTOCOL_VERSION_KEY] = protocolVersionsSignatureBase64();
|
||||
|
||||
// add networking
|
||||
if (!networkAddress.isEmpty()) {
|
||||
|
@ -1098,12 +1200,11 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
|||
static const QString AUTOMATIC_NETWORKING_KEY = "automatic_networking";
|
||||
domainObject[AUTOMATIC_NETWORKING_KEY] = _automaticNetworkingSetting;
|
||||
|
||||
|
||||
// add access level for anonymous connections
|
||||
// consider the domain to be "restricted" if anonymous connections are disallowed
|
||||
static const QString RESTRICTED_ACCESS_FLAG = "restricted";
|
||||
NodePermissions anonymousPermissions = _settingsManager.getPermissionsForName(NodePermissions::standardNameAnonymous);
|
||||
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.canConnectToDomain;
|
||||
domainObject[RESTRICTED_ACCESS_FLAG] = !anonymousPermissions.can(NodePermissions::Permission::canConnectToDomain);
|
||||
|
||||
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||
if (!temporaryDomainKey.isEmpty()) {
|
||||
|
@ -1121,12 +1222,7 @@ void DomainServer::sendHeartbeatToMetaverse(const QString& networkAddress) {
|
|||
QString domainUpdateJSON = QString("{\"domain\":%1}").arg(QString(QJsonDocument(domainObject).toJson(QJsonDocument::Compact)));
|
||||
|
||||
static const QString DOMAIN_UPDATE = "/api/v1/domains/%1";
|
||||
QString path = DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID()));
|
||||
#if DEV_BUILD || PR_BUILD
|
||||
qDebug() << "Domain metadata sent to" << path;
|
||||
qDebug() << "Domain metadata update:" << domainUpdateJSON;
|
||||
#endif
|
||||
DependencyManager::get<AccountManager>()->sendRequest(path,
|
||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())),
|
||||
AccountManagerAuth::Optional,
|
||||
QNetworkAccessManager::PutOperation,
|
||||
JSONCallbackParameters(nullptr, QString(), this, "handleMetaverseHeartbeatError"),
|
||||
|
@ -1139,87 +1235,123 @@ void DomainServer::handleMetaverseHeartbeatError(QNetworkReply& requestReply) {
|
|||
return;
|
||||
}
|
||||
|
||||
// check if we need to force a new temporary domain name
|
||||
switch (requestReply.error()) {
|
||||
// if we have a temporary domain with a bad token, we get a 401
|
||||
case QNetworkReply::NetworkError::AuthenticationRequiredError: {
|
||||
static const QString DATA_KEY = "data";
|
||||
static const QString TOKEN_KEY = "api_key";
|
||||
// only attempt to grab a new temporary name if we're already a temporary domain server
|
||||
if (_type == MetaverseTemporaryDomain) {
|
||||
// check if we need to force a new temporary domain name
|
||||
switch (requestReply.error()) {
|
||||
// if we have a temporary domain with a bad token, we get a 401
|
||||
case QNetworkReply::NetworkError::AuthenticationRequiredError: {
|
||||
static const QString DATA_KEY = "data";
|
||||
static const QString TOKEN_KEY = "api_key";
|
||||
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
|
||||
QJsonObject jsonObject = QJsonDocument::fromJson(requestReply.readAll()).object();
|
||||
auto tokenFailure = jsonObject[DATA_KEY].toObject()[TOKEN_KEY];
|
||||
|
||||
if (!tokenFailure.isNull()) {
|
||||
qWarning() << "Temporary domain name lacks a valid API key, and is being reset.";
|
||||
if (!tokenFailure.isNull()) {
|
||||
qWarning() << "Temporary domain name lacks a valid API key, and is being reset.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
// if the domain does not (or no longer) exists, we get a 404
|
||||
case QNetworkReply::NetworkError::ContentNotFoundError:
|
||||
qWarning() << "Domain not found, getting a new temporary domain.";
|
||||
break;
|
||||
// otherwise, we erred on something else, and should not force a temporary domain
|
||||
default:
|
||||
return;
|
||||
}
|
||||
// if the domain does not (or no longer) exists, we get a 404
|
||||
case QNetworkReply::NetworkError::ContentNotFoundError:
|
||||
qWarning() << "Domain not found, getting a new temporary domain.";
|
||||
break;
|
||||
// otherwise, we erred on something else, and should not force a temporary domain
|
||||
default:
|
||||
return;
|
||||
}
|
||||
|
||||
// halt heartbeats until we have a token
|
||||
_metaverseHeartbeatTimer->deleteLater();
|
||||
_metaverseHeartbeatTimer = nullptr;
|
||||
// halt heartbeats until we have a token
|
||||
_metaverseHeartbeatTimer->deleteLater();
|
||||
_metaverseHeartbeatTimer = nullptr;
|
||||
|
||||
// give up eventually to avoid flooding traffic
|
||||
static const int MAX_ATTEMPTS = 5;
|
||||
static int attempt = 0;
|
||||
if (++attempt < MAX_ATTEMPTS) {
|
||||
// get a new temporary name and token
|
||||
getTemporaryName(true);
|
||||
} else {
|
||||
qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart.";
|
||||
// give up eventually to avoid flooding traffic
|
||||
static const int MAX_ATTEMPTS = 5;
|
||||
static int attempt = 0;
|
||||
if (++attempt < MAX_ATTEMPTS) {
|
||||
// get a new temporary name and token
|
||||
getTemporaryName(true);
|
||||
} else {
|
||||
qWarning() << "Already attempted too many temporary domain requests. Please set a domain ID manually or restart.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::sendICEServerAddressToMetaverseAPI() {
|
||||
if (!_iceServerSocket.isNull()) {
|
||||
const QString ICE_SERVER_ADDRESS = "ice_server_address";
|
||||
if (_sendICEServerAddressToMetaverseAPIInProgress) {
|
||||
// don't have more than one of these in-flight at a time. set a flag to indicate that once the current one
|
||||
// is done, we need to do update metaverse again.
|
||||
_sendICEServerAddressToMetaverseAPIRedo = true;
|
||||
return;
|
||||
}
|
||||
_sendICEServerAddressToMetaverseAPIInProgress = true;
|
||||
const QString ICE_SERVER_ADDRESS = "ice_server_address";
|
||||
|
||||
QJsonObject domainObject;
|
||||
QJsonObject domainObject;
|
||||
|
||||
if (!_connectedToICEServer || _iceServerSocket.isNull()) {
|
||||
domainObject[ICE_SERVER_ADDRESS] = "0.0.0.0";
|
||||
} else {
|
||||
// we're using full automatic networking and we have a current ice-server socket, use that now
|
||||
domainObject[ICE_SERVER_ADDRESS] = _iceServerSocket.getAddress().toString();
|
||||
}
|
||||
|
||||
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||
if (!temporaryDomainKey.isEmpty()) {
|
||||
// add the temporary domain token
|
||||
const QString KEY_KEY = "api_key";
|
||||
domainObject[KEY_KEY] = temporaryDomainKey;
|
||||
}
|
||||
const auto& temporaryDomainKey = DependencyManager::get<AccountManager>()->getTemporaryDomainKey(getID());
|
||||
if (!temporaryDomainKey.isEmpty()) {
|
||||
// add the temporary domain token
|
||||
const QString KEY_KEY = "api_key";
|
||||
domainObject[KEY_KEY] = temporaryDomainKey;
|
||||
}
|
||||
|
||||
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
|
||||
QString domainUpdateJSON = QString("{\"domain\": %1 }").arg(QString(QJsonDocument(domainObject).toJson()));
|
||||
|
||||
// make sure we hear about failure so we can retry
|
||||
JSONCallbackParameters callbackParameters;
|
||||
callbackParameters.errorCallbackReceiver = this;
|
||||
callbackParameters.errorCallbackMethod = "handleFailedICEServerAddressUpdate";
|
||||
// make sure we hear about failure so we can retry
|
||||
JSONCallbackParameters callbackParameters;
|
||||
callbackParameters.errorCallbackReceiver = this;
|
||||
callbackParameters.errorCallbackMethod = "handleFailedICEServerAddressUpdate";
|
||||
callbackParameters.jsonCallbackReceiver = this;
|
||||
callbackParameters.jsonCallbackMethod = "handleSuccessfulICEServerAddressUpdate";
|
||||
|
||||
qDebug() << "Updating ice-server address in High Fidelity Metaverse API to" << _iceServerSocket.getAddress().toString();
|
||||
static QString repeatedMessage = LogHandler::getInstance().addOnlyOnceMessageRegex
|
||||
("Updating ice-server address in High Fidelity Metaverse API to [^ \n]+");
|
||||
qDebug() << "Updating ice-server address in High Fidelity Metaverse API to"
|
||||
<< (_iceServerSocket.isNull() ? "" : _iceServerSocket.getAddress().toString());
|
||||
|
||||
static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address";
|
||||
static const QString DOMAIN_ICE_ADDRESS_UPDATE = "/api/v1/domains/%1/ice_server_address";
|
||||
|
||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())),
|
||||
AccountManagerAuth::Optional,
|
||||
QNetworkAccessManager::PutOperation,
|
||||
callbackParameters,
|
||||
domainUpdateJSON.toUtf8());
|
||||
DependencyManager::get<AccountManager>()->sendRequest(DOMAIN_ICE_ADDRESS_UPDATE.arg(uuidStringWithoutCurlyBraces(getID())),
|
||||
AccountManagerAuth::Optional,
|
||||
QNetworkAccessManager::PutOperation,
|
||||
callbackParameters,
|
||||
domainUpdateJSON.toUtf8());
|
||||
}
|
||||
|
||||
void DomainServer::handleSuccessfulICEServerAddressUpdate(QNetworkReply& requestReply) {
|
||||
_sendICEServerAddressToMetaverseAPIInProgress = false;
|
||||
if (_sendICEServerAddressToMetaverseAPIRedo) {
|
||||
qDebug() << "ice-server address updated with metaverse, but has since changed. redoing update...";
|
||||
_sendICEServerAddressToMetaverseAPIRedo = false;
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
} else {
|
||||
qDebug() << "ice-server address updated with metaverse.";
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::handleFailedICEServerAddressUpdate(QNetworkReply& requestReply) {
|
||||
const int ICE_SERVER_UPDATE_RETRY_MS = 2 * 1000;
|
||||
_sendICEServerAddressToMetaverseAPIInProgress = false;
|
||||
if (_sendICEServerAddressToMetaverseAPIRedo) {
|
||||
// if we have new data, retry right away, even though the previous attempt didn't go well.
|
||||
_sendICEServerAddressToMetaverseAPIRedo = false;
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
} else {
|
||||
const int ICE_SERVER_UPDATE_RETRY_MS = 2 * 1000;
|
||||
|
||||
qWarning() << "Failed to update ice-server address with High Fidelity Metaverse - error was" << requestReply.errorString();
|
||||
qWarning() << "\tRe-attempting in" << ICE_SERVER_UPDATE_RETRY_MS / 1000 << "seconds";
|
||||
qWarning() << "Failed to update ice-server address with High Fidelity Metaverse - error was"
|
||||
<< requestReply.errorString();
|
||||
qWarning() << "\tRe-attempting in" << ICE_SERVER_UPDATE_RETRY_MS / 1000 << "seconds";
|
||||
|
||||
QTimer::singleShot(ICE_SERVER_UPDATE_RETRY_MS, this, SLOT(sendICEServerAddressToMetaverseAPI()));
|
||||
QTimer::singleShot(ICE_SERVER_UPDATE_RETRY_MS, this, SLOT(sendICEServerAddressToMetaverseAPI()));
|
||||
}
|
||||
}
|
||||
|
||||
void DomainServer::sendHeartbeatToIceServer() {
|
||||
|
@ -1262,6 +1394,7 @@ void DomainServer::sendHeartbeatToIceServer() {
|
|||
|
||||
// reset the connection flag for ICE server
|
||||
_connectedToICEServer = false;
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
|
||||
// randomize our ice-server address (and simultaneously look up any new hostnames for available ice-servers)
|
||||
randomizeICEServerAddress(true);
|
||||
|
@ -1644,7 +1777,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
|
||||
_ephemeralACScripts[scriptAssignment->getUUID()] = formData[0].second;
|
||||
|
||||
// add the script assigment to the assignment queue
|
||||
// add the script assignment to the assignment queue
|
||||
SharedAssignmentPointer sharedScriptedAssignment(scriptAssignment);
|
||||
_unfulfilledAssignments.enqueue(sharedScriptedAssignment);
|
||||
_allAssignments.insert(sharedScriptedAssignment->getUUID(), sharedScriptedAssignment);
|
||||
|
@ -1721,6 +1854,7 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
|
|||
.arg(authorizationCode, oauthRedirectURL().toString(), _oauthClientID, _oauthClientSecret);
|
||||
|
||||
QNetworkRequest tokenRequest(tokenRequestUrl);
|
||||
tokenRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
tokenRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
|
@ -1914,6 +2048,7 @@ QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenR
|
|||
profileURL.setQuery(QString("%1=%2").arg(OAUTH_JSON_ACCESS_TOKEN_KEY, accessToken));
|
||||
|
||||
QNetworkRequest profileRequest(profileURL);
|
||||
profileRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
profileRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT);
|
||||
return NetworkAccessManager::getInstance().get(profileRequest);
|
||||
}
|
||||
|
@ -2132,7 +2267,7 @@ void DomainServer::processPathQueryPacket(QSharedPointer<ReceivedMessage> messag
|
|||
QByteArray viewpointUTF8 = responseViewpoint.toUtf8();
|
||||
|
||||
// prepare a packet for the response
|
||||
auto pathResponsePacket = NLPacket::create(PacketType::DomainServerPathResponse);
|
||||
auto pathResponsePacket = NLPacket::create(PacketType::DomainServerPathResponse, -1, true);
|
||||
|
||||
// check the number of bytes the viewpoint is
|
||||
quint16 numViewpointBytes = viewpointUTF8.size();
|
||||
|
@ -2232,6 +2367,7 @@ void DomainServer::processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage>
|
|||
|
||||
if (!_connectedToICEServer) {
|
||||
_connectedToICEServer = true;
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
qInfo() << "Connected to ice-server at" << _iceServerSocket;
|
||||
}
|
||||
}
|
||||
|
@ -2332,3 +2468,14 @@ void DomainServer::randomizeICEServerAddress(bool shouldTriggerHostLookup) {
|
|||
// immediately send an update to the metaverse API when our ice-server changes
|
||||
sendICEServerAddressToMetaverseAPI();
|
||||
}
|
||||
|
||||
void DomainServer::setupGroupCacheRefresh() {
|
||||
const int REFRESH_GROUPS_INTERVAL_MSECS = 15 * MSECS_PER_SECOND;
|
||||
|
||||
if (!_metaverseGroupCacheTimer) {
|
||||
// setup a timer to refresh this server's cached group details
|
||||
_metaverseGroupCacheTimer = new QTimer { this };
|
||||
connect(_metaverseGroupCacheTimer, &QTimer::timeout, &_gatekeeper, &DomainGatekeeper::refreshGroupsCache);
|
||||
_metaverseGroupCacheTimer->start(REFRESH_GROUPS_INTERVAL_MSECS);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -41,11 +41,17 @@ class DomainServer : public QCoreApplication, public HTTPSRequestHandler {
|
|||
public:
|
||||
DomainServer(int argc, char* argv[]);
|
||||
~DomainServer();
|
||||
|
||||
|
||||
enum DomainType {
|
||||
NonMetaverse,
|
||||
MetaverseDomain,
|
||||
MetaverseTemporaryDomain
|
||||
};
|
||||
|
||||
static int const EXIT_CODE_REBOOT;
|
||||
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false);
|
||||
bool handleHTTPRequest(HTTPConnection* connection, const QUrl& url, bool skipSubHandler = false) override;
|
||||
bool handleHTTPSRequest(HTTPSConnection* connection, const QUrl& url, bool skipSubHandler = false) override;
|
||||
|
||||
public slots:
|
||||
/// Called by NodeList to inform us a node has been added
|
||||
|
@ -64,7 +70,7 @@ public slots:
|
|||
void processNodeDisconnectRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEServerHeartbeatDenialPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processICEServerHeartbeatACK(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
|
||||
private slots:
|
||||
void aboutToQuit();
|
||||
|
||||
|
@ -74,7 +80,7 @@ private slots:
|
|||
void performIPAddressUpdate(const HifiSockAddr& newPublicSockAddr);
|
||||
void sendHeartbeatToMetaverse() { sendHeartbeatToMetaverse(QString()); }
|
||||
void sendHeartbeatToIceServer();
|
||||
|
||||
|
||||
void handleConnectedNode(SharedNodePointer newNode);
|
||||
|
||||
void handleTempDomainSuccess(QNetworkReply& requestReply);
|
||||
|
@ -90,15 +96,17 @@ private slots:
|
|||
void handleICEHostInfo(const QHostInfo& hostInfo);
|
||||
|
||||
void sendICEServerAddressToMetaverseAPI();
|
||||
void handleSuccessfulICEServerAddressUpdate(QNetworkReply& requestReply);
|
||||
void handleFailedICEServerAddressUpdate(QNetworkReply& requestReply);
|
||||
|
||||
signals:
|
||||
void iceServerChanged();
|
||||
void userConnected();
|
||||
void userDisconnected();
|
||||
|
||||
|
||||
private:
|
||||
const QUuid& getID();
|
||||
void parseCommandLine();
|
||||
|
||||
void setupNodeListAndAssignments();
|
||||
bool optionallySetupOAuth();
|
||||
|
@ -136,7 +144,7 @@ private:
|
|||
SharedAssignmentPointer deployableAssignmentForRequest(const Assignment& requestAssignment);
|
||||
void refreshStaticAssignmentAndAddToQueue(SharedAssignmentPointer& assignment);
|
||||
void addStaticAssignmentsToQueue();
|
||||
|
||||
|
||||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
|
@ -151,7 +159,9 @@ private:
|
|||
|
||||
QJsonObject jsonForSocket(const HifiSockAddr& socket);
|
||||
QJsonObject jsonObjectForNode(const SharedNodePointer& node);
|
||||
|
||||
|
||||
void setupGroupCacheRefresh();
|
||||
|
||||
DomainGatekeeper _gatekeeper;
|
||||
|
||||
HTTPManager _httpManager;
|
||||
|
@ -184,6 +194,7 @@ private:
|
|||
DomainMetadata* _metadata { nullptr };
|
||||
QTimer* _iceHeartbeatTimer { nullptr };
|
||||
QTimer* _metaverseHeartbeatTimer { nullptr };
|
||||
QTimer* _metaverseGroupCacheTimer { nullptr };
|
||||
|
||||
QList<QHostAddress> _iceServerAddresses;
|
||||
QSet<QHostAddress> _failedIceServerAddresses;
|
||||
|
@ -192,8 +203,18 @@ private:
|
|||
int _numHeartbeatDenials { 0 };
|
||||
bool _connectedToICEServer { false };
|
||||
|
||||
DomainType _type { DomainType::NonMetaverse };
|
||||
|
||||
friend class DomainGatekeeper;
|
||||
friend class DomainMetadata;
|
||||
|
||||
QString _iceServerAddr;
|
||||
int _iceServerPort;
|
||||
bool _overrideDomainID { false }; // should we override the domain-id from settings?
|
||||
QUuid _overridingDomainID { QUuid() }; // what should we override it with?
|
||||
|
||||
bool _sendICEServerAddressToMetaverseAPIInProgress { false };
|
||||
bool _sendICEServerAddressToMetaverseAPIRedo { false };
|
||||
};
|
||||
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -27,6 +27,12 @@ const QString SETTINGS_PATH = "/settings";
|
|||
const QString SETTINGS_PATH_JSON = SETTINGS_PATH + ".json";
|
||||
const QString AGENT_STANDARD_PERMISSIONS_KEYPATH = "security.standard_permissions";
|
||||
const QString AGENT_PERMISSIONS_KEYPATH = "security.permissions";
|
||||
const QString IP_PERMISSIONS_KEYPATH = "security.ip_permissions";
|
||||
const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions";
|
||||
const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
|
||||
|
||||
using GroupByUUIDKey = QPair<QUuid, QUuid>; // groupID, rankID
|
||||
|
||||
|
||||
class DomainServerSettingsManager : public QObject {
|
||||
Q_OBJECT
|
||||
|
@ -38,23 +44,67 @@ public:
|
|||
void setupConfigMap(const QStringList& argumentList);
|
||||
QVariant valueOrDefaultValueForKeyPath(const QString& keyPath);
|
||||
|
||||
QVariantMap& getUserSettingsMap() { return _configMap.getUserConfig(); }
|
||||
QVariantMap& getSettingsMap() { return _configMap.getMergedConfig(); }
|
||||
QVariantMap& getSettingsMap() { return _configMap.getConfig(); }
|
||||
|
||||
QVariantMap& getDescriptorsMap();
|
||||
|
||||
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name); }
|
||||
bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name); }
|
||||
NodePermissions getStandardPermissionsForName(const QString& name) const;
|
||||
// these give access to anonymous/localhost/logged-in settings from the domain-server settings page
|
||||
bool haveStandardPermissionsForName(const QString& name) const { return _standardAgentPermissions.contains(name, 0); }
|
||||
NodePermissions getStandardPermissionsForName(const NodePermissionsKey& name) const;
|
||||
|
||||
// these give access to permissions for specific user-names from the domain-server settings page
|
||||
bool havePermissionsForName(const QString& name) const { return _agentPermissions.contains(name, 0); }
|
||||
NodePermissions getPermissionsForName(const QString& name) const;
|
||||
QStringList getAllNames() { return _agentPermissions.keys(); }
|
||||
NodePermissions getPermissionsForName(const NodePermissionsKey& key) const { return getPermissionsForName(key.first); }
|
||||
QStringList getAllNames() const;
|
||||
|
||||
// these give access to permissions for specific IPs from the domain-server settings page
|
||||
bool hasPermissionsForIP(const QHostAddress& address) const { return _ipPermissions.contains(address.toString(), 0); }
|
||||
NodePermissions getPermissionsForIP(const QHostAddress& address) const;
|
||||
|
||||
// these give access to permissions for specific groups from the domain-server settings page
|
||||
bool havePermissionsForGroup(const QString& groupName, QUuid rankID) const {
|
||||
return _groupPermissions.contains(groupName, rankID);
|
||||
}
|
||||
NodePermissions getPermissionsForGroup(const QString& groupName, QUuid rankID) const;
|
||||
NodePermissions getPermissionsForGroup(const QUuid& groupID, QUuid rankID) const;
|
||||
|
||||
// these remove permissions from users in certain groups
|
||||
bool haveForbiddensForGroup(const QString& groupName, QUuid rankID) const {
|
||||
return _groupForbiddens.contains(groupName, rankID);
|
||||
}
|
||||
NodePermissions getForbiddensForGroup(const QString& groupName, QUuid rankID) const;
|
||||
NodePermissions getForbiddensForGroup(const QUuid& groupID, QUuid rankID) const;
|
||||
|
||||
QStringList getAllKnownGroupNames();
|
||||
bool setGroupID(const QString& groupName, const QUuid& groupID);
|
||||
GroupRank getGroupRank(QUuid groupID, QUuid rankID) { return _groupRanks[groupID][rankID]; }
|
||||
|
||||
QList<QUuid> getGroupIDs();
|
||||
QList<QUuid> getBlacklistGroupIDs();
|
||||
|
||||
// these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api
|
||||
void clearGroupMemberships(const QString& name) { _groupMembership[name.toLower()].clear(); }
|
||||
void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID);
|
||||
QUuid isGroupMember(const QString& name, const QUuid& groupID); // returns rank or -1 if not a member
|
||||
|
||||
// calls http api to refresh group information
|
||||
void apiRefreshGroupInformation();
|
||||
|
||||
void debugDumpGroupsState();
|
||||
|
||||
signals:
|
||||
void updateNodePermissions();
|
||||
|
||||
public slots:
|
||||
void apiGetGroupIDJSONCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupIDErrorCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupRanksJSONCallback(QNetworkReply& requestReply);
|
||||
void apiGetGroupRanksErrorCallback(QNetworkReply& requestReply);
|
||||
|
||||
private slots:
|
||||
void processSettingsRequestPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void processNodeKickRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer sendingNode);
|
||||
|
||||
private:
|
||||
QStringList _argumentList;
|
||||
|
@ -76,11 +126,37 @@ private:
|
|||
|
||||
void validateDescriptorsMap();
|
||||
|
||||
void packPermissionsForMap(QString mapName, NodePermissionsMap& agentPermissions, QString keyPath);
|
||||
// these cause calls to metaverse's group api
|
||||
void apiGetGroupID(const QString& groupName);
|
||||
void apiGetGroupRanks(const QUuid& groupID);
|
||||
|
||||
void initializeGroupPermissions(NodePermissionsMap& permissionsRows, QString groupName, NodePermissionsPointer perms);
|
||||
void packPermissionsForMap(QString mapName, NodePermissionsMap& permissionsRows, QString keyPath);
|
||||
void packPermissions();
|
||||
void unpackPermissions();
|
||||
NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost
|
||||
bool unpackPermissionsForKeypath(const QString& keyPath, NodePermissionsMap* destinationMapPointer,
|
||||
std::function<void(NodePermissionsPointer)> customUnpacker = {});
|
||||
bool ensurePermissionsForGroupRanks();
|
||||
|
||||
NodePermissionsMap _standardAgentPermissions; // anonymous, logged-in, localhost, friend-of-domain-owner
|
||||
NodePermissionsMap _agentPermissions; // specific account-names
|
||||
|
||||
NodePermissionsMap _ipPermissions; // permissions granted by node IP address
|
||||
|
||||
NodePermissionsMap _groupPermissions; // permissions granted by membership to specific groups
|
||||
NodePermissionsMap _groupForbiddens; // permissions denied due to membership in a specific group
|
||||
// these are like _groupPermissions and _groupForbiddens but with uuids rather than group-names in the keys
|
||||
QHash<GroupByUUIDKey, NodePermissionsPointer> _groupPermissionsByUUID;
|
||||
QHash<GroupByUUIDKey, NodePermissionsPointer> _groupForbiddensByUUID;
|
||||
|
||||
QHash<QString, QUuid> _groupIDs; // keep track of group-name to group-id mappings
|
||||
QHash<QUuid, QString> _groupNames; // keep track of group-id to group-name mappings
|
||||
|
||||
// remember the responses to api/v1/groups/%1/ranks
|
||||
QHash<QUuid, QHash<QUuid, GroupRank>> _groupRanks; // QHash<group-id, QHash<rankID, rank>>
|
||||
|
||||
// keep track of answers to api queries about which users are in which groups
|
||||
QHash<QString, QHash<QUuid, QUuid>> _groupMembership; // QHash<user-name, QHash<group-id, rank-id>>
|
||||
};
|
||||
|
||||
#endif // hifi_DomainServerSettingsManager_h
|
||||
|
|
|
@ -3,7 +3,7 @@ set(TARGET_NAME gvr-interface)
|
|||
if (ANDROID)
|
||||
set(ANDROID_APK_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk-build")
|
||||
set(ANDROID_APK_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk")
|
||||
|
||||
|
||||
set(ANDROID_SDK_ROOT $ENV{ANDROID_HOME})
|
||||
set(ANDROID_APP_DISPLAY_NAME Interface)
|
||||
set(ANDROID_API_LEVEL 19)
|
||||
|
@ -13,10 +13,10 @@ if (ANDROID)
|
|||
set(ANDROID_APK_VERSION_CODE 1)
|
||||
set(ANDROID_APK_FULLSCREEN TRUE)
|
||||
set(ANDROID_DEPLOY_QT_INSTALL "--install")
|
||||
|
||||
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}")
|
||||
|
||||
|
||||
setup_hifi_library(Gui Widgets AndroidExtras)
|
||||
else ()
|
||||
setup_hifi_project(Gui Widgets)
|
||||
|
@ -28,12 +28,12 @@ link_hifi_libraries(shared networking audio-client avatars)
|
|||
|
||||
if (ANDROID)
|
||||
find_package(LibOVR)
|
||||
|
||||
|
||||
if (LIBOVR_FOUND)
|
||||
add_definitions(-DHAVE_LIBOVR)
|
||||
target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES} ${LIBOVR_ANDROID_LIBRARIES} ${TURBOJPEG_LIBRARY})
|
||||
include_directories(SYSTEM ${LIBOVR_INCLUDE_DIRS})
|
||||
|
||||
|
||||
# we need VRLib, so add a project.properties to our apk build folder that says that
|
||||
file(RELATIVE_PATH RELATIVE_VRLIB_PATH ${ANDROID_APK_OUTPUT_DIR} "${LIBOVR_VRLIB_DIR}")
|
||||
file(WRITE "${ANDROID_APK_BUILD_DIR}/project.properties" "android.library.reference.1=${RELATIVE_VRLIB_PATH}")
|
||||
|
@ -50,7 +50,7 @@ if (ANDROID AND HOCKEY_APP_ID)
|
|||
set(ANDROID_ACTIVITY_NAME io.highfidelity.gvrinterface.InterfaceBetaActivity)
|
||||
set(ANDROID_DEPLOY_QT_INSTALL "")
|
||||
set(ANDROID_APK_CUSTOM_NAME "Interface-beta.apk")
|
||||
|
||||
|
||||
# set the ANDROID_APK_VERSION_CODE to the number of git commits
|
||||
execute_process(
|
||||
COMMAND git rev-list --first-parent --count HEAD
|
||||
|
@ -58,16 +58,16 @@ if (ANDROID AND HOCKEY_APP_ID)
|
|||
OUTPUT_VARIABLE GIT_COMMIT_COUNT
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
|
||||
set(ANDROID_APK_VERSION_CODE ${GIT_COMMIT_COUNT})
|
||||
|
||||
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/templates/InterfaceBetaActivity.java.in" "${ANDROID_APK_BUILD_DIR}/src/io/highfidelity/gvrinterface/InterfaceBetaActivity.java")
|
||||
elseif (ANDROID)
|
||||
set(HOCKEY_APP_ENABLED false)
|
||||
endif ()
|
||||
|
||||
if (ANDROID)
|
||||
|
||||
|
||||
set(HIFI_URL_INTENT "<intent-filter>\
|
||||
\n <action android:name='android.intent.action.VIEW' />\
|
||||
\n <category android:name='android.intent.category.DEFAULT' />\
|
||||
|
@ -75,13 +75,11 @@ if (ANDROID)
|
|||
\n <data android:scheme='hifi' />\
|
||||
\n </intent-filter>"
|
||||
)
|
||||
|
||||
|
||||
set(ANDROID_EXTRA_APPLICATION_XML "${HOCKEY_APP_ACTIVITY}")
|
||||
set(ANDROID_EXTRA_ACTIVITY_XML "${HIFI_URL_INTENT}")
|
||||
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/templates/hockeyapp.xml.in" "${ANDROID_APK_BUILD_DIR}/res/values/hockeyapp.xml")
|
||||
qt_create_apk()
|
||||
|
||||
endif (ANDROID)
|
||||
|
||||
copy_dlls_beside_windows_executable()
|
||||
endif (ANDROID)
|
||||
|
|
|
@ -30,7 +30,7 @@ const int PEER_SILENCE_THRESHOLD_MSECS = 5 * 1000;
|
|||
IceServer::IceServer(int argc, char* argv[]) :
|
||||
QCoreApplication(argc, argv),
|
||||
_id(QUuid::createUuid()),
|
||||
_serverSocket(),
|
||||
_serverSocket(0, false),
|
||||
_activePeers()
|
||||
{
|
||||
// start the ice-server socket
|
||||
|
@ -213,6 +213,7 @@ void IceServer::requestDomainPublicKey(const QUuid& domainID) {
|
|||
publicKeyURL.setPath(publicKeyPath);
|
||||
|
||||
QNetworkRequest publicKeyRequest { publicKeyURL };
|
||||
publicKeyRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
publicKeyRequest.setAttribute(QNetworkRequest::User, domainID);
|
||||
|
||||
qDebug() << "Requesting public key for domain with ID" << domainID;
|
||||
|
|
|
@ -39,9 +39,20 @@ else ()
|
|||
list(REMOVE_ITEM INTERFACE_SRCS ${SPEECHRECOGNIZER_CPP})
|
||||
endif ()
|
||||
|
||||
find_package(Qt5 COMPONENTS
|
||||
Gui Multimedia Network OpenGL Qml Quick Script ScriptTools Svg
|
||||
WebChannel WebEngine WebEngineWidgets WebKitWidgets WebSockets)
|
||||
if (ANDROID)
|
||||
set(PLATFORM_QT_COMPONENTS AndroidExtras)
|
||||
set(PLATFORM_QT_LIBRARIES Qt5::AndroidExtras)
|
||||
else ()
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
|
||||
set(PLATFORM_QT_LIBRARIES Qt5::WebEngine Qt5::WebEngineWidgets)
|
||||
endif ()
|
||||
|
||||
find_package(
|
||||
Qt5 COMPONENTS
|
||||
Gui Multimedia Network OpenGL Qml Quick Script Svg
|
||||
${PLATFORM_QT_COMPONENTS}
|
||||
WebChannel WebSockets
|
||||
)
|
||||
|
||||
# grab the ui files in resources/ui
|
||||
file (GLOB_RECURSE QT_UI_FILES ui/*.ui)
|
||||
|
@ -57,6 +68,26 @@ set(INTERFACE_SRCS ${INTERFACE_SRCS} "${QT_UI_HEADERS}" "${QT_RESOURCES}")
|
|||
# set(TS ${TARGET_NAME}_en.ts)
|
||||
# qt5_create_translation_custom(${QM} ${INTERFACE_SRCS} ${QT_UI_FILES} ${TS})
|
||||
|
||||
# setup the android parameters that will help us produce an APK
|
||||
if (ANDROID)
|
||||
set(ANDROID_APK_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk-build")
|
||||
set(ANDROID_APK_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk")
|
||||
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}")
|
||||
|
||||
set(ANDROID_SDK_ROOT $ENV{ANDROID_HOME})
|
||||
set(ANDROID_APP_DISPLAY_NAME Interface)
|
||||
set(ANDROID_API_LEVEL 19)
|
||||
set(ANDROID_APK_PACKAGE io.highfidelity.interface)
|
||||
set(ANDROID_ACTIVITY_NAME io.highfidelity.interface.InterfaceActivity)
|
||||
set(ANDROID_APK_VERSION_NAME "0.1")
|
||||
set(ANDROID_APK_VERSION_CODE 1)
|
||||
set(ANDROID_APK_FULLSCREEN TRUE)
|
||||
set(ANDROID_DEPLOY_QT_INSTALL "--install")
|
||||
|
||||
set(BUILD_SHARED_LIBS ON)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
|
||||
# configure CMake to use a custom Info.plist
|
||||
|
@ -94,17 +125,22 @@ if (APPLE)
|
|||
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM})
|
||||
|
||||
# make sure the output name for the .app bundle is correct
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES OUTPUT_NAME ${INTERFACE_BUNDLE_NAME})
|
||||
elseif(WIN32)
|
||||
# Fix up the rpath so macdeployqt works
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks")
|
||||
elseif (WIN32)
|
||||
# configure an rc file for the chosen icon
|
||||
set(CONFIGURE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/icon/${INTERFACE_ICON_FILENAME}")
|
||||
set(CONFIGURE_ICON_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Icon.rc")
|
||||
configure_file("${HF_CMAKE_DIR}/templates/Icon.rc.in" ${CONFIGURE_ICON_RC_OUTPUT})
|
||||
|
||||
# add an executable that also has the icon itself and the configured rc file as resources
|
||||
add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT})
|
||||
set(APP_FULL_NAME "High Fidelity Interface")
|
||||
set(CONFIGURE_VERSION_INFO_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/VersionInfo.rc")
|
||||
configure_file("${HF_CMAKE_DIR}/templates/VersionInfo.rc.in" ${CONFIGURE_VERSION_INFO_RC_OUTPUT})
|
||||
|
||||
if ( NOT DEV_BUILD )
|
||||
# add an executable that also has the icon itself and the configured rc file as resources
|
||||
add_executable(${TARGET_NAME} WIN32 ${INTERFACE_SRCS} ${QM} ${CONFIGURE_ICON_RC_OUTPUT} ${CONFIGURE_VERSION_INFO_RC_OUTPUT})
|
||||
|
||||
if (NOT DEV_BUILD)
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
|
@ -113,9 +149,12 @@ elseif(WIN32)
|
|||
)
|
||||
endif()
|
||||
|
||||
else()
|
||||
elseif (ANDROID)
|
||||
# on android the Interface target is a library that gets linked/used by the APK shell that qtcreateapk produces
|
||||
add_library(${TARGET_NAME} ${INTERFACE_SRCS} ${QM})
|
||||
else ()
|
||||
add_executable(${TARGET_NAME} ${INTERFACE_SRCS} ${QM})
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/includes")
|
||||
|
||||
|
@ -126,6 +165,7 @@ if (WIN32)
|
|||
add_dependency_external_projects(sdl2)
|
||||
add_dependency_external_projects(OpenVR)
|
||||
add_dependency_external_projects(neuron)
|
||||
add_dependency_external_projects(wasapi)
|
||||
endif()
|
||||
|
||||
# disable /OPT:REF and /OPT:ICF for the Debug builds
|
||||
|
@ -135,21 +175,32 @@ if (WIN32)
|
|||
set_property(TARGET ${TARGET_NAME} APPEND_STRING PROPERTY LINK_FLAGS_DEBUG "/OPT:NOREF /OPT:NOICF")
|
||||
endif()
|
||||
|
||||
if (NOT ANDROID)
|
||||
set(NON_ANDROID_LIBRARIES steamworks-wrapper)
|
||||
endif ()
|
||||
|
||||
# link required hifi libraries
|
||||
link_hifi_libraries(shared octree gpu gl gpu-gl procedural model render
|
||||
recording fbx networking model-networking entities avatars
|
||||
audio audio-client animation script-engine physics
|
||||
render-utils entities-renderer ui auto-updater
|
||||
controllers plugins ui-plugins display-plugins input-plugins steamworks-wrapper)
|
||||
link_hifi_libraries(
|
||||
shared octree gpu gl gpu-gl procedural model render
|
||||
recording fbx networking model-networking entities avatars
|
||||
audio audio-client animation script-engine physics
|
||||
render-utils entities-renderer ui auto-updater
|
||||
controllers plugins
|
||||
ui-plugins display-plugins input-plugins
|
||||
${NON_ANDROID_LIBRARIES}
|
||||
)
|
||||
|
||||
# include the binary directory of render-utils for shader includes
|
||||
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/render-utils")
|
||||
|
||||
#fixme find a way to express faceshift as a plugin
|
||||
target_bullet()
|
||||
target_glew()
|
||||
target_opengl()
|
||||
|
||||
if (NOT ANDROID)
|
||||
target_glew()
|
||||
endif ()
|
||||
|
||||
if (WIN32 OR APPLE)
|
||||
target_faceshift()
|
||||
endif()
|
||||
|
@ -198,16 +249,11 @@ include_directories("${PROJECT_SOURCE_DIR}/src")
|
|||
target_link_libraries(
|
||||
${TARGET_NAME}
|
||||
Qt5::Gui Qt5::Network Qt5::Multimedia Qt5::OpenGL
|
||||
Qt5::Qml Qt5::Quick Qt5::Script Qt5::ScriptTools Qt5::Svg
|
||||
Qt5::WebChannel Qt5::WebEngine Qt5::WebEngineWidgets Qt5::WebKitWidgets
|
||||
Qt5::Qml Qt5::Quick Qt5::Script Qt5::Svg
|
||||
Qt5::WebChannel Qt5::WebEngine
|
||||
${PLATFORM_QT_LIBRARIES}
|
||||
)
|
||||
|
||||
# Issue causes build failure unless we add this directory.
|
||||
# See https://bugreports.qt.io/browse/QTBUG-43351
|
||||
if (WIN32)
|
||||
add_paths_to_fixup_libs(${Qt5_DIR}/../../../plugins/qtwebengine)
|
||||
endif()
|
||||
|
||||
if (UNIX)
|
||||
target_link_libraries(${TARGET_NAME} pthread)
|
||||
endif(UNIX)
|
||||
|
@ -291,3 +337,17 @@ if (WIN32)
|
|||
|
||||
package_libraries_for_deployment()
|
||||
endif()
|
||||
|
||||
if (ANDROID)
|
||||
set(HIFI_URL_INTENT "<intent-filter>\
|
||||
\n <action android:name='android.intent.action.VIEW' />\
|
||||
\n <category android:name='android.intent.category.DEFAULT' />\
|
||||
\n <category android:name='android.intent.category.BROWSABLE' />\
|
||||
\n <data android:scheme='hifi' />\
|
||||
\n </intent-filter>"
|
||||
)
|
||||
|
||||
set(ANDROID_EXTRA_ACTIVITY_XML "${HIFI_URL_INTENT}")
|
||||
|
||||
qt_create_apk()
|
||||
endif ()
|
||||
|
|
25
interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml
Normal file
25
interface/resources/QtWebEngine/UIDelegates/AlertDialog.qml
Normal file
|
@ -0,0 +1,25 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.0
|
||||
|
||||
import "../../qml/dialogs"
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
signal accepted;
|
||||
property var text;
|
||||
|
||||
property var messageDialogBuilder: Component { MessageDialog { } }
|
||||
|
||||
function open() {
|
||||
console.log("prompt text " + text)
|
||||
var dialog = messageDialogBuilder.createObject(desktop, {
|
||||
text: root.text
|
||||
});
|
||||
|
||||
dialog.selected.connect(function(button){
|
||||
accepted();
|
||||
dialog.destroy();
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
import QtQuick 2.4
|
||||
|
||||
import QtQuick.Dialogs 1.1 as OriginalDialogs
|
||||
|
||||
import "../../qml/dialogs"
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
signal accepted;
|
||||
signal rejected;
|
||||
property var text;
|
||||
|
||||
property var messageDialogBuilder: Component { MessageDialog { } }
|
||||
|
||||
function open() {
|
||||
var dialog = messageDialogBuilder.createObject(desktop, {
|
||||
text: root.text,
|
||||
icon: OriginalDialogs.StandardIcon.Question,
|
||||
buttons: OriginalDialogs.StandardButton.Ok | OriginalDialogs.StandardButton.Cancel
|
||||
});
|
||||
|
||||
dialog.selected.connect(function(button){
|
||||
if (button === OriginalDialogs.StandardButton.Ok) {
|
||||
accepted()
|
||||
} else {
|
||||
rejected();
|
||||
}
|
||||
dialog.destroy();
|
||||
});
|
||||
}
|
||||
}
|
39
interface/resources/QtWebEngine/UIDelegates/FilePicker.qml
Normal file
39
interface/resources/QtWebEngine/UIDelegates/FilePicker.qml
Normal file
|
@ -0,0 +1,39 @@
|
|||
import QtQuick 2.4
|
||||
import QtQuick.Dialogs 1.1
|
||||
import QtQuick.Controls 1.4
|
||||
|
||||
import "../../qml/dialogs"
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
signal filesSelected(var fileList);
|
||||
signal rejected();
|
||||
property var text;
|
||||
property url fileUrl;
|
||||
property var fileUrls;
|
||||
property url folder;
|
||||
property var nameFilters;
|
||||
property bool selectExisting;
|
||||
property bool selectFolder;
|
||||
property bool selectMultiple;
|
||||
property string selectedNameFilter;
|
||||
property string title;
|
||||
|
||||
property var fileDialogBuilder: Component { FileDialog { } }
|
||||
|
||||
function open() {
|
||||
var foo = root;
|
||||
var dialog = fileDialogBuilder.createObject(desktop, {
|
||||
});
|
||||
|
||||
dialog.canceled.connect(function(){
|
||||
root.filesSelected([]);
|
||||
dialog.destroy();
|
||||
});
|
||||
|
||||
dialog.selectedFile.connect(function(file){
|
||||
root.filesSelected(fileDialogHelper.urlToList(file));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
68
interface/resources/QtWebEngine/UIDelegates/Menu.qml
Normal file
68
interface/resources/QtWebEngine/UIDelegates/Menu.qml
Normal file
|
@ -0,0 +1,68 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4 as Controls
|
||||
|
||||
import "../../qml/menus"
|
||||
import "../../qml/controls-uit"
|
||||
import "../../qml/styles-uit"
|
||||
|
||||
Item {
|
||||
id: menu
|
||||
HifiConstants { id: hifi }
|
||||
signal done()
|
||||
implicitHeight: column.height
|
||||
implicitWidth: column.width
|
||||
|
||||
Rectangle {
|
||||
id: background
|
||||
anchors {
|
||||
fill: parent
|
||||
margins: -16
|
||||
}
|
||||
radius: hifi.dimensions.borderRadius
|
||||
border.width: hifi.dimensions.borderWidth
|
||||
border.color: hifi.colors.lightGrayText80
|
||||
color: hifi.colors.faintGray80
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: closer
|
||||
width: 8192
|
||||
height: 8192
|
||||
x: -4096
|
||||
y: -4096
|
||||
propagateComposedEvents: true
|
||||
acceptedButtons: "AllButtons"
|
||||
onClicked: {
|
||||
menu.done();
|
||||
mouse.accepted = false;
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
id: column
|
||||
}
|
||||
|
||||
function popup() {
|
||||
var position = Reticle.position;
|
||||
var localPosition = menu.parent.mapFromItem(desktop, position.x, position.y);
|
||||
x = localPosition.x
|
||||
y = localPosition.y
|
||||
console.log("Popup at " + x + " x " + y)
|
||||
var moveChildren = [];
|
||||
for (var i = 0; i < children.length; ++i) {
|
||||
var child = children[i];
|
||||
if (child.objectName !== "MenuItem") {
|
||||
continue;
|
||||
}
|
||||
moveChildren.push(child);
|
||||
}
|
||||
|
||||
for (i = 0; i < moveChildren.length; ++i) {
|
||||
child = moveChildren[i];
|
||||
child.parent = column;
|
||||
child.menu = menu
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
39
interface/resources/QtWebEngine/UIDelegates/MenuItem.qml
Normal file
39
interface/resources/QtWebEngine/UIDelegates/MenuItem.qml
Normal file
|
@ -0,0 +1,39 @@
|
|||
|
||||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4 as Controls
|
||||
|
||||
import "../../qml/controls-uit"
|
||||
import "../../qml/styles-uit"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
objectName: "MenuItem"
|
||||
|
||||
property alias text: label.text
|
||||
property var menu;
|
||||
property var shortcut;
|
||||
signal triggered();
|
||||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
implicitHeight: 2 * label.implicitHeight
|
||||
implicitWidth: 2 * hifi.dimensions.menuPadding.x + label.width
|
||||
|
||||
RalewaySemiBold {
|
||||
id: label
|
||||
size: hifi.fontSizes.rootMenu
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: hifi.dimensions.menuPadding.x
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
color: enabled ? hifi.colors.baseGrayShadow : hifi.colors.baseGrayShadow50
|
||||
enabled: root.enabled
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.triggered();
|
||||
menu.done();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
import QtQuick 2.5
|
||||
|
||||
Item {
|
||||
width: 100
|
||||
height: 20
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
import QtQuick 2.5
|
||||
|
||||
Item {
|
||||
}
|
42
interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml
Normal file
42
interface/resources/QtWebEngine/UIDelegates/PromptDialog.qml
Normal file
|
@ -0,0 +1,42 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 1.4
|
||||
import QtQuick.Layouts 1.0
|
||||
|
||||
import "../../qml/controls-uit"
|
||||
import "../../qml/styles-uit"
|
||||
import "../../qml/dialogs"
|
||||
|
||||
QtObject {
|
||||
id: root
|
||||
signal input(string text);
|
||||
signal accepted;
|
||||
signal rejected;
|
||||
signal closing(var close)
|
||||
|
||||
property var titleWidth;
|
||||
property var text;
|
||||
property var prompt;
|
||||
|
||||
property var inputDialogBuilder: Component { QueryDialog { } }
|
||||
|
||||
function open() {
|
||||
console.log("prompt text " + text)
|
||||
console.log("prompt prompt " + prompt)
|
||||
|
||||
var dialog = inputDialogBuilder.createObject(desktop, {
|
||||
label: root.text,
|
||||
current: root.prompt
|
||||
});
|
||||
|
||||
dialog.selected.connect(function(result){
|
||||
root.input(dialog.result)
|
||||
root.accepted();
|
||||
dialog.destroy();
|
||||
});
|
||||
|
||||
dialog.canceled.connect(function(){
|
||||
root.rejected();
|
||||
dialog.destroy();
|
||||
});
|
||||
}
|
||||
}
|
8
interface/resources/QtWebEngine/UIDelegates/qmldir
Normal file
8
interface/resources/QtWebEngine/UIDelegates/qmldir
Normal file
|
@ -0,0 +1,8 @@
|
|||
module QtWebEngine.UIDelegates
|
||||
AlertDialog 1.0 AlertDialog.qml
|
||||
ConfirmDialog 1.0 ConfirmDialog.qml
|
||||
FilePicker 1.0 FilePicker.qml
|
||||
PromptDialog 1.0 PromptDialog.qml
|
||||
Menu 1.0 Menu.qml
|
||||
MenuItem 1.0 MenuItem.qml
|
||||
MenuSeparator 1.0 MenuSeparator.qml
|
|
@ -1,15 +1,26 @@
|
|||
{
|
||||
"name": "Hydra to Standard",
|
||||
"channels": [
|
||||
{ "from": "Hydra.LY", "filters": "invert", "to": "Standard.LY" },
|
||||
{ "from": "Hydra.LX", "to": "Standard.LX" },
|
||||
{ "from": "Hydra.LY", "to": "Standard.LY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "Hydra.LX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.LX" },
|
||||
{ "from": "Hydra.LT", "to": "Standard.LTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
},
|
||||
{ "from": "Hydra.LT", "to": "Standard.LT" },
|
||||
{ "from": "Hydra.RY", "filters": "invert", "to": "Standard.RY" },
|
||||
{ "from": "Hydra.RX", "to": "Standard.RX" },
|
||||
{ "from": "Hydra.LT", "to": "Standard.LT" },
|
||||
|
||||
{ "from": "Hydra.RY", "to": "Standard.RY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "Hydra.RX", "filters": { "type": "deadZone", "min": 0.05 }, "to": "Standard.RX" },
|
||||
{ "from": "Hydra.RT", "to": "Standard.RTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
|
@ -28,7 +39,6 @@
|
|||
{ "from": [ "Hydra.R1", "Hydra.R3" ], "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": [ "Hydra.R2", "Hydra.R4" ], "to": "Standard.RightSecondaryThumb" },
|
||||
{ "from": [ "Hydra.L2", "Hydra.L4" ], "to": "Standard.LeftSecondaryThumb" },
|
||||
|
||||
{ "from": "Hydra.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Hydra.RightHand", "to": "Standard.RightHand" }
|
||||
]
|
||||
|
|
|
@ -63,6 +63,19 @@
|
|||
["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"]
|
||||
]
|
||||
},
|
||||
"when": "Application.CameraFirstPerson",
|
||||
"to": "Actions.Yaw"
|
||||
},
|
||||
{ "from": { "makeAxis" : [
|
||||
["Keyboard.A", "Keyboard.Left", "Keyboard.TouchpadLeft"],
|
||||
["Keyboard.D", "Keyboard.Right", "Keyboard.TouchpadRight"]
|
||||
]
|
||||
},
|
||||
"when": "Application.CameraThirdPerson",
|
||||
"to": "Actions.Yaw"
|
||||
},
|
||||
{ "from": { "makeAxis" : [ ["Keyboard.A"], ["Keyboard.D"] ] },
|
||||
"when": "Application.CameraFSM",
|
||||
"to": "Actions.Yaw"
|
||||
},
|
||||
|
||||
|
@ -81,9 +94,10 @@
|
|||
{ "from": "Keyboard.Right", "when": "Keyboard.Shift", "to": "Actions.LATERAL_RIGHT" },
|
||||
{ "from": "Keyboard.Down", "when": "Keyboard.Shift", "to": "Actions.PITCH_DOWN" },
|
||||
{ "from": "Keyboard.Up", "when": "Keyboard.Shift", "to": "Actions.PITCH_UP" },
|
||||
|
||||
{ "from": "Keyboard.Up", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||
{ "from": "Keyboard.Down", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||
{ "from": "Keyboard.Up", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||
{ "from": "Keyboard.Up", "when": "Application.CameraThirdPerson", "to": "Actions.LONGITUDINAL_FORWARD" },
|
||||
{ "from": "Keyboard.Down", "when": "Application.CameraFirstPerson", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||
{ "from": "Keyboard.Down", "when": "Application.CameraThirdPerson", "to": "Actions.LONGITUDINAL_BACKWARD" },
|
||||
|
||||
{ "from": "Keyboard.PgDown", "to": "Actions.VERTICAL_DOWN" },
|
||||
{ "from": "Keyboard.PgUp", "to": "Actions.VERTICAL_UP" },
|
||||
|
|
|
@ -1,32 +1,45 @@
|
|||
{
|
||||
"name": "Oculus Touch to Standard",
|
||||
"channels": [
|
||||
{ "from": "OculusTouch.A", "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": "OculusTouch.B", "to": "Standard.RightSecondaryThumb" },
|
||||
{ "from": "OculusTouch.X", "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": "OculusTouch.Y", "to": "Standard.LeftSecondaryThumb" },
|
||||
{ "from": "OculusTouch.A", "to": "Standard.RightPrimaryThumb", "peek": true },
|
||||
{ "from": "OculusTouch.X", "to": "Standard.LeftPrimaryThumb", "peek": true },
|
||||
|
||||
{ "from": "OculusTouch.LY", "filters": "invert", "to": "Standard.LY" },
|
||||
{ "from": "OculusTouch.LX", "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.LT", "to": "Standard.LTClick",
|
||||
{ "from": "OculusTouch.A", "to": "Standard.A" },
|
||||
{ "from": "OculusTouch.B", "to": "Standard.B" },
|
||||
{ "from": "OculusTouch.X", "to": "Standard.X" },
|
||||
{ "from": "OculusTouch.Y", "to": "Standard.Y" },
|
||||
|
||||
{ "from": "OculusTouch.LY", "to": "Standard.LY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.3 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "OculusTouch.LX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.LX" },
|
||||
{ "from": "OculusTouch.LT", "to": "Standard.LTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
},
|
||||
{ "from": "OculusTouch.LT", "to": "Standard.LT" },
|
||||
{ "from": "OculusTouch.LS", "to": "Standard.LS" },
|
||||
{ "from": "OculusTouch.LeftGrip", "to": "Standard.LeftGrip" },
|
||||
{ "from": "OculusTouch.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "OculusTouch.LeftGrip", "filters": { "type": "deadZone", "min": 0.5 }, "to": "Standard.LeftGrip" },
|
||||
{ "from": "OculusTouch.LeftHand", "to": "Standard.LeftHand", "when": [ "Application.InHMD" ] },
|
||||
|
||||
{ "from": "OculusTouch.RY", "filters": "invert", "to": "Standard.RY" },
|
||||
{ "from": "OculusTouch.RX", "to": "Standard.RX" },
|
||||
{ "from": "OculusTouch.RT", "to": "Standard.RTClick",
|
||||
{ "from": "OculusTouch.RY", "to": "Standard.RY",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.3 },
|
||||
"invert"
|
||||
]
|
||||
},
|
||||
{ "from": "OculusTouch.RX", "filters": { "type": "deadZone", "min": 0.3 }, "to": "Standard.RX" },
|
||||
{ "from": "OculusTouch.RT", "to": "Standard.RTClick",
|
||||
"peek": true,
|
||||
"filters": [ { "type": "hysteresis", "min": 0.85, "max": 0.9 } ]
|
||||
},
|
||||
{ "from": "OculusTouch.RT", "to": "Standard.RT" },
|
||||
{ "from": "OculusTouch.RS", "to": "Standard.RS" },
|
||||
{ "from": "OculusTouch.RightGrip", "to": "Standard.RightGrip" },
|
||||
{ "from": "OculusTouch.RightHand", "to": "Standard.RightHand" },
|
||||
{ "from": "OculusTouch.RightGrip", "filters": { "type": "deadZone", "min": 0.5 }, "to": "Standard.RightGrip" },
|
||||
{ "from": "OculusTouch.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] },
|
||||
|
||||
{ "from": "OculusTouch.LeftApplicationMenu", "to": "Standard.Back" },
|
||||
{ "from": "OculusTouch.RightApplicationMenu", "to": "Standard.Start" },
|
||||
|
@ -45,5 +58,3 @@
|
|||
{ "from": "OculusTouch.RightIndexPoint", "to": "Standard.RightIndexPoint" }
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
[
|
||||
{ "type": "deadZone", "min": 0.15 },
|
||||
"constrainToInteger",
|
||||
{ "type": "pulse", "interval": 0.5 },
|
||||
{ "type": "pulse", "interval": 0.25 },
|
||||
{ "type": "scale", "scale": 22.5 }
|
||||
]
|
||||
},
|
||||
|
@ -32,9 +32,6 @@
|
|||
{ "from": "Standard.Back", "to": "Actions.CycleCamera" },
|
||||
{ "from": "Standard.Start", "to": "Actions.ContextMenu" },
|
||||
|
||||
{ "from": [ "Standard.DU", "Standard.DL", "Standard.DR", "Standard.DD" ], "to": "Standard.LeftPrimaryThumb" },
|
||||
{ "from": [ "Standard.A", "Standard.B", "Standard.X", "Standard.Y" ], "to": "Standard.RightPrimaryThumb" },
|
||||
|
||||
{ "from": "Standard.LT", "to": "Actions.LeftHandClick" },
|
||||
{ "from": "Standard.RT", "to": "Actions.RightHandClick" },
|
||||
|
||||
|
|
|
@ -4,8 +4,8 @@
|
|||
{ "from": "Vive.LY", "when": "Vive.LSY", "filters": ["invert"], "to": "Standard.LY" },
|
||||
{ "from": "Vive.LX", "when": "Vive.LSX", "to": "Standard.LX" },
|
||||
{
|
||||
"from": "Vive.LT", "to": "Standard.LT",
|
||||
"filters": [
|
||||
"from": "Vive.LT", "to": "Standard.LT",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 }
|
||||
]
|
||||
},
|
||||
|
@ -18,8 +18,8 @@
|
|||
{ "from": "Vive.RY", "when": "Vive.RSY", "filters": ["invert"], "to": "Standard.RY" },
|
||||
{ "from": "Vive.RX", "when": "Vive.RSX", "to": "Standard.RX" },
|
||||
{
|
||||
"from": "Vive.RT", "to": "Standard.RT",
|
||||
"filters": [
|
||||
"from": "Vive.RT", "to": "Standard.RT",
|
||||
"filters": [
|
||||
{ "type": "deadZone", "min": 0.05 }
|
||||
]
|
||||
},
|
||||
|
@ -34,7 +34,7 @@
|
|||
{ "from": "Vive.RSCenter", "to": "Standard.RightPrimaryThumb" },
|
||||
{ "from": "Vive.RightApplicationMenu", "to": "Standard.RightSecondaryThumb" },
|
||||
|
||||
{ "from": "Vive.LeftHand", "to": "Standard.LeftHand" },
|
||||
{ "from": "Vive.RightHand", "to": "Standard.RightHand" }
|
||||
{ "from": "Vive.LeftHand", "to": "Standard.LeftHand", "when": [ "Application.InHMD" ] },
|
||||
{ "from": "Vive.RightHand", "to": "Standard.RightHand", "when": [ "Application.InHMD" ] }
|
||||
]
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue