Merge branch 'master' into feature/domain-metadata-exporter

This commit is contained in:
Kasen IO 2020-08-18 21:35:51 -04:00
commit c70a041490
155 changed files with 18541 additions and 632 deletions

View file

@ -21,8 +21,8 @@
These dependencies need not be installed manually. They are automatically downloaded on the platforms where they are required. These dependencies need not be installed manually. They are automatically downloaded on the platforms where they are required.
- [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases): 2.83 - [Bullet Physics Engine](https://github.com/bulletphysics/bullet3/releases): 2.83
- [glm](https://glm.g-truc.net/0.9.8/index.html): 0.9.8 - [glm](https://glm.g-truc.net/0.9.8/index.html): 0.9.8
- [Oculus SDK](https://developer.oculus.com/downloads/): 1.11 (Win32) / 0.5 (Mac) - [Oculus SDK](https://developer.oculus.com/downloads/): 1.11 (Windows) / 0.5 (Mac)
- [OpenVR](https://github.com/ValveSoftware/openvr): 1.11.11 (Win32 only) - [OpenVR](https://github.com/ValveSoftware/openvr): 1.11.11 (Windows, Linux)
- [Polyvox](http://www.volumesoffun.com/): 0.2.1 - [Polyvox](http://www.volumesoffun.com/): 0.2.1
- [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/): 0.7.3 - [QuaZip](https://sourceforge.net/projects/quazip/files/quazip/): 0.7.3
- [SDL2](https://www.libsdl.org/download-2.0.php): 2.0.3 - [SDL2](https://www.libsdl.org/download-2.0.php): 2.0.3

View file

@ -1,6 +1,6 @@
# Build Linux # Build Linux
*Last Updated on January 20, 2020* *Last Updated on April 11, 2020*
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file. Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only Linux specific instructions are found in this file.

View file

@ -1,6 +1,6 @@
# Build OSX # Build OSX
*Last Updated on April 30, 2019* *Last Updated on July 13, 2020*
Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only macOS specific instructions are found in this document. Please read the [general build guide](BUILD.md) for information on dependencies required for all platforms. Only macOS specific instructions are found in this document.
@ -8,24 +8,29 @@ Please read the [general build guide](BUILD.md) for information on dependencies
[Homebrew](https://brew.sh/) is an excellent package manager for macOS. It makes install of some Vircadia dependencies very simple. [Homebrew](https://brew.sh/) is an excellent package manager for macOS. It makes install of some Vircadia dependencies very simple.
brew install cmake openssl brew install cmake openssl npm
### Python 3 ### Python 3
Download an install Python 3.6.6 or higher from [here](https://www.python.org/downloads/). Download an install Python 3.6.6 or higher from [here](https://www.python.org/downloads/).
Execute the `Update Shell Profile.command` script that is provided with the installer. Execute the `Update Shell Profile.command` script that is provided with the installer.
### OSX SDK
You will need the OSX SDK for building. The easiest way to get this is to install Xcode from the App Store.
### OpenSSL ### OpenSSL
Assuming you've installed OpenSSL using the homebrew instructions above, you'll need to set OPENSSL_ROOT_DIR 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: For OpenSSL installed via homebrew, set OPENSSL_ROOT_DIR via
`export OPENSSL_ROOT_DIR=/usr/local/opt/openssl`
or by appending `-DOPENSSL_ROOT_DIR=/usr/local/opt/openssl` to `cmake`
export OPENSSL_ROOT_DIR=/usr/local/Cellar/openssl/1.0.2l
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.
### Xcode ### Xcode
If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. If Xcode is your editor of choice, you can ask CMake to generate Xcode project files instead of Unix Makefiles. You will need to select the Xcode installation in the terminal first if you have not done so already.
sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer
cmake .. -G Xcode cmake .. -G Xcode
@ -34,3 +39,7 @@ If `cmake` complains about Python 3 being missing, you may need to update your C
After running cmake, you will have the make files or Xcode project file necessary to build all of the components. Open the hifi.xcodeproj file, choose ALL_BUILD from the Product > Scheme menu (or target drop down), and click Run. After running cmake, you will have the make files or Xcode project file necessary to build all of the components. Open the hifi.xcodeproj file, choose ALL_BUILD from the Product > Scheme menu (or target drop down), and click Run.
If the build completes successfully, you will have built targets for all components located in the `build/${target_name}/Debug` directories. If the build completes successfully, you will have built targets for all components located in the `build/${target_name}/Debug` directories.
### make
If you build with make rather than Xcode, you can append `-j4`for assigning more threads. The number indicates the number of threads, e.g. 4.

View file

@ -9,7 +9,7 @@ This variable is set by the `project(hifi)` command in `CMakeLists.txt` to `C:/P
### Packaging ### Packaging
To produce an installer, run the `package` target. To produce an installer, run the `package` target. However you will want to follow the steps specific to your platform below.
#### Windows #### Windows
@ -62,7 +62,6 @@ To produce an executable installer on Windows, the following are required:
1. Perform a clean cmake from a new terminal. 1. Perform a clean cmake from a new terminal.
1. Open the `vircadia.sln` solution with elevated (administrator) permissions on Visual Studio and select the **Release** configuration. 1. Open the `vircadia.sln` solution with elevated (administrator) permissions on Visual Studio and select the **Release** configuration.
1. Build the solution. 1. Build the solution.
1. Build CMakeTargets->INSTALL
1. Build `packaged-server-console-npm-install` (found under **hidden/Server Console**) 1. Build `packaged-server-console-npm-install` (found under **hidden/Server Console**)
1. Build `packaged-server-console` (found under **Server Console**) 1. Build `packaged-server-console` (found under **Server Console**)
This will add 2 folders to `build\server-console\` - This will add 2 folders to `build\server-console\` -

View file

@ -615,6 +615,10 @@ void Agent::setIsAvatar(bool isAvatar) {
delete _avatarQueryTimer; delete _avatarQueryTimer;
_avatarQueryTimer = nullptr; _avatarQueryTimer = nullptr;
// Clear the skeleton model so that if agent is set to an avatar again the skeleton model is (re)loaded.
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
scriptedAvatar->setSkeletonModelURL(QUrl());
// The avatar mixer never times out a connection (e.g., based on identity or data packets) // 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 // 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 // when we stop sending identity, but then get woken up again by the mixer itself, which sends

View file

@ -23,6 +23,7 @@
#include <AvatarLogging.h> #include <AvatarLogging.h>
#include <EntityItem.h> #include <EntityItem.h>
#include <EntityItemProperties.h> #include <EntityItemProperties.h>
#include <NetworkingConstants.h>
ScriptableAvatar::ScriptableAvatar() { ScriptableAvatar::ScriptableAvatar() {
@ -221,7 +222,7 @@ void ScriptableAvatar::updateJointMappings() {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL); QNetworkRequest networkRequest = QNetworkRequest(_skeletonModelURL);
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
DependencyManager::get<ResourceRequestObserver>()->update( DependencyManager::get<ResourceRequestObserver>()->update(
_skeletonModelURL, -1, "AvatarData::updateJointMappings"); _skeletonModelURL, -1, "AvatarData::updateJointMappings");
QNetworkReply* networkReply = networkAccessManager.get(networkRequest); QNetworkReply* networkReply = networkAccessManager.get(networkRequest);

View file

@ -370,16 +370,18 @@ void EntityServer::entityFilterAdded(EntityItemID id, bool success) {
void EntityServer::nodeAdded(SharedNodePointer node) { void EntityServer::nodeAdded(SharedNodePointer node) {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
tree->knowAvatarID(node->getUUID()); if (tree) {
tree->knowAvatarID(node->getUUID());
}
OctreeServer::nodeAdded(node); OctreeServer::nodeAdded(node);
} }
void EntityServer::nodeKilled(SharedNodePointer node) { void EntityServer::nodeKilled(SharedNodePointer node) {
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree); EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
tree->withWriteLock([&] { if (tree) {
tree->deleteDescendantsOfAvatar(node->getUUID()); tree->deleteDescendantsOfAvatar(node->getUUID());
}); tree->forgetAvatarID(node->getUUID());
tree->forgetAvatarID(node->getUUID()); }
OctreeServer::nodeKilled(node); OctreeServer::nodeKilled(node);
} }

View file

@ -1,4 +1,4 @@
Source: hifi-client-deps Source: hifi-client-deps
Version: 0.1 Version: 0.1
Description: Collected dependencies for High Fidelity applications Description: Collected dependencies for High Fidelity applications
Build-Depends: hifi-deps, aristo (windows), glslang, liblo (windows), nlohmann-json, openvr (windows), quazip (!android), sdl2 (!android), spirv-cross (!android), spirv-tools (!android), sranipal (windows), vulkanmemoryallocator Build-Depends: hifi-deps, aristo (windows), glslang, liblo (windows), nlohmann-json, openvr (linux|windows), quazip (!android), sdl2 (!android), spirv-cross (!android), spirv-tools (!android), sranipal (windows), vulkanmemoryallocator

View file

@ -11,15 +11,23 @@ vcpkg_from_github(
set(VCPKG_LIBRARY_LINKAGE dynamic) set(VCPKG_LIBRARY_LINKAGE dynamic)
if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64") if(VCPKG_TARGET_ARCHITECTURE STREQUAL "x64")
set(ARCH_PATH "win64") if(WIN32)
set(ARCH_PATH "win64")
else()
set(ARCH_PATH "linux64")
endif()
elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86") elseif(VCPKG_TARGET_ARCHITECTURE STREQUAL "x86")
set(ARCH_PATH "win32") if(WIN32)
set(ARCH_PATH "win32")
else()
set(ARCH_PATH "linux32")
endif()
else() else()
message(FATAL_ERROR "Package only supports x64 and x86 windows.") message(FATAL_ERROR "Package only supports x64 and x86 Windows and Linux.")
endif() endif()
if(VCPKG_CMAKE_SYSTEM_NAME) if(VCPKG_CMAKE_SYSTEM_NAME AND NOT (VCPKG_CMAKE_SYSTEM_NAME STREQUAL "Linux"))
message(FATAL_ERROR "Package only supports windows desktop.") message(FATAL_ERROR "Package only supports Windows or Linux desktop.")
endif() endif()
file(MAKE_DIRECTORY file(MAKE_DIRECTORY
@ -28,18 +36,35 @@ file(MAKE_DIRECTORY
${CURRENT_PACKAGES_DIR}/debug/lib ${CURRENT_PACKAGES_DIR}/debug/lib
${CURRENT_PACKAGES_DIR}/debug/bin ${CURRENT_PACKAGES_DIR}/debug/bin
) )
file(COPY ${SOURCE_PATH}/lib/${ARCH_PATH}/openvr_api.lib DESTINATION ${CURRENT_PACKAGES_DIR}/lib)
file(COPY ${SOURCE_PATH}/lib/${ARCH_PATH}/openvr_api.lib DESTINATION ${CURRENT_PACKAGES_DIR}/debug/lib) if(WIN32)
file(COPY file(COPY ${SOURCE_PATH}/lib/${ARCH_PATH}/openvr_api.lib DESTINATION ${CURRENT_PACKAGES_DIR}/lib)
${SOURCE_PATH}/bin/${ARCH_PATH}/openvr_api.dll file(COPY ${SOURCE_PATH}/lib/${ARCH_PATH}/openvr_api.lib DESTINATION ${CURRENT_PACKAGES_DIR}/debug/lib)
${SOURCE_PATH}/bin/${ARCH_PATH}/openvr_api.pdb file(COPY
DESTINATION ${CURRENT_PACKAGES_DIR}/bin ${SOURCE_PATH}/bin/${ARCH_PATH}/openvr_api.dll
) ${SOURCE_PATH}/bin/${ARCH_PATH}/openvr_api.pdb
file(COPY DESTINATION ${CURRENT_PACKAGES_DIR}/bin
${SOURCE_PATH}/bin/${ARCH_PATH}/openvr_api.dll )
${SOURCE_PATH}/bin/${ARCH_PATH}/openvr_api.pdb file(COPY
DESTINATION ${CURRENT_PACKAGES_DIR}/debug/bin ${SOURCE_PATH}/bin/${ARCH_PATH}/openvr_api.dll
) ${SOURCE_PATH}/bin/${ARCH_PATH}/openvr_api.pdb
DESTINATION ${CURRENT_PACKAGES_DIR}/debug/bin
)
else()
file(COPY ${SOURCE_PATH}/lib/${ARCH_PATH}/libopenvr_api.so DESTINATION ${CURRENT_PACKAGES_DIR}/lib)
file(COPY ${SOURCE_PATH}/lib/${ARCH_PATH}/libopenvr_api.so DESTINATION ${CURRENT_PACKAGES_DIR}/debug/lib)
file(COPY
${SOURCE_PATH}/bin/${ARCH_PATH}/libopenvr_api.so
${SOURCE_PATH}/bin/${ARCH_PATH}/libopenvr_api.so.dbg
DESTINATION ${CURRENT_PACKAGES_DIR}/bin
)
file(COPY
${SOURCE_PATH}/bin/${ARCH_PATH}/libopenvr_api.so
${SOURCE_PATH}/bin/${ARCH_PATH}/libopenvr_api.so.dbg
DESTINATION ${CURRENT_PACKAGES_DIR}/debug/bin
)
endif()
file(COPY ${SOURCE_PATH}/headers DESTINATION ${CURRENT_PACKAGES_DIR}) file(COPY ${SOURCE_PATH}/headers DESTINATION ${CURRENT_PACKAGES_DIR})
file(RENAME ${CURRENT_PACKAGES_DIR}/headers ${CURRENT_PACKAGES_DIR}/include) file(RENAME ${CURRENT_PACKAGES_DIR}/headers ${CURRENT_PACKAGES_DIR}/include)

View file

@ -7,9 +7,9 @@ file(READ "${VCPKG_ROOT_DIR}/_env/EXTERNAL_BUILD_ASSETS.txt" EXTERNAL_BUILD_ASSE
if (WIN32) if (WIN32)
vcpkg_download_distfile( vcpkg_download_distfile(
SRANIPAL_SOURCE_ARCHIVE SRANIPAL_SOURCE_ARCHIVE
URLS "${EXTERNAL_BUILD_ASSETS}/seth/sranipal-1.1.0.1-windows.zip" URLS "${EXTERNAL_BUILD_ASSETS}/seth/sranipal-1.1.0.1-2-windows.zip"
SHA512 b09ce012abe4e3c71e8e69626bdd7823ff6576601a821ab365275f2764406a3e5f7b65fcf2eb1d0962eff31eb5958a148b00901f67c229dc6ace56eb5e6c9e1b SHA512 f1f68f6beef52ae5e034bc3f44932ae0800ee187b75d80e76ae7b17b8ddd7bc54c039ce5594d231035e3caf3a61fed36f38621a860b4fb20170cb0176d9c28f0
FILENAME sranipal-1.1.0.1-windows.zip FILENAME sranipal-1.1.0.1-2-windows.zip
) )
vcpkg_extract_source_archive(${SRANIPAL_SOURCE_ARCHIVE}) vcpkg_extract_source_archive(${SRANIPAL_SOURCE_ARCHIVE})

View file

@ -871,7 +871,7 @@ Function PostInstallOptionsPage
Pop $LaunchConsoleNowCheckbox Pop $LaunchConsoleNowCheckbox
; set the checkbox state depending on what is present in the registry ; set the checkbox state depending on what is present in the registry
!insertmacro SetInstallOption $LaunchConsoleNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_CHECKED} !insertmacro SetInstallOption $LaunchConsoleNowCheckbox @SERVER_LAUNCH_NOW_REG_KEY@ ${BST_UNCHECKED}
${StrContains} $substringResult "/forceNoLaunchServer" $CMDLINE ${StrContains} $substringResult "/forceNoLaunchServer" $CMDLINE
${IfNot} $substringResult == "" ${IfNot} $substringResult == ""
${NSD_SetState} $LaunchConsoleNowCheckbox ${BST_UNCHECKED} ${NSD_SetState} $LaunchConsoleNowCheckbox ${BST_UNCHECKED}
@ -887,7 +887,7 @@ Function PostInstallOptionsPage
IntOp $CurrentOffset $CurrentOffset + 15 IntOp $CurrentOffset $CurrentOffset + 15
; set the checkbox state depending on what is present in the registry ; set the checkbox state depending on what is present in the registry
!insertmacro SetInstallOption $ConsoleStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_CHECKED} !insertmacro SetInstallOption $ConsoleStartupCheckbox @CONSOLE_STARTUP_REG_KEY@ ${BST_UNCHECKED}
${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)" ${NSD_CreateCheckbox} 0 $CurrentOffset$OffsetUnits 100% 10u "&Perform a clean install (Delete older settings and content)"
Pop $CleanInstallCheckbox Pop $CleanInstallCheckbox
@ -940,8 +940,8 @@ Function ReadInstallTypes
StrCpy $Express "1" StrCpy $Express "1"
StrCpy $DesktopClientState ${BST_CHECKED} StrCpy $DesktopClientState ${BST_CHECKED}
StrCpy $ConsoleStartupState ${BST_CHECKED} StrCpy $ConsoleStartupState ${BST_UNCHECKED}
StrCpy $LaunchConsoleNowState ${BST_CHECKED} StrCpy $LaunchConsoleNowState ${BST_UNCHECKED}
StrCpy $LaunchClientNowState ${BST_CHECKED} StrCpy $LaunchClientNowState ${BST_CHECKED}
StrCpy $CleanInstallState ${BST_UNCHECKED} StrCpy $CleanInstallState ${BST_UNCHECKED}
StrCpy $DesktopConsoleState ${BST_UNCHECKED} StrCpy $DesktopConsoleState ${BST_UNCHECKED}

View file

@ -3,7 +3,7 @@
"settings": [ "settings": [
{ {
"name": "metaverse", "name": "metaverse",
"label": "Metaverse / Networking", "label": "Networking / Metaverse",
"settings": [ "settings": [
{ {
"name": "access_token", "name": "access_token",
@ -73,6 +73,39 @@
} }
] ]
}, },
{
"name": "authentication",
"label": "Networking / WordPress OAuth2",
"settings": [
{
"name": "enable_oauth2",
"label": "Enable OAuth2 Authentication",
"help": "Allow a WordPress-based (miniOrange) OAuth2 service to assign users to groups based on their role with the service.",
"default": false,
"type": "checkbox",
"advanced": true
},
{
"name": "oauth2_url_path",
"label": "Authentication URL",
"help": "The URL that the Interface will use to login via OAuth2.",
"advanced": true
},
{
"name": "wordpress_url_base",
"label": "WordPress API URL Base",
"help": "The URL base that the domain server will use to make WordPress API calls. Typically \"https://oursite.com/wp-json/\". However, if using non-pretty permalinks or otherwise get a 404 error then try \"https://oursite.com/?rest_route=/\".",
"advanced": true
},
{
"name": "plugin_client_id",
"label": "WordPress Plugin Client ID",
"help": "This is the client ID from the WordPress plugin configuration.",
"advanced": true,
"backup": false
}
]
},
{ {
"label": "Monitoring", "label": "Monitoring",
"name": "monitoring", "name": "monitoring",
@ -271,7 +304,7 @@
"name": "standard_permissions", "name": "standard_permissions",
"type": "table", "type": "table",
"label": "Domain-Wide User Permissions", "label": "Domain-Wide User Permissions",
"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 &ldquo;locked&rdquo; 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>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;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><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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&rsquo;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 &ldquo;locked&rdquo; 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>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;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><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</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&rsquo;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", "caption": "Standard Permissions",
"can_add_new_rows": false, "can_add_new_rows": false,
"groups": [ "groups": [
@ -280,7 +313,7 @@
"span": 1 "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 &ldquo;locked&rdquo; 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>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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&rsquo;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>", "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 &ldquo;locked&rdquo; 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>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</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&rsquo;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": 11 "span": 11
} }
], ],
@ -361,7 +394,7 @@
}, },
{ {
"name": "id_can_get_and_set_private_user_data", "name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data", "label": "Get and Set Private User Data",
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": false "default": false
@ -405,6 +438,7 @@
"name": "group_permissions", "name": "group_permissions",
"type": "table", "type": "table",
"caption": "Permissions for Users in Groups", "caption": "Permissions for Users in Groups",
"help": "For groups that are provided from WordPress you need to denote them by putting an \"@\" symbol in front of each item, e.g., \"@silver\".",
"categorize_by_key": "permissions_id", "categorize_by_key": "permissions_id",
"can_add_new_categories": true, "can_add_new_categories": true,
"can_add_new_rows": false, "can_add_new_rows": false,
@ -416,7 +450,7 @@
"span": 1 "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 &ldquo;locked&rdquo; 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>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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&rsquo;t have their own row in the per-account section, below.</p>'>?</a>", "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 &ldquo;locked&rdquo; 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>Rez Temporary</strong><br />Sets whether users in specific groups can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</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&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 11 "span": 11
} }
], ],
@ -522,7 +556,7 @@
}, },
{ {
"name": "id_can_get_and_set_private_user_data", "name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data", "label": "Get and Set Private User Data",
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": false "default": false
@ -533,6 +567,7 @@
"name": "group_forbiddens", "name": "group_forbiddens",
"type": "table", "type": "table",
"caption": "Permissions Denied to Users in Groups", "caption": "Permissions Denied to Users in Groups",
"help": "For groups that are provided from WordPress you need to denote them by putting an \"@\" symbol in front of each item, e.g., \"@silver\".",
"categorize_by_key": "permissions_id", "categorize_by_key": "permissions_id",
"can_add_new_categories": true, "can_add_new_categories": true,
"can_add_new_rows": false, "can_add_new_rows": false,
@ -544,7 +579,7 @@
"span": 1 "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 &ldquo;locked&rdquo; 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>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;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><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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&rsquo;t have their own row in the per-account section, below.</p>'>?</a>", "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 &ldquo;locked&rdquo; 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>Rez Certified</strong><br />Sets whether a users in specific groups can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users in specific groups can make changes to the domain&rsquo;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><li><strong>Replace Content</strong><br>Sets whether users in specific groups can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property</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&rsquo;t have their own row in the per-account section, below.</p>'>?</a>",
"span": 11 "span": 11
} }
], ],
@ -647,7 +682,7 @@
}, },
{ {
"name": "id_can_get_and_set_private_user_data", "name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data", "label": "Get and Set Private User Data",
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": false "default": false
@ -665,7 +700,7 @@
"span": 1 "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 &ldquo;locked&rdquo; 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>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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>", "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 &ldquo;locked&rdquo; 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>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether a user can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether a user can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether a user can make changes to the domain&rsquo;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><li><strong>Replace Content</strong><br>Sets whether a user can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</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": 11 "span": 11
} }
], ],
@ -746,7 +781,7 @@
}, },
{ {
"name": "id_can_get_and_set_private_user_data", "name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data", "label": "Get and Set Private User Data",
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": false "default": false
@ -764,7 +799,7 @@
"span": 1 "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 &ldquo;locked&rdquo; 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>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users from specific IPs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users from specific IPs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain&rsquo;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><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</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>", "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 &ldquo;locked&rdquo; 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>Rez Temporary</strong><br />Sets whether a user can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users from specific IPs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users from specific IPs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users from specific IPs can make changes to the domain&rsquo;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><li><strong>Replace Content</strong><br>Sets whether users from specific IPs can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</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": 11 "span": 11
} }
], ],
@ -845,7 +880,7 @@
}, },
{ {
"name": "id_can_get_and_set_private_user_data", "name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data", "label": "Get and Set Private User Data",
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": false "default": false
@ -863,7 +898,7 @@
"span": 1 "span": 1
}, },
{ {
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific MACs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific MACs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>", "label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide MAC Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific MACs can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific MACs can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific MACs can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific MACs can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific MACs can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific MACs can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific MACs can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific MACs can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific MACs can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific MAC will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). MAC address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 11 "span": 11
} }
], ],
@ -944,7 +979,7 @@
}, },
{ {
"name": "id_can_get_and_set_private_user_data", "name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data", "label": "Get and Set Private User Data",
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": false "default": false
@ -962,7 +997,7 @@
"span": 1 "span": 1
}, },
{ {
"label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li><li><strong>Can Get and Set Private User Data</strong><br>Sets whether a user can get and set the Private User Data entity property</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>", "label": "Permissions <a data-toggle='tooltip' data-html='true' title='<p><strong>Domain-Wide Machine Fingerprint Permissions</strong></p><ul><li><strong>Connect</strong><br />Sets whether users with specific Machine Fingerprints can connect to the domain.</li><li><strong>Lock / Unlock</strong><br />Sets whether users from specific Machine Fingerprints can change the &ldquo;locked&rdquo; property of an entity (either from on to off or off to on).</li><li><strong>Rez</strong><br />Sets whether users with specific Machine Fingerprints can create new entities.</li><li><strong>Rez Temporary</strong><br />Sets whether users with specific Machine Fingerprints can create new entities with a finite lifetime.</li><li><strong>Rez Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities.</li><li><strong>Rez Temporary Certified</strong><br />Sets whether users with specific Machine Fingerprints can create new certified entities with a finite lifetime.</li><li><strong>Write Assets</strong><br />Sets whether users with specific Machine Fingerprints can make changes to the domain&rsquo;s asset-server assets.</li><li><strong>Ignore Max Capacity</strong><br />Sets whether users with specific Machine Fingerprints can connect even if the domain has reached or exceeded its maximum allowed agents.</li><li><strong>Replace Content</strong><br>Sets whether users with specific Machine Fingerprints can replace entire content sets by wiping existing domain content.</li><li><strong>Get and Set Private User Data</strong><br>Sets whether a user can get and set the privateUserData entity property.</li></ul><p>Note that permissions assigned to a specific Machine Fingerprint will supersede any parameter-level permissions that might otherwise apply to that user (from groups or standard permissions above). Machine Fingerprint address permissions are overriden if the user has their own row in the users section.</p>'>?</a>",
"span": 11 "span": 11
} }
], ],
@ -1043,7 +1078,7 @@
}, },
{ {
"name": "id_can_get_and_set_private_user_data", "name": "id_can_get_and_set_private_user_data",
"label": "Can Get and Set Private User Data", "label": "Get and Set Private User Data",
"type": "checkbox", "type": "checkbox",
"editable": true, "editable": true,
"default": false "default": false

View file

@ -4,6 +4,7 @@
// //
// Created by Stephen Birarda on 2015-08-24. // Created by Stephen Birarda on 2015-08-24.
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -94,6 +95,9 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
} else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) { } else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) {
QByteArray usernameSignature; QByteArray usernameSignature;
QString domainUsername;
QStringList domainTokens;
if (message->getBytesLeftToRead() > 0) { if (message->getBytesLeftToRead() > 0) {
// read username from packet // read username from packet
packetStream >> username; packetStream >> username;
@ -101,10 +105,25 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
if (message->getBytesLeftToRead() > 0) { if (message->getBytesLeftToRead() > 0) {
// read user signature from packet // read user signature from packet
packetStream >> usernameSignature; packetStream >> usernameSignature;
if (message->getBytesLeftToRead() > 0) {
// Read domain username from packet.
packetStream >> domainUsername;
domainUsername = domainUsername.toLower(); // Domain usernames are case-insensitive; internally lower-case.
if (message->getBytesLeftToRead() > 0) {
// Read domain tokens from packet.
QString domainTokensString;
packetStream >> domainTokensString;
domainTokens = domainTokensString.split(":");
}
}
} }
} }
node = processAgentConnectRequest(nodeConnection, username, usernameSignature); node = processAgentConnectRequest(nodeConnection, username, usernameSignature,
domainUsername, domainTokens.value(0), domainTokens.value(1));
} }
if (node) { if (node) {
@ -142,7 +161,8 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointer<ReceivedMessag
} }
} }
NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress, NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QString verifiedUsername,
QString verifiedDomainUserName, const QHostAddress& senderAddress,
const QString& hardwareAddress, const QUuid& machineFingerprint) { const QString& hardwareAddress, const QUuid& machineFingerprint) {
NodePermissions userPerms; NodePermissions userPerms;
@ -155,6 +175,27 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
#endif #endif
} }
// If this user is a known member of a domain group, give them the implied permissions.
// Do before processing verifiedUsername in case user is logged into the metaverse and is a member of a blacklist group.
if (!verifiedDomainUserName.isEmpty()) {
auto userGroups = _domainGroupMemberships[verifiedDomainUserName];
foreach (QString userGroup, userGroups) {
// A domain group is signified by a leading special character, "@".
// Multiple domain groups may be specified in one domain server setting as a comma- and/or space-separated lists of
// domain group names. For example, "@silver @Gold, @platinum".
auto domainGroups = _server->_settingsManager.getDomainServerGroupNames()
.filter(QRegularExpression("^(.*[\\s,])?" + QRegularExpression::escape(userGroup) + "([\\s,].*)?$",
QRegularExpression::CaseInsensitiveOption));
foreach(QString domainGroup, domainGroups) {
userPerms |= _server->_settingsManager.getPermissionsForGroup(domainGroup, QUuid()); // No rank for domain groups.
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: domain user " << verifiedDomainUserName << "is in group:" << domainGroup
<< "so:" << userPerms;
#endif
}
}
}
if (verifiedUsername.isEmpty()) { if (verifiedUsername.isEmpty()) {
userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous); userPerms |= _server->_settingsManager.getStandardPermissionsForName(NodePermissions::standardNameAnonymous);
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
@ -256,6 +297,27 @@ NodePermissions DomainGatekeeper::setPermissionsForUser(bool isLocalUser, QStrin
userPerms.setVerifiedUserName(verifiedUsername); userPerms.setVerifiedUserName(verifiedUsername);
} }
// If this user is a known member of an domain group that is blacklisted, remove the implied permissions.
if (!verifiedDomainUserName.isEmpty()) {
auto userGroups = _domainGroupMemberships[verifiedDomainUserName];
foreach(QString userGroup, userGroups) {
// A domain group is signified by a leading special character, "@".
// Multiple domain groups may be specified in one domain server setting as a comma- and/or space-separated lists of
// domain group names. For example, "@silver @Gold, @platinum".
auto domainGroups = _server->_settingsManager.getDomainServerBlacklistGroupNames()
.filter(QRegularExpression("^(.*[\\s,])?" + QRegularExpression::escape(userGroup) + "([\\s,].*)?$",
QRegularExpression::CaseInsensitiveOption));
foreach(QString domainGroup, domainGroups) {
userPerms &= ~_server->_settingsManager.getForbiddensForGroup(domainGroup, QUuid());
#ifdef WANT_DEBUG
qDebug() << "| user-permissions: domain user is in blacklist group:" << domainGroup << "so:" << userPerms;
#endif
}
}
userPerms.setVerifiedDomainUserName(verifiedDomainUserName);
}
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
qDebug() << "| user-permissions: final:" << userPerms; qDebug() << "| user-permissions: final:" << userPerms;
#endif #endif
@ -275,6 +337,7 @@ void DomainGatekeeper::updateNodePermissions() {
// the id and the username in NodePermissions will often be the same, but id is set before // 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. // authentication and verifiedUsername is only set once they user's key has been confirmed.
QString verifiedUsername = node->getPermissions().getVerifiedUserName(); QString verifiedUsername = node->getPermissions().getVerifiedUserName();
QString verifiedDomainUserName = node->getPermissions().getVerifiedDomainUserName();
NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0)); NodePermissions userPerms(NodePermissionsKey(verifiedUsername, 0));
if (node->getPermissions().isAssignment) { if (node->getPermissions().isAssignment) {
@ -309,7 +372,8 @@ void DomainGatekeeper::updateNodePermissions() {
sendingAddress == QHostAddress::LocalHost); sendingAddress == QHostAddress::LocalHost);
} }
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, connectingAddr.getAddress(), hardwareAddress, machineFingerprint); userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUserName,
connectingAddr.getAddress(), hardwareAddress, machineFingerprint);
} }
node->setPermissions(userPerms); node->setPermissions(userPerms);
@ -387,12 +451,19 @@ SharedNodePointer DomainGatekeeper::processAssignmentConnectRequest(const NodeCo
return newNode; return newNode;
} }
const QString AUTHENTICATION_ENABLE_OAUTH2 = "authentication.enable_oauth2";
const QString AUTHENTICATION_OAUTH2_URL_PATH = "authentication.oauth2_url_path";
const QString AUTHENTICATION_WORDPRESS_URL_BASE = "authentication.wordpress_url_base";
const QString AUTHENTICATION_PLUGIN_CLIENT_ID = "authentication.plugin_client_id";
const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity"; const QString MAXIMUM_USER_CAPACITY = "security.maximum_user_capacity";
const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location"; const QString MAXIMUM_USER_CAPACITY_REDIRECT_LOCATION = "security.maximum_user_capacity_redirect_location";
SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection, SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnectionData& nodeConnection,
const QString& username, const QString& username,
const QByteArray& usernameSignature) { const QByteArray& usernameSignature,
const QString& domainUsername,
const QString& domainAccessToken,
const QString& domainRefreshToken) {
auto limitedNodeList = DependencyManager::get<LimitedNodeList>(); auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
@ -419,7 +490,9 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
qDebug() << "stalling login because we have no username-signature:" << username; qDebug() << "stalling login because we have no username-signature:" << username;
#endif #endif
return SharedNodePointer(); if (!domainHasLogin() || domainUsername.isEmpty()) {
return SharedNodePointer();
}
} else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) { } else if (verifyUserSignature(username, usernameSignature, nodeConnection.senderSockAddr)) {
// they sent us a username and the signature verifies it // they sent us a username and the signature verifies it
getGroupMemberships(username); getGroupMemberships(username);
@ -430,16 +503,70 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
qDebug() << "stalling login because signature verification failed:" << username; qDebug() << "stalling login because signature verification failed:" << username;
#endif #endif
return SharedNodePointer(); if (!domainHasLogin() || domainUsername.isEmpty()) {
return SharedNodePointer();
}
} }
} }
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, nodeConnection.senderSockAddr.getAddress(), // The domain may have its own users and groups.
nodeConnection.hardwareAddress, nodeConnection.machineFingerprint); QString verifiedDomainUsername;
QStringList verifiedDomainUserGroups;
if (domainHasLogin() && !domainUsername.isEmpty()) {
if (domainAccessToken.isEmpty()) {
// User is attempting to prove their domain identity.
#ifdef WANT_DEBUG
qDebug() << "Stalling login because we have no domain OAuth2 tokens:" << domainUsername;
#endif
return SharedNodePointer();
} else if (needToVerifyDomainUserIdentity(domainUsername, domainAccessToken, domainRefreshToken)) {
// User's domain identity needs to be confirmed.
requestDomainUser(domainUsername, domainAccessToken, domainRefreshToken);
#ifdef WANT_DEBUG
qDebug() << "Stalling login because we haven't authenticated user yet:" << domainUsername;
#endif
} else if (verifyDomainUserIdentity(domainUsername, domainAccessToken, domainRefreshToken,
nodeConnection.senderSockAddr)) {
// User's domain identity is confirmed.
verifiedDomainUsername = domainUsername;
} else {
// User's domain identity didn't check out.
#ifdef WANT_DEBUG
qDebug() << "Stalling login because domain user verification failed:" << domainUsername;
#endif
return SharedNodePointer();
}
}
userPerms = setPermissionsForUser(isLocalUser, verifiedUsername, verifiedDomainUsername,
nodeConnection.senderSockAddr.getAddress(), nodeConnection.hardwareAddress,
nodeConnection.machineFingerprint);
if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) { if (!userPerms.can(NodePermissions::Permission::canConnectToDomain)) {
sendConnectionDeniedPacket("You lack the required permissions to connect to this domain.", if (domainHasLogin()) {
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorized); QString domainAuthURL;
auto domainAuthURLVariant = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_PATH);
if (domainAuthURLVariant.canConvert<QString>()) {
domainAuthURL = domainAuthURLVariant.toString();
}
QString domainAuthClientID;
auto domainAuthClientIDVariant = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_PLUGIN_CLIENT_ID);
if (domainAuthClientIDVariant.canConvert<QString>()) {
domainAuthClientID = domainAuthClientIDVariant.toString();
}
sendConnectionDeniedPacket("You lack the required domain permissions to connect to this domain.",
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedDomain,
domainAuthURL + "|" + domainAuthClientID);
} else {
sendConnectionDeniedPacket("You lack the required metaverse permissions to connect to this domain.",
nodeConnection.senderSockAddr, DomainHandler::ConnectionRefusedReason::NotAuthorizedMetaverse);
}
#ifdef WANT_DEBUG #ifdef WANT_DEBUG
qDebug() << "stalling login due to permissions:" << username; qDebug() << "stalling login due to permissions:" << username;
#endif #endif
@ -600,15 +727,15 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
return true; return true;
} else { } else {
// we only send back a LoginError if this wasn't an "optimistic" key // we only send back a LoginErrorMetaverse if this wasn't an "optimistic" key
// (a key that we hoped would work but is probably stale) // (a key that we hoped would work but is probably stale)
if (!senderSockAddr.isNull() && !isOptimisticKey) { if (!senderSockAddr.isNull() && !isOptimisticKey) {
qDebug() << "Error decrypting username signature for" << username << "- denying connection."; qDebug() << "Error decrypting metaverse username signature for" << username << "- denying connection.";
sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr, sendConnectionDeniedPacket("Error decrypting username signature.", senderSockAddr,
DomainHandler::ConnectionRefusedReason::LoginError); DomainHandler::ConnectionRefusedReason::LoginErrorMetaverse);
} else if (!senderSockAddr.isNull()) { } else if (!senderSockAddr.isNull()) {
qDebug() << "Error decrypting username signature for" << username << "with optimisitic key -" qDebug() << "Error decrypting metaverse username signature for" << username << "with optimistic key -"
<< "re-requesting public key and delaying connection"; << "re-requesting public key and delaying connection";
} }
@ -622,7 +749,7 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
if (!senderSockAddr.isNull()) { if (!senderSockAddr.isNull()) {
qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection."; qDebug() << "Couldn't convert data to RSA key for" << username << "- denying connection.";
sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr, sendConnectionDeniedPacket("Couldn't convert data to RSA key.", senderSockAddr,
DomainHandler::ConnectionRefusedReason::LoginError); DomainHandler::ConnectionRefusedReason::LoginErrorMetaverse);
} }
} }
} else { } else {
@ -635,6 +762,25 @@ bool DomainGatekeeper::verifyUserSignature(const QString& username,
return false; return false;
} }
bool DomainGatekeeper::needToVerifyDomainUserIdentity(const QString& username, const QString& accessToken,
const QString& refreshToken) {
return !_verifiedDomainUserIdentities.contains(username)
|| _verifiedDomainUserIdentities.value(username) != QPair<QString, QString>(accessToken, refreshToken);
}
bool DomainGatekeeper::verifyDomainUserIdentity(const QString& username, const QString& accessToken,
const QString& refreshToken, const HifiSockAddr& senderSockAddr) {
if (_verifiedDomainUserIdentities.contains(username)
&& _verifiedDomainUserIdentities.value(username) == QPair<QString, QString>(accessToken, refreshToken)) {
return true;
}
sendConnectionDeniedPacket("Error verifying domain user.", senderSockAddr,
DomainHandler::ConnectionRefusedReason::LoginErrorDomain);
return false;
}
bool DomainGatekeeper::isWithinMaxCapacity() { bool DomainGatekeeper::isWithinMaxCapacity() {
// find out what our maximum capacity is // find out what our maximum capacity is
QVariant maximumUserCapacityVariant = QVariant maximumUserCapacityVariant =
@ -907,7 +1053,6 @@ void DomainGatekeeper::getGroupMemberships(const QString& username) {
AccountManagerAuth::Required, AccountManagerAuth::Required,
QNetworkAccessManager::PostOperation, callbackParams, QNetworkAccessManager::PostOperation, callbackParams,
QJsonDocument(json).toJson()); QJsonDocument(json).toJson());
} }
QString extractUsernameFromGroupMembershipsReply(QNetworkReply* requestReply) { QString extractUsernameFromGroupMembershipsReply(QNetworkReply* requestReply) {
@ -962,6 +1107,7 @@ void DomainGatekeeper::getIsGroupMemberErrorCallback(QNetworkReply* requestReply
_inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply)); _inFlightGroupMembershipsRequests.remove(extractUsernameFromGroupMembershipsReply(requestReply));
} }
void DomainGatekeeper::getDomainOwnerFriendsList() { void DomainGatekeeper::getDomainOwnerFriendsList() {
JSONCallbackParameters callbackParams; JSONCallbackParameters callbackParams;
callbackParams.callbackReceiver = this; callbackParams.callbackReceiver = this;
@ -1010,6 +1156,7 @@ void DomainGatekeeper::getDomainOwnerFriendsListErrorCallback(QNetworkReply* req
qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply->error(); qDebug() << "getDomainOwnerFriendsList api call failed:" << requestReply->error();
} }
// ####### TODO: Domain equivalent or addition
void DomainGatekeeper::refreshGroupsCache() { void DomainGatekeeper::refreshGroupsCache() {
// if agents are connected to this domain, refresh our cached information about groups and memberships in such. // if agents are connected to this domain, refresh our cached information about groups and memberships in such.
getDomainOwnerFriendsList(); getDomainOwnerFriendsList();
@ -1029,7 +1176,7 @@ void DomainGatekeeper::refreshGroupsCache() {
updateNodePermissions(); updateNodePermissions();
#if WANT_DEBUG #ifdef WANT_DEBUG
_server->_settingsManager.debugDumpGroupsState(); _server->_settingsManager.debugDumpGroupsState();
#endif #endif
} }
@ -1061,3 +1208,91 @@ Node::LocalID DomainGatekeeper::findOrCreateLocalID(const QUuid& uuid) {
_localIDs.insert(newLocalID); _localIDs.insert(newLocalID);
return newLocalID; return newLocalID;
} }
bool DomainGatekeeper::domainHasLogin() {
// The domain may have its own users and groups in a WordPress site.
return _server->_settingsManager.valueForKeyPath(AUTHENTICATION_ENABLE_OAUTH2).toBool()
&& !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_OAUTH2_URL_PATH).toString().isEmpty()
&& !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString().isEmpty()
&& !_server->_settingsManager.valueForKeyPath(AUTHENTICATION_PLUGIN_CLIENT_ID).toString().isEmpty();
}
void DomainGatekeeper::requestDomainUser(const QString& username, const QString& accessToken, const QString& refreshToken) {
if (_inFlightDomainUserIdentityRequests.contains(username)) {
// Domain identify request for this username is already in progress.
return;
}
_inFlightDomainUserIdentityRequests.insert(username, QPair<QString, QString>(accessToken, refreshToken));
if (_verifiedDomainUserIdentities.contains(username)) {
_verifiedDomainUserIdentities.remove(username);
}
QString apiBase = _server->_settingsManager.valueForKeyPath(AUTHENTICATION_WORDPRESS_URL_BASE).toString();
if (!apiBase.endsWith("/")) {
apiBase += "/";
}
// Get data pertaining to "me", the user who generated the access token.
const QString WORDPRESS_USER_ROUTE = "wp/v2/users/me";
const QString WORDPRESS_USER_QUERY = "_fields=username,roles";
QUrl domainUserURL = apiBase + WORDPRESS_USER_ROUTE + (apiBase.contains("?") ? "&" : "?") + WORDPRESS_USER_QUERY;
QNetworkRequest request;
request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
request.setRawHeader(QByteArray("Authorization"), QString("Bearer " + accessToken).toUtf8());
QByteArray formData; // No data to send.
request.setUrl(domainUserURL);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* requestReply = networkAccessManager.post(request, formData);
connect(requestReply, &QNetworkReply::finished, this, &DomainGatekeeper::requestDomainUserFinished);
}
void DomainGatekeeper::requestDomainUserFinished() {
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
const QJsonObject& rootObject = jsonResponse.object();
auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (200 <= httpStatus && httpStatus < 300) {
QString username = rootObject.value("username").toString().toLower();
if (_inFlightDomainUserIdentityRequests.contains(username)) {
// Success! Verified user.
_verifiedDomainUserIdentities.insert(username, _inFlightDomainUserIdentityRequests.value(username));
_inFlightDomainUserIdentityRequests.remove(username);
// User user's WordPress roles as domain groups.
QStringList domainUserGroups;
auto userRoles = rootObject.value("roles").toArray();
foreach (auto role, userRoles) {
// Distinguish domain groups from metaverse groups by adding a leading special character.
domainUserGroups.append(DOMAIN_GROUP_CHAR + role.toString().toLower());
}
_domainGroupMemberships[username] = domainUserGroups;
} else {
// Failure.
qDebug() << "Unexpected username in response for user details -" << username;
}
} else {
// Failure.
qDebug() << "Error in response for user details -" << httpStatus << requestReply->error()
<< "-" << rootObject["error"].toString() << rootObject["error_description"].toString();
_inFlightDomainUserIdentityRequests.clear();
}
}

View file

@ -4,6 +4,7 @@
// //
// Created by Stephen Birarda on 2015-08-24. // Created by Stephen Birarda on 2015-08-24.
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -29,6 +30,8 @@
#include "NodeConnectionData.h" #include "NodeConnectionData.h"
#include "PendingAssignedNodeData.h" #include "PendingAssignedNodeData.h"
const QString DOMAIN_GROUP_CHAR = "@";
class DomainServer; class DomainServer;
class DomainGatekeeper : public QObject { class DomainGatekeeper : public QObject {
@ -71,16 +74,28 @@ public slots:
private slots: private slots:
void handlePeerPingTimeout(); void handlePeerPingTimeout();
// Login and groups for domain, separate from metaverse.
void requestDomainUserFinished();
private: private:
SharedNodePointer processAssignmentConnectRequest(const NodeConnectionData& nodeConnection, SharedNodePointer processAssignmentConnectRequest(const NodeConnectionData& nodeConnection,
const PendingAssignedNodeData& pendingAssignment); const PendingAssignedNodeData& pendingAssignment);
SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection, SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection,
const QString& username, const QString& username,
const QByteArray& usernameSignature); const QByteArray& usernameSignature,
const QString& domainUsername,
const QString& domainAccessToken,
const QString& domainRefreshToken);
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection); SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection);
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature, bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr); const HifiSockAddr& senderSockAddr);
bool needToVerifyDomainUserIdentity(const QString& username, const QString& accessToken, const QString& refreshToken);
bool verifyDomainUserIdentity(const QString& username, const QString& accessToken, const QString& refreshToken,
const HifiSockAddr& senderSockAddr);
bool isWithinMaxCapacity(); bool isWithinMaxCapacity();
bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature, bool shouldAllowConnectionFromNode(const QString& username, const QByteArray& usernameSignature,
@ -120,8 +135,9 @@ private:
QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner QSet<QString> _domainOwnerFriends; // keep track of friends of the domain owner
QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for QSet<QString> _inFlightGroupMembershipsRequests; // keep track of which we've already asked for
NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, const QHostAddress& senderAddress, NodePermissions setPermissionsForUser(bool isLocalUser, QString verifiedUsername, QString verifiedDomainUsername,
const QString& hardwareAddress, const QUuid& machineFingerprint); const QHostAddress& senderAddress, const QString& hardwareAddress,
const QUuid& machineFingerprint);
void getGroupMemberships(const QString& username); void getGroupMemberships(const QString& username);
// void getIsGroupMember(const QString& username, const QUuid groupID); // void getIsGroupMember(const QString& username, const QUuid groupID);
@ -133,9 +149,18 @@ private:
using LocalIDs = std::unordered_set<Node::LocalID>; using LocalIDs = std::unordered_set<Node::LocalID>;
LocalIDs _localIDs; LocalIDs _localIDs;
UUIDToLocalID _uuidToLocalID; UUIDToLocalID _uuidToLocalID;
Node::LocalID _currentLocalID; Node::LocalID _currentLocalID;
Node::LocalID _idIncrement; Node::LocalID _idIncrement;
// Login and groups for domain, separate from metaverse.
bool domainHasLogin();
void requestDomainUser(const QString& username, const QString& accessToken, const QString& refreshToken);
typedef QHash<QString, QPair<QString, QString>> DomainUserIdentities; // <domainUserName, <access_token, refresh_token>>
DomainUserIdentities _inFlightDomainUserIdentityRequests; // Domain user identity requests currently in progress.
DomainUserIdentities _verifiedDomainUserIdentities; // Verified domain users.
QHash<QString, QStringList> _domainGroupMemberships; // <domainUserName, [domainGroupName]>
}; };

View file

@ -121,7 +121,7 @@ bool DomainServer::forwardMetaverseAPIRequest(HTTPConnection* connection,
QUrl url{ MetaverseAPI::getCurrentMetaverseServerURL().toString() + metaversePath }; QUrl url{ MetaverseAPI::getCurrentMetaverseServerURL().toString() + metaversePath };
QNetworkRequest req(url); QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); req.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
if (accessTokenVariant.isValid()) { if (accessTokenVariant.isValid()) {
@ -2470,7 +2470,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
url.setQuery("access_token=" + accessTokenVariant.toString()); url.setQuery("access_token=" + accessTokenVariant.toString());
QNetworkRequest req(url); QNetworkRequest req(url);
req.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); req.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); req.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
QNetworkReply* reply = NetworkAccessManager::getInstance().put(req, doc.toJson()); QNetworkReply* reply = NetworkAccessManager::getInstance().put(req, doc.toJson());
@ -2571,7 +2571,7 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u
QNetworkRequest tokenRequest(tokenRequestUrl); QNetworkRequest tokenRequest(tokenRequestUrl);
tokenRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); tokenRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
tokenRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); tokenRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
QNetworkReply* tokenReply = NetworkAccessManager::getInstance().post(tokenRequest, tokenPostBody.toLocal8Bit()); QNetworkReply* tokenReply = NetworkAccessManager::getInstance().post(tokenRequest, tokenPostBody.toLocal8Bit());
@ -2883,7 +2883,7 @@ QNetworkReply* DomainServer::profileRequestGivenTokenReply(QNetworkReply* tokenR
QNetworkRequest profileRequest(profileURL); QNetworkRequest profileRequest(profileURL);
profileRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); profileRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
profileRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); profileRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
return NetworkAccessManager::getInstance().get(profileRequest); return NetworkAccessManager::getInstance().get(profileRequest);
} }

View file

@ -1966,6 +1966,10 @@ void DomainServerSettingsManager::apiRefreshGroupInformation() {
QStringList groupNames = getAllKnownGroupNames(); QStringList groupNames = getAllKnownGroupNames();
foreach (QString groupName, groupNames) { foreach (QString groupName, groupNames) {
QString lowerGroupName = groupName.toLower(); QString lowerGroupName = groupName.toLower();
if (lowerGroupName.startsWith(DOMAIN_GROUP_CHAR)) {
// Ignore domain groups. (Assumption: metaverse group names can't start with a "@".)
return;
}
if (_groupIDs.contains(lowerGroupName)) { if (_groupIDs.contains(lowerGroupName)) {
// we already know about this one. recall setGroupID in case the group has been // we already know about this one. recall setGroupID in case the group has been
// added to another section (the same group is found in both groups and blacklists). // added to another section (the same group is found in both groups and blacklists).
@ -2185,6 +2189,24 @@ QList<QUuid> DomainServerSettingsManager::getBlacklistGroupIDs() {
return result.toList(); return result.toList();
} }
QStringList DomainServerSettingsManager::getDomainServerGroupNames() {
// All names as listed in the domain server settings; both metaverse groups and domain groups
QSet<QString> result;
foreach(NodePermissionsKey groupKey, _groupPermissions.keys()) {
result += _groupPermissions[groupKey]->getID();
}
return result.toList();
}
QStringList DomainServerSettingsManager::getDomainServerBlacklistGroupNames() {
// All names as listed in the domain server settings; not necessarily mnetaverse groups.
QSet<QString> result;
foreach (NodePermissionsKey groupKey, _groupForbiddens.keys()) {
result += _groupForbiddens[groupKey]->getID();
}
return result.toList();
}
void DomainServerSettingsManager::debugDumpGroupsState() { void DomainServerSettingsManager::debugDumpGroupsState() {
qDebug() << "--------- GROUPS ---------"; qDebug() << "--------- GROUPS ---------";

View file

@ -19,11 +19,11 @@
#include <HifiConfigVariantMap.h> #include <HifiConfigVariantMap.h>
#include <HTTPManager.h> #include <HTTPManager.h>
#include <ReceivedMessage.h>
#include "NodePermissions.h"
#include <Node.h> #include <Node.h>
#include <ReceivedMessage.h>
#include "DomainGatekeeper.h"
#include "NodePermissions.h"
const QString SETTINGS_PATHS_KEY = "paths"; const QString SETTINGS_PATHS_KEY = "paths";
@ -105,6 +105,9 @@ public:
QList<QUuid> getGroupIDs(); QList<QUuid> getGroupIDs();
QList<QUuid> getBlacklistGroupIDs(); QList<QUuid> getBlacklistGroupIDs();
QStringList getDomainServerGroupNames();
QStringList getDomainServerBlacklistGroupNames();
// these are used to locally cache the result of calling "api/v1/groups/.../is_member/..." on metaverse's api // 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 clearGroupMemberships(const QString& name) { _groupMembership[name.toLower()].clear(); }
void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID); void recordGroupMembership(const QString& name, const QUuid groupID, QUuid rankID);

View file

@ -10,87 +10,74 @@ import zipfile
print = functools.partial(print, flush=True) print = functools.partial(print, flush=True)
ANDROID_PACKAGE_URL = 'https://hifi-public.s3.amazonaws.com/dependencies/android/' ANDROID_PACKAGE_URL = 'https://content.vircadia.com/eu-c-1/vircadia-public/dependencies/android/'
ANDROID_PACKAGES = { ANDROID_PACKAGES = {
'qt' : { 'qt' : {
'file': 'qt-5.11.1_linux_armv8-libcpp_openssl_patched.tgz', 'file': 'qt-5.11.1_linux_armv8-libcpp_openssl_patched.tgz',
'versionId': '3S97HBM5G5Xw9EfE52sikmgdN3t6C2MN',
'checksum': 'aa449d4bfa963f3bc9a9dfe558ba29df', 'checksum': 'aa449d4bfa963f3bc9a9dfe558ba29df',
}, },
'bullet': { 'bullet': {
'file': 'bullet-2.88_armv8-libcpp.tgz', 'file': 'bullet-2.88_armv8-libcpp.tgz',
'versionId': 'S8YaoED0Cl8sSb8fSV7Q2G1lQJSNDxqg',
'checksum': '81642779ccb110f8c7338e8739ac38a0', 'checksum': '81642779ccb110f8c7338e8739ac38a0',
}, },
'draco': { 'draco': {
'file': 'draco_armv8-libcpp.tgz', 'file': 'draco_armv8-libcpp.tgz',
'versionId': '3.B.uBj31kWlgND3_R2xwQzT_TP6Dz_8',
'checksum': '617a80d213a5ec69fbfa21a1f2f738cd', 'checksum': '617a80d213a5ec69fbfa21a1f2f738cd',
}, },
'glad': { 'glad': {
'file': 'glad_armv8-libcpp.zip', 'file': 'glad_armv8-libcpp.zip',
'versionId': 'r5Zran.JSCtvrrB6Q4KaqfIoALPw3lYY',
'checksum': 'a8ee8584cf1ccd34766c7ddd9d5e5449', 'checksum': 'a8ee8584cf1ccd34766c7ddd9d5e5449',
}, },
'gvr': { 'gvr': {
'file': 'gvrsdk_v1.101.0.tgz', 'file': 'gvrsdk_v1.101.0.tgz',
'versionId': 'nqBV_j81Uc31rC7bKIrlya_Hah4v3y5r',
'checksum': '57fd02baa069176ba18597a29b6b4fc7', 'checksum': '57fd02baa069176ba18597a29b6b4fc7',
}, },
'nvtt': { 'nvtt': {
'file': 'nvtt_armv8-libcpp.zip', 'file': 'nvtt_armv8-libcpp.zip',
'versionId': 'lmkBVR5t4UF1UUwMwEirnk9H_8Nt90IO',
'checksum': 'eb46d0b683e66987190ed124aabf8910', 'checksum': 'eb46d0b683e66987190ed124aabf8910',
'sharedLibFolder': 'lib', 'sharedLibFolder': 'lib',
'includeLibs': ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so'] 'includeLibs': ['libnvtt.so', 'libnvmath.so', 'libnvimage.so', 'libnvcore.so']
}, },
'oculus_1.22': { 'oculus_1.22': {
'file': 'ovr_sdk_mobile_1.22.zip', 'file': 'ovr_sdk_mobile_1.22.zip',
'versionId': 'InhomR5gwkzyiLAelH3X9k4nvV3iIpA_',
'checksum': '1ac3c5b0521e5406f287f351015daff8', 'checksum': '1ac3c5b0521e5406f287f351015daff8',
'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release', 'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release',
'includeLibs': ['libvrapi.so'] 'includeLibs': ['libvrapi.so']
}, },
'oculusPlatform': { 'oculusPlatform': {
'file': 'OVRPlatformSDK_v1.34.0.zip', 'file': 'OVRPlatformSDK_v1.34.0.zip',
'versionId': 'vbRUkkyzUAXfTGSEtuiUr_7.Fm5h5BZk',
'checksum': '16e4c5f39520f122bc49cb6d5bb88289', 'checksum': '16e4c5f39520f122bc49cb6d5bb88289',
'sharedLibFolder': 'Android/libs/arm64-v8a', 'sharedLibFolder': 'Android/libs/arm64-v8a',
'includeLibs': ['libovrplatformloader.so'] 'includeLibs': ['libovrplatformloader.so']
}, },
'openssl': { 'openssl': {
'file': 'openssl-1.1.0g_armv8.tgz', 'file': 'openssl-1.1.0g_armv8.tgz',
'versionId': 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW',
'checksum': 'cabb681fbccd79594f65fcc266e02f32' 'checksum': 'cabb681fbccd79594f65fcc266e02f32'
}, },
'polyvox': { 'polyvox': {
'file': 'polyvox_armv8-libcpp.tgz', 'file': 'polyvox_armv8-libcpp.tgz',
'versionId': 'A2kbKiNhpIenGq23bKRRzg7IMAI5BI92',
'checksum': 'dba88b3a098747af4bb169e9eb9af57e', 'checksum': 'dba88b3a098747af4bb169e9eb9af57e',
'sharedLibFolder': 'lib', 'sharedLibFolder': 'lib',
'includeLibs': ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'], 'includeLibs': ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'],
}, },
'tbb': { 'tbb': {
'file': 'tbb-2018_U1_armv8_libcpp.tgz', 'file': 'tbb-2018_U1_armv8_libcpp.tgz',
'versionId': 'mrRbWnv4O4evcM1quRH43RJqimlRtaKB',
'checksum': '20768f298f53b195e71b414b0ae240c4', 'checksum': '20768f298f53b195e71b414b0ae240c4',
'sharedLibFolder': 'lib/release', 'sharedLibFolder': 'lib/release',
'includeLibs': ['libtbb.so', 'libtbbmalloc.so'], 'includeLibs': ['libtbb.so', 'libtbbmalloc.so'],
}, },
'hifiAC': { 'hifiAC': {
'baseUrl': 'http://s3.amazonaws.com/hifi-public/dependencies/', 'baseUrl': 'https://content.vircadia.com/eu-c-1/vircadia-public/dependencies/',
'file': 'codecSDK-android_armv8-2.0.zip', 'file': 'codecSDK-android_armv8-2.0.zip',
'checksum': '1cbef929675818fc64c4101b72f84a6a' 'checksum': '1cbef929675818fc64c4101b72f84a6a'
}, },
'etc2comp': { 'etc2comp': {
'file': 'etc2comp-patched-armv8-libcpp.tgz', 'file': 'etc2comp-patched-armv8-libcpp.tgz',
'versionId': 'bHhGECRAQR1vkpshBcK6ByNc1BQIM8gU',
'checksum': '14b02795d774457a33bbc60e00a786bc' 'checksum': '14b02795d774457a33bbc60e00a786bc'
}, },
'breakpad': { 'breakpad': {
'file': 'breakpad.tgz', 'file': 'breakpad.tgz',
'versionId': '8VrYXz7oyc.QBxNia0BVJOUBvrFO61jI',
'checksum': 'ddcb23df336b08017042ba4786db1d9e', 'checksum': 'ddcb23df336b08017042ba4786db1d9e',
'sharedLibFolder': 'lib', 'sharedLibFolder': 'lib',
'includeLibs': {'libbreakpad_client.a'} 'includeLibs': {'libbreakpad_client.a'}
@ -105,14 +92,12 @@ ANDROID_PLATFORM_PACKAGES = {
'Darwin' : { 'Darwin' : {
'qt': { 'qt': {
'file': 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz', 'file': 'qt-5.11.1_osx_armv8-libcpp_openssl_patched.tgz',
'versionId': 'OxBD7iKINv1HbyOXmAmDrBb8AF3N.Kup',
'checksum': 'c83cc477c08a892e00c71764dca051a0' 'checksum': 'c83cc477c08a892e00c71764dca051a0'
}, },
}, },
'Windows' : { 'Windows' : {
'qt': { 'qt': {
'file': 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz', 'file': 'qt-5.11.1_win_armv8-libcpp_openssl_patched.tgz',
'versionId': 'JfWM0P_Mz5Qp0LwpzhrsRwN3fqlLSFeT',
'checksum': '0582191cc55431aa4f660848a542883e' 'checksum': '0582191cc55431aa4f660848a542883e'
}, },
} }

View file

@ -121,14 +121,14 @@ if (APPLE)
# configure CMake to use a custom Info.plist # configure CMake to use a custom Info.plist
set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in) set_target_properties(${this_target} PROPERTIES MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in)
set(MACOSX_BUNDLE_BUNDLE_NAME "High Fidelity") set(MACOSX_BUNDLE_BUNDLE_NAME "Vircadia")
if (PRODUCTION_BUILD) if (PRODUCTION_BUILD)
set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.interface) set(MACOSX_BUNDLE_GUI_IDENTIFIER com.vircadia.interface)
else () else ()
if (DEV_BUILD) if (DEV_BUILD)
set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.interface-dev) set(MACOSX_BUNDLE_GUI_IDENTIFIER com.vircadia.interface-dev)
elseif (PR_BUILD) elseif (PR_BUILD)
set(MACOSX_BUNDLE_GUI_IDENTIFIER com.highfidelity.interface-pr) set(MACOSX_BUNDLE_GUI_IDENTIFIER com.vircadia.interface-pr)
endif () endif ()
endif () endif ()

View file

@ -7,7 +7,7 @@ MessageDialog {
objectName: "ConnectionFailureDialog" objectName: "ConnectionFailureDialog"
title: "No Connection" title: "No Connection"
text: "Unable to connect to this domain. Click the 'GO TO' button on the toolbar to visit another domain." text: "Unable to connect to this domain. Click the 'EXPLORE' button on the toolbar to visit another domain."
buttons: OriginalDialogs.StandardButton.Ok buttons: OriginalDialogs.StandardButton.Ok
icon: OriginalDialogs.StandardIcon.Warning icon: OriginalDialogs.StandardIcon.Warning
defaultButton: OriginalDialogs.StandardButton.NoButton; defaultButton: OriginalDialogs.StandardButton.NoButton;

View file

@ -45,6 +45,9 @@ Item {
property bool lostFocus: false property bool lostFocus: false
readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
// If not logging into domain, then we must be logging into the metaverse...
readonly property bool isLoggingInToDomain: loginDialog.getDomainLoginRequested()
readonly property string domainLoginDomain: loginDialog.getDomainLoginDomain()
QtObject { QtObject {
id: d id: d
@ -71,7 +74,12 @@ Item {
} }
function login() { function login() {
loginDialog.login(emailField.text, passwordField.text); if (!isLoggingInToDomain) {
loginDialog.login(emailField.text, passwordField.text);
} else {
loginDialog.loginDomain(emailField.text, passwordField.text);
}
if (linkAccountBody.loginDialogPoppedUp) { if (linkAccountBody.loginDialogPoppedUp) {
var data; var data;
if (linkAccountBody.linkSteam) { if (linkAccountBody.linkSteam) {
@ -87,7 +95,7 @@ Item {
} }
bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam, bodyLoader.setSource("LoggingInBody.qml", { "loginDialog": loginDialog, "root": root, "bodyLoader": bodyLoader, "withSteam": linkAccountBody.withSteam,
"withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus, "withOculus": linkAccountBody.withOculus, "linkSteam": linkAccountBody.linkSteam, "linkOculus": linkAccountBody.linkOculus,
"displayName":displayNameField.text }); "displayName":displayNameField.text, "isLoggingInToDomain": linkAccountBody.isLoggingInToDomain, "domainLoginDomain": linkAccountBody.domainLoginDomain });
} }
function init() { function init() {
@ -98,14 +106,22 @@ Item {
loginErrorMessage.wrapMode = Text.WordWrap; loginErrorMessage.wrapMode = Text.WordWrap;
errorContainer.height = (loginErrorMessageTextMetrics.width / displayNameField.width) * loginErrorMessageTextMetrics.height; errorContainer.height = (loginErrorMessageTextMetrics.width / displayNameField.width) * loginErrorMessageTextMetrics.height;
} }
var domainLoginText = "Log In to Domain\n" + domainLoginDomain;
loginDialogText.text = (!isLoggingInToDomain) ? "Log In to Metaverse" : domainLoginText;
loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account"; loginButton.text = (!linkAccountBody.linkSteam && !linkAccountBody.linkOculus) ? "Log In" : "Link Account";
loginButton.text = (!isLoggingInToDomain) ? "Log In to Metaverse" : "Log In to Domain";
loginButton.color = hifi.buttons.blue; loginButton.color = hifi.buttons.blue;
displayNameField.placeholderText = "Display Name (optional)"; displayNameField.placeholderText = "Display Name (optional)";
var savedDisplayName = Settings.getValue("Avatar/displayName", ""); var savedDisplayName = Settings.getValue("Avatar/displayName", "");
displayNameField.text = savedDisplayName; displayNameField.text = savedDisplayName;
emailField.placeholderText = "Username or Email"; emailField.placeholderText = (!isLoggingInToDomain) ? "Username or Email" : "Username";
var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", ""); if (!isLoggingInToDomain) {
emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : ""; var savedUsername = Settings.getValue("keepMeLoggedIn/savedUsername", "");
emailField.text = keepMeLoggedInCheckbox.checked ? savedUsername === "Unknown user" ? "" : savedUsername : "";
} else {
// ####### TODO
}
if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) { if (linkAccountBody.linkSteam || linkAccountBody.linkOculus) {
loginButton.width = (passwordField.width - hifi.dimensions.contentSpacing.x) / 2; loginButton.width = (passwordField.width - hifi.dimensions.contentSpacing.x) / 2;
loginButton.anchors.right = displayNameField.right; loginButton.anchors.right = displayNameField.right;
@ -131,7 +147,7 @@ Item {
Item { Item {
id: loginContainer id: loginContainer
width: displayNameField.width width: displayNameField.width
height: errorContainer.height + displayNameField.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y + height: errorContainer.height + loginDialogTextContainer.height + displayNameField.height + emailField.height + passwordField.height + 5.5 * hifi.dimensions.contentSpacing.y +
keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height keepMeLoggedInCheckbox.height + loginButton.height + cantAccessTextMetrics.height + continueButton.height
anchors { anchors {
top: parent.top top: parent.top
@ -145,9 +161,10 @@ Item {
width: parent.width width: parent.width
height: loginErrorMessageTextMetrics.height height: loginErrorMessageTextMetrics.height
anchors { anchors {
bottom: displayNameField.top; bottom: loginDialogTextContainer.top
bottomMargin: hifi.dimensions.contentSpacing.y; bottomMargin: hifi.dimensions.contentSpacing.y
left: displayNameField.left; left: loginDialogTextContainer.left
right: loginDialogTextContainer.right
} }
TextMetrics { TextMetrics {
id: loginErrorMessageTextMetrics id: loginErrorMessageTextMetrics
@ -160,12 +177,45 @@ Item {
font.family: linkAccountBody.fontFamily font.family: linkAccountBody.fontFamily
font.pixelSize: linkAccountBody.textFieldFontSize font.pixelSize: linkAccountBody.textFieldFontSize
font.bold: linkAccountBody.fontBold font.bold: linkAccountBody.fontBold
anchors {
top: parent.top
left: parent.left
right: parent.right
}
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
text: "" text: ""
visible: false visible: false
} }
} }
Item {
id: loginDialogTextContainer
height: 56
anchors {
top: parent.top
left: parent.left
right: parent.right
topMargin: 1.5 * hifi.dimensions.contentSpacing.y
}
Text {
id: loginDialogText
text: qsTr("Log In")
lineHeight: 1
color: "white"
anchors {
top: parent.top
left: parent.left
right: parent.right
}
font.family: linkAccountBody.fontFamily
font.pixelSize: 24
font.bold: linkAccountBody.fontBold
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter
}
}
HifiControlsUit.TextField { HifiControlsUit.TextField {
id: displayNameField id: displayNameField
@ -174,8 +224,8 @@ Item {
font.pixelSize: linkAccountBody.textFieldFontSize font.pixelSize: linkAccountBody.textFieldFontSize
styleRenderType: Text.QtRendering styleRenderType: Text.QtRendering
anchors { anchors {
top: parent.top top: loginDialogTextContainer.bottom
topMargin: errorContainer.height topMargin: 1.5 * hifi.dimensions.contentSpacing.y
} }
placeholderText: "Display Name (optional)" placeholderText: "Display Name (optional)"
activeFocusOnPress: true activeFocusOnPress: true
@ -193,7 +243,11 @@ Item {
case Qt.Key_Return: case Qt.Key_Return:
event.accepted = true; event.accepted = true;
if (keepMeLoggedInCheckbox.checked) { if (keepMeLoggedInCheckbox.checked) {
Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); if (!isLoggingInToDomain) {
Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text);
} else {
// ####### TODO
}
} }
linkAccountBody.login(); linkAccountBody.login();
break; break;
@ -232,7 +286,11 @@ Item {
case Qt.Key_Return: case Qt.Key_Return:
event.accepted = true; event.accepted = true;
if (keepMeLoggedInCheckbox.checked) { if (keepMeLoggedInCheckbox.checked) {
Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); if (!isLoggingInToDomain) {
Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text);
} else {
// ####### TODO
}
} }
linkAccountBody.login(); linkAccountBody.login();
break; break;
@ -312,7 +370,11 @@ Item {
case Qt.Key_Return: case Qt.Key_Return:
event.accepted = true; event.accepted = true;
if (keepMeLoggedInCheckbox.checked) { if (keepMeLoggedInCheckbox.checked) {
Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); if (!isLoggingInToDomain) {
Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text);
} else {
// ####### TODO
}
} }
linkAccountBody.login(); linkAccountBody.login();
break; break;
@ -321,12 +383,13 @@ Item {
} }
HifiControlsUit.CheckBox { HifiControlsUit.CheckBox {
id: keepMeLoggedInCheckbox id: keepMeLoggedInCheckbox
checked: Settings.getValue("keepMeLoggedIn", false); checked: !isLoggingInToDomain ? Settings.getValue("keepMeLoggedIn", false) : false; // ####### TODO
text: qsTr("Keep Me Logged In"); text: qsTr("Keep Me Logged In");
boxSize: 18; boxSize: 18;
labelFontFamily: linkAccountBody.fontFamily labelFontFamily: linkAccountBody.fontFamily
labelFontSize: 18; labelFontSize: 18;
color: hifi.colors.white; color: hifi.colors.white;
visible: !isLoggingInToDomain
anchors { anchors {
top: passwordField.bottom; top: passwordField.bottom;
topMargin: hifi.dimensions.contentSpacing.y; topMargin: hifi.dimensions.contentSpacing.y;
@ -334,14 +397,22 @@ Item {
} }
onCheckedChanged: { onCheckedChanged: {
Settings.setValue("keepMeLoggedIn", checked); Settings.setValue("keepMeLoggedIn", checked);
if (keepMeLoggedInCheckbox.checked) { if (!isLoggingInToDomain) {
Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text); if (keepMeLoggedInCheckbox.checked) {
Settings.setValue("keepMeLoggedIn/savedUsername", emailField.text);
} else {
Settings.setValue("keepMeLoggedIn/savedUsername", "");
}
} else { } else {
Settings.setValue("keepMeLoggedIn/savedUsername", ""); // ####### TODO
} }
} }
Component.onCompleted: { Component.onCompleted: {
keepMeLoggedInCheckbox.checked = !Account.loggedIn; if (!isLoggingInToDomain) {
keepMeLoggedInCheckbox.checked = !Account.loggedIn;
} else {
// ####### TODO
}
} }
} }
HifiControlsUit.Button { HifiControlsUit.Button {
@ -393,7 +464,7 @@ Item {
HifiStylesUit.ShortcutText { HifiStylesUit.ShortcutText {
id: cantAccessText id: cantAccessText
z: 10 z: 10
visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus && !linkAccountBody.isLoggingInToDomain
anchors { anchors {
top: loginButton.bottom top: loginButton.bottom
topMargin: hifi.dimensions.contentSpacing.y topMargin: hifi.dimensions.contentSpacing.y
@ -403,7 +474,7 @@ Item {
font.pixelSize: linkAccountBody.textFieldFontSize font.pixelSize: linkAccountBody.textFieldFontSize
font.bold: linkAccountBody.fontBold font.bold: linkAccountBody.fontBold
text: "<a href='metaverse.vircadia.com/users/password/new'> Can't access your account?</a>" text: "<a href='https://metaverse.vircadia.com/users/password/new'> Can't access your account?</a>"
verticalAlignment: Text.AlignVCenter verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
@ -492,7 +563,7 @@ Item {
id: signUpContainer id: signUpContainer
width: loginContainer.width width: loginContainer.width
height: signUpTextMetrics.height height: signUpTextMetrics.height
visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus visible: !linkAccountBody.linkSteam && !linkAccountBody.linkOculus && !linkAccountBody.isLoggingInToDomain
anchors { anchors {
left: loginContainer.left left: loginContainer.left
top: loginContainer.bottom top: loginContainer.bottom
@ -529,7 +600,7 @@ Item {
leftMargin: hifi.dimensions.contentSpacing.x leftMargin: hifi.dimensions.contentSpacing.x
} }
text: "<a href='metaverse.vircadia.com/users/register'>Sign Up</a>" text: "<a href='https://metaverse.vircadia.com/users/register'>Sign Up</a>"
linkColor: hifi.colors.blueAccent linkColor: hifi.colors.blueAccent
onLinkActivated: { onLinkActivated: {

View file

@ -3,6 +3,7 @@
// //
// Created by Wayne Chen on 10/18/18 // Created by Wayne Chen on 10/18/18
// Copyright 2018 High Fidelity, Inc. // Copyright 2018 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -31,6 +32,8 @@ Item {
property bool linkSteam: linkSteam property bool linkSteam: linkSteam
property bool linkOculus: linkOculus property bool linkOculus: linkOculus
property bool createOculus: createOculus property bool createOculus: createOculus
property bool isLoggingInToDomain: isLoggingInToDomain
property string domainLoginDomain: domainLoginDomain
property string displayName: "" property string displayName: ""
readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp() readonly property bool loginDialogPoppedUp: loginDialog.getLoginDialogPoppedUp()
@ -106,6 +109,9 @@ Item {
loggingInGlyph.visible = true; loggingInGlyph.visible = true;
loggingInText.text = "Logging in to Oculus"; loggingInText.text = "Logging in to Oculus";
loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2; loggingInText.x = loggingInHeader.width/2 - loggingInTextMetrics.width/2 + loggingInGlyphTextMetrics.width/2;
} else if (loggingInBody.isLoggingInToDomain) {
loggingInText.text = "Logging in to " + domainLoginDomain;
loggingInText.anchors.centerIn = loggingInHeader;
} else { } else {
loggingInText.text = "Logging in"; loggingInText.text = "Logging in";
loggingInText.anchors.centerIn = loggingInHeader; loggingInText.anchors.centerIn = loggingInHeader;

View file

@ -137,7 +137,7 @@ Item {
if (webViewCoreUserAgent !== undefined) { if (webViewCoreUserAgent !== undefined) {
webViewCore.profile.httpUserAgent = webViewCoreUserAgent webViewCore.profile.httpUserAgent = webViewCoreUserAgent
} else { } else {
webViewCore.profile.httpUserAgent += " (HighFidelityInterface)"; webViewCore.profile.httpUserAgent += " (VircadiaInterface)";
} }
// Ensure the JS from the web-engine makes it to our logging // Ensure the JS from the web-engine makes it to our logging
webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) { webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {

View file

@ -19,7 +19,7 @@ Item {
buttons: OriginalDialogs.StandardButton.Ok, buttons: OriginalDialogs.StandardButton.Ok,
defaultButton: OriginalDialogs.StandardButton.NoButton, defaultButton: OriginalDialogs.StandardButton.NoButton,
title: "No Connection", title: "No Connection",
text: "Unable to connect to this domain. Click the 'GO TO' button on the toolbar to visit another domain." text: "Unable to connect to this domain. Click the 'EXPLORE' button on the toolbar to visit another domain."
}); });
object.selected.connect(function(button) { object.selected.connect(function(button) {
if (button === OriginalDialogs.StandardButton.Ok) { if (button === OriginalDialogs.StandardButton.Ok) {

View file

@ -6,6 +6,7 @@
// //
// Created by Vlad Stelmahovsky on 03/22/2017 // Created by Vlad Stelmahovsky on 03/22/2017
// Copyright 2017 High Fidelity, Inc. // Copyright 2017 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -325,178 +326,14 @@ Rectangle {
size: 16; size: 16;
text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to talk.") : text: (bar.currentIndex === 0) ? qsTr("Press and hold the button \"T\" to talk.") :
qsTr("Press and hold grip triggers on both of your controllers to talk."); qsTr("Press and hold grip triggers on both controllers to talk.");
}
}
Separator {
id: secondSeparator;
anchors.top: pttTextContainer.visible ? pttTextContainer.bottom : switchesContainer.bottom;
anchors.topMargin: 10;
}
Item {
id: inputDeviceHeader
x: margins.paddings;
width: parent.width - margins.paddings*2;
height: 36;
anchors.top: secondSeparator.bottom;
anchors.topMargin: 10;
HiFiGlyphs {
width: margins.sizeCheckBox;
text: hifi.glyphs.mic;
color: hifi.colors.white;
anchors.left: parent.left;
anchors.leftMargin: -size/4; //the glyph has empty space at left about 25%
anchors.verticalCenter: parent.verticalCenter;
size: 30;
}
RalewayRegular {
anchors.verticalCenter: parent.verticalCenter;
width: margins.sizeText + margins.sizeLevel;
anchors.left: parent.left;
anchors.leftMargin: margins.sizeCheckBox;
size: 22;
color: hifi.colors.white;
text: qsTr("Choose input device");
}
}
ListView {
id: inputView;
width: rightMostInputLevelPos;
anchors.top: inputDeviceHeader.bottom;
anchors.topMargin: 10;
x: margins.paddings
interactive: false;
height: contentHeight;
clip: true;
model: AudioScriptingInterface.devices.input;
delegate: Item {
width: rightMostInputLevelPos - margins.paddings*2
height: ((type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)) ?
(margins.sizeCheckBox > checkBoxInput.implicitHeight ? margins.sizeCheckBox + 4 : checkBoxInput.implicitHeight + 4) : 0
visible: (type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)
AudioControls.CheckBox {
id: checkBoxInput
anchors.left: parent.left
spacing: margins.sizeCheckBox - boxSize
anchors.verticalCenter: parent.verticalCenter
width: parent.width - inputLevel.width
clip: true
checkable: !checked
checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD;
boxSize: margins.sizeCheckBox / 2
isRound: true
text: devicename
fontSize: 16;
onPressed: {
if (!checked) {
stereoInput.checked = false;
AudioScriptingInterface.setStereoInput(false); // the next selected audio device might not support stereo
AudioScriptingInterface.setInputDevice(info, bar.currentIndex === 1);
}
}
}
AudioControls.InputPeak {
id: inputLevel
anchors.right: parent.right
peak: model.peak;
anchors.verticalCenter: parent.verticalCenter
visible: ((bar.currentIndex === 1 && isVR) ||
(bar.currentIndex === 0 && !isVR)) &&
AudioScriptingInterface.devices.input.peakValuesAvailable;
}
}
}
AudioControls.LoopbackAudio {
id: loopbackAudio
x: margins.paddings
anchors.top: inputView.bottom;
anchors.topMargin: 10;
visible: (bar.currentIndex === 1 && isVR) ||
(bar.currentIndex === 0 && !isVR);
anchors { left: parent.left; leftMargin: margins.paddings }
}
Separator {
id: thirdSeparator;
anchors.top: loopbackAudio.visible ? loopbackAudio.bottom : inputView.bottom;
anchors.topMargin: 10;
}
Item {
id: outputDeviceHeader;
anchors.topMargin: 10;
anchors.top: thirdSeparator.bottom;
x: margins.paddings;
width: parent.width - margins.paddings*2
height: 36
HiFiGlyphs {
anchors.left: parent.left
anchors.leftMargin: -size/4 //the glyph has empty space at left about 25%
anchors.verticalCenter: parent.verticalCenter;
width: margins.sizeCheckBox
text: hifi.glyphs.unmuted;
color: hifi.colors.white;
size: 36;
}
RalewayRegular {
width: margins.sizeText + margins.sizeLevel
anchors.left: parent.left
anchors.leftMargin: margins.sizeCheckBox
anchors.verticalCenter: parent.verticalCenter;
size: 22;
color: hifi.colors.white;
text: qsTr("Choose output device");
}
}
ListView {
id: outputView
width: parent.width - margins.paddings*2
x: margins.paddings;
interactive: false;
height: contentHeight;
anchors.top: outputDeviceHeader.bottom;
anchors.topMargin: 10;
clip: true;
model: AudioScriptingInterface.devices.output;
delegate: Item {
width: rightMostInputLevelPos
height: ((type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)) ?
(margins.sizeCheckBox > checkBoxOutput.implicitHeight ? margins.sizeCheckBox + 4 : checkBoxOutput.implicitHeight + 4) : 0
visible: (type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)
AudioControls.CheckBox {
id: checkBoxOutput
width: parent.width
spacing: margins.sizeCheckBox - boxSize
boxSize: margins.sizeCheckBox / 2
isRound: true
checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD;
checkable: !checked
text: devicename
fontSize: 16
onPressed: {
if (!checked) {
AudioScriptingInterface.setOutputDevice(info, bar.currentIndex === 1);
}
}
}
} }
} }
Item { Item {
id: avatarGainContainer id: avatarGainContainer
x: margins.paddings; x: margins.paddings;
anchors.top: outputView.bottom; anchors.top: pttTextContainer.bottom;
anchors.topMargin: 10; anchors.topMargin: 10;
width: parent.width - margins.paddings*2 width: parent.width - margins.paddings*2
height: avatarGainSliderTextMetrics.height height: avatarGainSliderTextMetrics.height
@ -677,12 +514,174 @@ Rectangle {
} }
} }
AudioControls.PlaySampleSound { Separator {
id: playSampleSound id: secondSeparator;
x: margins.paddings
anchors.top: systemInjectorGainContainer.bottom; anchors.top: systemInjectorGainContainer.bottom;
anchors.topMargin: 10; anchors.topMargin: 10;
} }
}
} Item {
id: inputDeviceHeader
x: margins.paddings;
width: parent.width - margins.paddings*2;
height: 36;
anchors.top: secondSeparator.bottom;
anchors.topMargin: 10;
HiFiGlyphs {
width: margins.sizeCheckBox;
text: hifi.glyphs.mic;
color: hifi.colors.white;
anchors.left: parent.left;
anchors.leftMargin: -size/4; //the glyph has empty space at left about 25%
anchors.verticalCenter: parent.verticalCenter;
size: 30;
}
RalewayRegular {
anchors.verticalCenter: parent.verticalCenter;
width: margins.sizeText + margins.sizeLevel;
anchors.left: parent.left;
anchors.leftMargin: margins.sizeCheckBox;
size: 22;
color: hifi.colors.white;
text: qsTr("Choose input device");
}
}
AudioControls.LoopbackAudio {
id: loopbackAudio
x: margins.paddings
anchors.top: inputDeviceHeader.bottom;
anchors.topMargin: 10;
visible: (bar.currentIndex === 1 && isVR) ||
(bar.currentIndex === 0 && !isVR);
anchors { left: parent.left; leftMargin: margins.paddings }
}
ListView {
id: inputView;
width: rightMostInputLevelPos;
anchors.top: loopbackAudio.visible ? loopbackAudio.bottom : inputDeviceHeader.bottom;
anchors.topMargin: 10;
x: margins.paddings
interactive: false;
height: contentHeight;
clip: true;
model: AudioScriptingInterface.devices.input;
delegate: Item {
width: rightMostInputLevelPos - margins.paddings*2
height: ((type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)) ?
(margins.sizeCheckBox > checkBoxInput.implicitHeight ? margins.sizeCheckBox + 4 : checkBoxInput.implicitHeight + 4) : 0
visible: (type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)
AudioControls.CheckBox {
id: checkBoxInput
anchors.left: parent.left
spacing: margins.sizeCheckBox - boxSize
anchors.verticalCenter: parent.verticalCenter
width: parent.width - inputLevel.width
clip: true
checkable: !checked
checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD;
boxSize: margins.sizeCheckBox / 2
isRound: true
text: devicename
fontSize: 16;
onPressed: {
if (!checked) {
stereoInput.checked = false;
AudioScriptingInterface.setStereoInput(false); // the next selected audio device might not support stereo
AudioScriptingInterface.setInputDevice(info, bar.currentIndex === 1);
}
}
}
AudioControls.InputPeak {
id: inputLevel
anchors.right: parent.right
peak: model.peak;
anchors.verticalCenter: parent.verticalCenter
visible: ((bar.currentIndex === 1 && isVR) ||
(bar.currentIndex === 0 && !isVR)) &&
AudioScriptingInterface.devices.input.peakValuesAvailable;
}
}
}
Separator {
id: thirdSeparator;
anchors.top: inputView.bottom;
anchors.topMargin: 10;
}
Item {
id: outputDeviceHeader;
anchors.topMargin: 10;
anchors.top: thirdSeparator.bottom;
x: margins.paddings;
width: parent.width - margins.paddings*2
height: 36
HiFiGlyphs {
anchors.left: parent.left
anchors.leftMargin: -size/4 //the glyph has empty space at left about 25%
anchors.verticalCenter: parent.verticalCenter;
width: margins.sizeCheckBox
text: hifi.glyphs.unmuted;
color: hifi.colors.white;
size: 36;
}
RalewayRegular {
width: margins.sizeText + margins.sizeLevel
anchors.left: parent.left
anchors.leftMargin: margins.sizeCheckBox
anchors.verticalCenter: parent.verticalCenter;
size: 22;
color: hifi.colors.white;
text: qsTr("Choose output device");
}
}
AudioControls.PlaySampleSound {
id: playSampleSound
x: margins.paddings
anchors.top: outputDeviceHeader.bottom;
anchors.topMargin: 10;
}
ListView {
id: outputView
width: parent.width - margins.paddings*2
x: margins.paddings;
interactive: false;
height: contentHeight + 10;
anchors.top: playSampleSound.bottom;
anchors.topMargin: 10;
clip: true;
model: AudioScriptingInterface.devices.output;
delegate: Item {
width: rightMostInputLevelPos
height: ((type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)) ?
(margins.sizeCheckBox > checkBoxOutput.implicitHeight ? margins.sizeCheckBox + 4 : checkBoxOutput.implicitHeight + 4) : 0
visible: (type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)
AudioControls.CheckBox {
id: checkBoxOutput
width: parent.width
spacing: margins.sizeCheckBox - boxSize
boxSize: margins.sizeCheckBox / 2
isRound: true
checked: bar.currentIndex === 0 ? selectedDesktop : selectedHMD;
checkable: !checked
text: devicename
fontSize: 16
onPressed: {
if (!checked) {
AudioScriptingInterface.setOutputDevice(info, bar.currentIndex === 1);
}
}
}
}
}
}
}

View file

@ -336,8 +336,8 @@ Item {
height: parent.height height: parent.height
colorScheme: hifi.colorSchemes.dark colorScheme: hifi.colorSchemes.dark
minimumValue: 0.25 minimumValue: 0.25
maximumValue: 1.0 maximumValue: 2.0
stepSize: 0.02 stepSize: 0.05
value: Render.viewportResolutionScale value: Render.viewportResolutionScale
live: true live: true

View file

@ -1,6 +1,7 @@
// //
// Created by Dante Ruiz on 6/1/17. // Created by Dante Ruiz on 6/1/17.
// Copyright 2017 High Fidelity, Inc. // Copyright 2017 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -96,7 +97,8 @@ Item {
HifiControls.ImageMessageBox { HifiControls.ImageMessageBox {
id: imageMessageBox id: imageMessageBox
anchors.fill: parent anchors.top: parent.top
anchors.topMargin: 444
z: 2000 z: 2000
imageWidth: 442 imageWidth: 442
imageHeight: 670 imageHeight: 670
@ -179,7 +181,7 @@ Item {
HifiControls.CheckBox { HifiControls.CheckBox {
id: checkBox id: checkBox
colorScheme: hifi.colorSchemes.dark colorScheme: hifi.colorSchemes.dark
text: "show all input devices" text: "Show all input devices"
onClicked: { onClicked: {
box.model = inputPlugins(); box.model = inputPlugins();

View file

@ -1,6 +1,7 @@
// //
// Created by Dante Ruiz on 6/5/17. // Created by Dante Ruiz on 6/5/17.
// Copyright 2017 High Fidelity, Inc. // Copyright 2017 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -71,6 +72,7 @@ Flickable {
property int state: buttonState.disabled property int state: buttonState.disabled
property var lastConfiguration: null property var lastConfiguration: null
property bool isConfiguring: false
HifiConstants { id: hifi } HifiConstants { id: hifi }
@ -360,9 +362,9 @@ Flickable {
RalewayRegular { RalewayRegular {
id: info id: info
text: "See Recommended Tracker Placement" text: "See Recommended Placement"
color: hifi.colors.blueHighlight color: hifi.colors.blueHighlight
size: 10 size: 12
anchors { anchors {
left: additional.right left: additional.right
leftMargin: 10 leftMargin: 10
@ -415,7 +417,6 @@ Flickable {
id: feetBox id: feetBox
width: 15 width: 15
height: 15 height: 15
boxRadius: 7
onClicked: { onClicked: {
if (!checked) { if (!checked) {
@ -446,7 +447,6 @@ Flickable {
id: hipBox id: hipBox
width: 15 width: 15
height: 15 height: 15
boxRadius: 7
onClicked: { onClicked: {
if (checked) { if (checked) {
@ -486,7 +486,6 @@ Flickable {
id: chestBox id: chestBox
width: 15 width: 15
height: 15 height: 15
boxRadius: 7
onClicked: { onClicked: {
if (checked) { if (checked) {
@ -524,7 +523,6 @@ Flickable {
id: shoulderBox id: shoulderBox
width: 15 width: 15
height: 15 height: 15
boxRadius: 7
onClicked: { onClicked: {
if (checked) { if (checked) {
@ -823,7 +821,6 @@ Flickable {
id: viveInDesktop id: viveInDesktop
width: 15 width: 15
height: 15 height: 15
boxRadius: 7
anchors.top: advanceSettings.bottom anchors.top: advanceSettings.bottom
anchors.topMargin: 5 anchors.topMargin: 5
@ -840,9 +837,71 @@ Flickable {
} }
} }
HifiControls.CheckBox {
id: eyeTracking
width: 15
height: 15
anchors.top: viveInDesktop.bottom
anchors.topMargin: 5
anchors.left: openVrConfiguration.left
anchors.leftMargin: leftMargin + 10
onClicked: {
sendConfigurationSettings();
}
}
RalewayBold {
id: eyeTrackingLabel
size: 12
text: "Use eye tracking (if available)."
color: hifi.colors.lightGrayText
anchors {
left: eyeTracking.right
leftMargin: 5
verticalCenter: eyeTracking.verticalCenter
}
}
RalewayRegular {
id: privacyPolicy
text: "Privacy Policy"
color: hifi.colors.blueHighlight
size: 12
anchors {
left: eyeTrackingLabel.right
leftMargin: 10
verticalCenter: eyeTrackingLabel.verticalCenter
}
Rectangle {
id: privacyPolicyUnderline
color: hifi.colors.blueHighlight
width: privacyPolicy.width
height: 1
anchors {
top: privacyPolicy.bottom
topMargin: 1
left: privacyPolicy.left
}
visible: false
}
MouseArea {
anchors.fill: parent;
hoverEnabled: true
onEntered: privacyPolicyUnderline.visible = true;
onExited: privacyPolicyUnderline.visible = false;
onClicked: HiFiAbout.openUrl("https://vircadia.com/privacy-policy");
}
}
Row { Row {
id: outOfRangeDataStrategyRow id: outOfRangeDataStrategyRow
anchors.top: viveInDesktop.bottom anchors.top: eyeTracking.bottom
anchors.topMargin: 5 anchors.topMargin: 5
anchors.left: openVrConfiguration.left anchors.left: openVrConfiguration.left
anchors.leftMargin: leftMargin + 10 anchors.leftMargin: leftMargin + 10
@ -966,6 +1025,8 @@ Flickable {
} }
function displayConfiguration() { function displayConfiguration() {
isConfiguring = true;
var settings = InputConfiguration.configurationSettings(openVrConfiguration.pluginName); var settings = InputConfiguration.configurationSettings(openVrConfiguration.pluginName);
var configurationType = settings["trackerConfiguration"]; var configurationType = settings["trackerConfiguration"];
displayTrackerConfiguration(configurationType); displayTrackerConfiguration(configurationType);
@ -982,6 +1043,7 @@ Flickable {
var viveController = settings["handController"]; var viveController = settings["handController"];
var desktopMode = settings["desktopMode"]; var desktopMode = settings["desktopMode"];
var hmdDesktopPosition = settings["hmdDesktopTracking"]; var hmdDesktopPosition = settings["hmdDesktopTracking"];
var eyeTrackingEnabled = settings["eyeTrackingEnabled"];
armCircumference.realValue = settings.armCircumference; armCircumference.realValue = settings.armCircumference;
shoulderWidth.realValue = settings.shoulderWidth; shoulderWidth.realValue = settings.shoulderWidth;
@ -1004,6 +1066,7 @@ Flickable {
viveInDesktop.checked = desktopMode; viveInDesktop.checked = desktopMode;
hmdInDesktop.checked = hmdDesktopPosition; hmdInDesktop.checked = hmdDesktopPosition;
eyeTracking.checked = eyeTrackingEnabled;
outOfRangeDataStrategyComboBox.currentIndex = outOfRangeDataStrategyComboBox.model.indexOf(settings.outOfRangeDataStrategy); outOfRangeDataStrategyComboBox.currentIndex = outOfRangeDataStrategyComboBox.model.indexOf(settings.outOfRangeDataStrategy);
initializeButtonState(); initializeButtonState();
@ -1014,6 +1077,8 @@ Flickable {
}; };
UserActivityLogger.logAction("mocap_ui_open_dialog", data); UserActivityLogger.logAction("mocap_ui_open_dialog", data);
isConfiguring = false;
} }
function displayTrackerConfiguration(type) { function displayTrackerConfiguration(type) {
@ -1170,6 +1235,7 @@ Flickable {
"shoulderWidth": shoulderWidth.realValue, "shoulderWidth": shoulderWidth.realValue,
"desktopMode": viveInDesktop.checked, "desktopMode": viveInDesktop.checked,
"hmdDesktopTracking": hmdInDesktop.checked, "hmdDesktopTracking": hmdInDesktop.checked,
"eyeTrackingEnabled": eyeTracking.checked,
"outOfRangeDataStrategy": outOfRangeDataStrategyComboBox.model[outOfRangeDataStrategyComboBox.currentIndex] "outOfRangeDataStrategy": outOfRangeDataStrategyComboBox.model[outOfRangeDataStrategyComboBox.currentIndex]
} }
@ -1177,6 +1243,10 @@ Flickable {
} }
function sendConfigurationSettings() { function sendConfigurationSettings() {
if (isConfiguring) {
// Ignore control value changes during dialog initialization.
return;
}
var settings = composeConfigurationSettings(); var settings = composeConfigurationSettings();
InputConfiguration.setConfigurationSettings(settings, openVrConfiguration.pluginName); InputConfiguration.setConfigurationSettings(settings, openVrConfiguration.pluginName);
updateCalibrationButton(); updateCalibrationButton();

View file

@ -4,6 +4,7 @@
// //
// Created by Andrzej Kapolka on 5/10/13. // Created by Andrzej Kapolka on 5/10/13.
// Copyright 2013 High Fidelity, Inc. // Copyright 2013 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -65,6 +66,7 @@
#include <Trace.h> #include <Trace.h>
#include <ResourceScriptingInterface.h> #include <ResourceScriptingInterface.h>
#include <AccountManager.h> #include <AccountManager.h>
#include <DomainAccountManager.h>
#include <AddressManager.h> #include <AddressManager.h>
#include <AnimDebugDraw.h> #include <AnimDebugDraw.h>
#include <BuildInfo.h> #include <BuildInfo.h>
@ -852,6 +854,7 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
#else #else
DependencyManager::set<AccountManager>(true, std::bind(&Application::getUserAgent, qApp)); DependencyManager::set<AccountManager>(true, std::bind(&Application::getUserAgent, qApp));
#endif #endif
DependencyManager::set<DomainAccountManager>();
DependencyManager::set<StatTracker>(); DependencyManager::set<StatTracker>();
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT, defaultScriptsOverrideOption); DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT, defaultScriptsOverrideOption);
DependencyManager::set<Preferences>(); DependencyManager::set<Preferences>();
@ -1348,6 +1351,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
#endif #endif
connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle); connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
connect(domainAccountManager.data(), &DomainAccountManager::authRequired, dialogsManager.data(),
&DialogsManager::showDomainLoginDialog);
connect(domainAccountManager.data(), &DomainAccountManager::loginComplete, this,
&Application::updateWindowTitle);
// ####### TODO: Connect any other signals from domainAccountManager.
// use our MyAvatar position and quat for address manager path // use our MyAvatar position and quat for address manager path
addressManager->setPositionGetter([] { addressManager->setPositionGetter([] {
auto avatarManager = DependencyManager::get<AvatarManager>(); auto avatarManager = DependencyManager::get<AvatarManager>();
@ -1571,7 +1581,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
// Do not show login dialog if requested not to on the command line // Do not show login dialog if requested not to on the command line
QString hifiNoLoginCommandLineKey = QString("--").append(HIFI_NO_LOGIN_COMMAND_LINE_KEY); QString hifiNoLoginCommandLineKey = QString("--").append(HIFI_NO_LOGIN_COMMAND_LINE_KEY);
int index = arguments().indexOf(hifiNoLoginCommandLineKey); int index = arguments().indexOf(hifiNoLoginCommandLineKey);
if (index != -1) { if (index != -1 || _disableLoginScreen) {
resumeAfterLoginDialogActionTaken(); resumeAfterLoginDialogActionTaken();
return; return;
} }
@ -2614,7 +2624,7 @@ QString Application::getUserAgent() {
return userAgent; return userAgent;
} }
QString userAgent = "Mozilla/5.0 (HighFidelityInterface/" + BuildInfo::VERSION + "; " QString userAgent = NetworkingConstants::VIRCADIA_USER_AGENT + "/" + BuildInfo::VERSION + "; "
+ QSysInfo::productType() + " " + QSysInfo::productVersion() + ")"; + QSysInfo::productType() + " " + QSysInfo::productVersion() + ")";
auto formatPluginName = [](QString name) -> QString { return name.trimmed().replace(" ", "-"); }; auto formatPluginName = [](QString name) -> QString { return name.trimmed().replace(" ", "-"); };
@ -2801,6 +2811,7 @@ void Application::cleanupBeforeQuit() {
if (!keepMeLoggedIn) { if (!keepMeLoggedIn) {
DependencyManager::get<AccountManager>()->removeAccountFromFile(); DependencyManager::get<AccountManager>()->removeAccountFromFile();
} }
// ####### TODO
_displayPlugin.reset(); _displayPlugin.reset();
PluginManager::getInstance()->shutdown(); PluginManager::getInstance()->shutdown();
@ -3150,6 +3161,7 @@ extern void setupPreferences();
static void addDisplayPluginToMenu(const DisplayPluginPointer& displayPlugin, int index, bool active = false); static void addDisplayPluginToMenu(const DisplayPluginPointer& displayPlugin, int index, bool active = false);
#endif #endif
// ####### TODO
void Application::showLoginScreen() { void Application::showLoginScreen() {
#if !defined(DISABLE_QML) #if !defined(DISABLE_QML)
auto accountManager = DependencyManager::get<AccountManager>(); auto accountManager = DependencyManager::get<AccountManager>();
@ -3857,6 +3869,11 @@ void Application::showHelp() {
//InfoView::show(INFO_HELP_PATH, false, queryString.toString()); //InfoView::show(INFO_HELP_PATH, false, queryString.toString());
} }
void Application::gotoTutorial() {
const QString TUTORIAL_ADDRESS = "file:///~/serverless/tutorial.json";
DependencyManager::get<AddressManager>()->handleLookupString(TUTORIAL_ADDRESS);
}
void Application::resizeEvent(QResizeEvent* event) { void Application::resizeEvent(QResizeEvent* event) {
resizeGL(); resizeGL();
} }
@ -3934,12 +3951,15 @@ void Application::handleSandboxStatus(QNetworkReply* reply) {
qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMDAndHandControllers; qCDebug(interfaceapp) << "HMD:" << hasHMD << ", Hand Controllers: " << hasHandControllers << ", Using HMD: " << isUsingHMDAndHandControllers;
// when --url in command line, teleport to location
const QString HIFI_URL_COMMAND_LINE_KEY = "--url";
int urlIndex = arguments().indexOf(HIFI_URL_COMMAND_LINE_KEY);
QString addressLookupString; QString addressLookupString;
if (urlIndex != -1) {
QUrl url(arguments().value(urlIndex + 1)); // when --url in command line, teleport to location
QCommandLineParser parser;
QCommandLineOption urlOption("url", "", "value");
parser.addOption(urlOption);
parser.parse(arguments());
if (parser.isSet(urlOption)) {
QUrl url = QUrl(parser.value(urlOption));
if (url.scheme() == URL_SCHEME_HIFIAPP) { if (url.scheme() == URL_SCHEME_HIFIAPP) {
Setting::Handle<QVariant>("startUpApp").set(url.path()); Setting::Handle<QVariant>("startUpApp").set(url.path());
} else { } else {
@ -4065,7 +4085,7 @@ std::map<QString, QString> Application::prepareServerlessDomainContents(QUrl dom
bool success = tmpTree->readFromByteArray(domainURL.toString(), data); bool success = tmpTree->readFromByteArray(domainURL.toString(), data);
if (success) { if (success) {
tmpTree->reaverageOctreeElements(); tmpTree->reaverageOctreeElements();
tmpTree->sendEntities(&_entityEditSender, getEntities()->getTree(), 0, 0, 0); tmpTree->sendEntities(&_entityEditSender, getEntities()->getTree(), "domain", 0, 0, 0);
} }
std::map<QString, QString> namedPaths = tmpTree->getNamedPaths(); std::map<QString, QString> namedPaths = tmpTree->getNamedPaths();
@ -5537,7 +5557,7 @@ bool Application::importEntities(const QString& urlOrFilename, const bool isObse
// FIXME: readFromURL() can take over the main event loop which may cause problems, especially if downloading the JSON // FIXME: readFromURL() can take over the main event loop which may cause problems, especially if downloading the JSON
// from the Web. // from the Web.
success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId); success = _entityClipboard->readFromURL(urlOrFilename, isObservable, callerId, true);
if (success) { if (success) {
_entityClipboard->reaverageOctreeElements(); _entityClipboard->reaverageOctreeElements();
} }
@ -5545,8 +5565,8 @@ bool Application::importEntities(const QString& urlOrFilename, const bool isObse
return success; return success;
} }
QVector<EntityItemID> Application::pasteEntities(float x, float y, float z) { QVector<EntityItemID> Application::pasteEntities(const QString& entityHostType, float x, float y, float z) {
return _entityClipboard->sendEntities(&_entityEditSender, getEntities()->getTree(), x, y, z); return _entityClipboard->sendEntities(&_entityEditSender, getEntities()->getTree(), entityHostType, x, y, z);
} }
void Application::init() { void Application::init() {
@ -7061,19 +7081,23 @@ void Application::updateWindowTitle() const {
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
auto accountManager = DependencyManager::get<AccountManager>(); auto accountManager = DependencyManager::get<AccountManager>();
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
auto isInErrorState = nodeList->getDomainHandler().isInErrorState(); auto isInErrorState = nodeList->getDomainHandler().isInErrorState();
bool isMetaverseLoggedIn = accountManager->isLoggedIn();
bool isDomainLoggedIn = domainAccountManager->isLoggedIn();
QString authedDomain = domainAccountManager->getAuthedDomain();
QString buildVersion = " - Vircadia - " QString buildVersion = " - Vircadia - "
+ (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build")) + (BuildInfo::BUILD_TYPE == BuildInfo::BuildType::Stable ? QString("Version") : QString("Build"))
+ " " + applicationVersion(); + " " + applicationVersion();
QString loginStatus = accountManager->isLoggedIn() ? "" : " (NOT LOGGED IN)";
QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" : QString connectionStatus = isInErrorState ? " (ERROR CONNECTING)" :
nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)"; nodeList->getDomainHandler().isConnected() ? "" : " (NOT CONNECTED)";
QString username = accountManager->getAccountInfo().getUsername();
setCrashAnnotation("sentry[user][username]", username.toStdString()); QString metaverseUsername = accountManager->getAccountInfo().getUsername();
QString domainUsername = domainAccountManager->getUsername();
setCrashAnnotation("sentry[user][username]", metaverseUsername.toStdString());
QString currentPlaceName; QString currentPlaceName;
if (isServerlessMode()) { if (isServerlessMode()) {
@ -7089,8 +7113,22 @@ void Application::updateWindowTitle() const {
} }
} }
QString title = QString() + (!username.isEmpty() ? username + " @ " : QString()) QString metaverseDetails;
+ currentPlaceName + connectionStatus + loginStatus + buildVersion; if (isMetaverseLoggedIn) {
metaverseDetails = "Metaverse: Logged in as " + metaverseUsername;
} else {
metaverseDetails = "Metaverse: Not Logged In";
}
QString domainDetails;
if (currentPlaceName == authedDomain && isDomainLoggedIn) {
domainDetails = "Domain: Logged in as " + domainUsername;
} else {
domainDetails = "Domain: Not Logged In";
}
QString title = QString() + currentPlaceName + connectionStatus + " (" + metaverseDetails + ") (" + domainDetails + ")"
+ buildVersion;
#ifndef WIN32 #ifndef WIN32
// crashes with vs2013/win32 // crashes with vs2013/win32
@ -7650,7 +7688,7 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(url); QNetworkRequest networkRequest = QNetworkRequest(url);
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(networkRequest); QNetworkReply* reply = networkAccessManager.get(networkRequest);
int requestNumber = ++_avatarAttachmentRequest; int requestNumber = ++_avatarAttachmentRequest;
connect(reply, &QNetworkReply::finished, [this, reply, url, requestNumber]() { connect(reply, &QNetworkReply::finished, [this, reply, url, requestNumber]() {

View file

@ -375,7 +375,7 @@ signals:
void awayStateWhenFocusLostInVRChanged(bool enabled); void awayStateWhenFocusLostInVRChanged(bool enabled);
public slots: public slots:
QVector<EntityItemID> pasteEntities(float x, float y, float z); QVector<EntityItemID> pasteEntities(const QString& entityHostType, float x, float y, float z);
bool exportEntities(const QString& filename, const QVector<QUuid>& entityIDs, const glm::vec3* givenOffset = nullptr); bool exportEntities(const QString& filename, const QVector<QUuid>& entityIDs, const glm::vec3* givenOffset = nullptr);
bool exportEntities(const QString& filename, float x, float y, float z, float scale); bool exportEntities(const QString& filename, float x, float y, float z, float scale);
bool importEntities(const QString& url, const bool isObservable = true, const qint64 callerId = -1); bool importEntities(const QString& url, const bool isObservable = true, const qint64 callerId = -1);
@ -425,6 +425,7 @@ public slots:
#endif #endif
static void showHelp(); static void showHelp();
static void gotoTutorial();
void cycleCamera(); void cycleCamera();
void cameraModeChanged(); void cameraModeChanged();
@ -732,6 +733,7 @@ private:
GraphicsEngine _graphicsEngine; GraphicsEngine _graphicsEngine;
void updateRenderArgs(float deltaTime); void updateRenderArgs(float deltaTime);
bool _disableLoginScreen { true };
Overlays _overlays; Overlays _overlays;
ApplicationOverlay _applicationOverlay; ApplicationOverlay _applicationOverlay;

View file

@ -14,8 +14,10 @@
#include "Application.h" #include "Application.h"
#include "ui/DialogsManager.h" #include "ui/DialogsManager.h"
#include <AccountManager.h>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <DomainHandler.h> #include <DomainHandler.h>
#include <DomainAccountManager.h>
#include <AddressManager.h> #include <AddressManager.h>
#include <NodeList.h> #include <NodeList.h>
@ -34,6 +36,10 @@ void ConnectionMonitor::init() {
connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ConnectionMonitor::stopTimer); connect(&domainHandler, &DomainHandler::domainConnectionRefused, this, &ConnectionMonitor::stopTimer);
connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &ConnectionMonitor::stopTimer); connect(&domainHandler, &DomainHandler::redirectToErrorDomainURL, this, &ConnectionMonitor::stopTimer);
connect(this, &ConnectionMonitor::setRedirectErrorState, &domainHandler, &DomainHandler::setRedirectErrorState); connect(this, &ConnectionMonitor::setRedirectErrorState, &domainHandler, &DomainHandler::setRedirectErrorState);
auto accountManager = DependencyManager::get<AccountManager>();
connect(accountManager.data(), &AccountManager::loginComplete, this, &ConnectionMonitor::startTimer);
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
connect(domainAccountManager.data(), &DomainAccountManager::loginComplete, this, &ConnectionMonitor::startTimer);
_timer.setSingleShot(true); _timer.setSingleShot(true);
if (!domainHandler.isConnected()) { if (!domainHandler.isConnected()) {

View file

@ -9,6 +9,10 @@
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
// //
// For happ(ier) development of QML, use these two things:
// This forces QML files to be pulled from the source as you edit it: set environment variable HIFI_USE_SOURCE_TREE_RESOURCES=1
// Use this to live reload: DependencyManager::get<OffscreenUi>()->clearCache();
#include "Menu.h" #include "Menu.h"
#include <QDesktopServices> #include <QDesktopServices>
#include <QFileDialog> #include <QFileDialog>
@ -84,6 +88,13 @@ Menu::Menu() {
dialogsManager.data(), &DialogsManager::toggleLoginDialog); dialogsManager.data(), &DialogsManager::toggleLoginDialog);
} }
auto domainLogin = addActionToQMenuAndActionHash(fileMenu, "Domain: Log In");
connect(domainLogin, &QAction::triggered, [] {
auto dialogsManager = DependencyManager::get<DialogsManager>();
dialogsManager->setDomainLoginState();
dialogsManager->showDomainLoginDialog();
});
// File > Quit // File > Quit
addActionToQMenuAndActionHash(fileMenu, MenuOption::Quit, Qt::CTRL | Qt::Key_Q, qApp, SLOT(quit()), QAction::QuitRole); addActionToQMenuAndActionHash(fileMenu, MenuOption::Quit, Qt::CTRL | Qt::Key_Q, qApp, SLOT(quit()), QAction::QuitRole);
@ -816,6 +827,8 @@ Menu::Menu() {
addActionToQMenuAndActionHash(helpMenu, "Controls Reference", 0, qApp, SLOT(showHelp())); addActionToQMenuAndActionHash(helpMenu, "Controls Reference", 0, qApp, SLOT(showHelp()));
addActionToQMenuAndActionHash(helpMenu, "Tutorial", 0, qApp, SLOT(gotoTutorial()));
helpMenu->addSeparator(); helpMenu->addSeparator();
// Help > Release Notes // Help > Release Notes

View file

@ -24,7 +24,7 @@
#include "ModelPropertiesDialog.h" #include "ModelPropertiesDialog.h"
#include "InterfaceLogging.h" #include "InterfaceLogging.h"
static const int MAX_TEXTURE_SIZE = 1024; static const int MAX_TEXTURE_SIZE = 8192;
void copyDirectoryContent(QDir& from, QDir& to) { void copyDirectoryContent(QDir& from, QDir& to) {
for (auto entry : from.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot | for (auto entry : from.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot |

View file

@ -27,7 +27,7 @@ void PerformanceManager::setupPerformancePresetSettings(bool evaluatePlatformTie
// If evaluatePlatformTier, evalute the Platform Tier and assign the matching Performance profile by default. // If evaluatePlatformTier, evalute the Platform Tier and assign the matching Performance profile by default.
// A bunch of Performance, Simulation and Render settings will be set to a matching default value from this // A bunch of Performance, Simulation and Render settings will be set to a matching default value from this
// Here is the mapping between pelatformTIer and performance profile // Here is the mapping between platformTier and performance profile
const std::array<PerformanceManager::PerformancePreset, platform::Profiler::NumTiers> platformToPerformancePresetMap = { { const std::array<PerformanceManager::PerformancePreset, platform::Profiler::NumTiers> platformToPerformancePresetMap = { {
PerformanceManager::PerformancePreset::MID, // platform::Profiler::UNKNOWN PerformanceManager::PerformancePreset::MID, // platform::Profiler::UNKNOWN
PerformanceManager::PerformancePreset::LOW, // platform::Profiler::LOW PerformanceManager::PerformancePreset::LOW, // platform::Profiler::LOW

View file

@ -722,7 +722,7 @@ public:
* @function MyAvatar.restoreHandAnimation * @function MyAvatar.restoreHandAnimation
* @param isLeft {boolean} Set to true if using the left hand * @param isLeft {boolean} Set to true if using the left hand
* @example <caption> Override left hand animation for three seconds. </caption> * @example <caption> Override left hand animation for three seconds. </caption>
* var ANIM_URL = "https://apidocs.projectathena.dev/models/ClapHands_Standing.fbx"; * var ANIM_URL = "https://apidocs.vircadia.dev/models/ClapHands_Standing.fbx";
* MyAvatar.overrideHandAnimation(isLeft, ANIM_URL, 30, true, 0, 53); * MyAvatar.overrideHandAnimation(isLeft, ANIM_URL, 30, true, 0, 53);
* Script.setTimeout(function () { * Script.setTimeout(function () {
* MyAvatar.restoreHandAnimation(); * MyAvatar.restoreHandAnimation();
@ -780,7 +780,7 @@ public:
* hanging at its sides when it is not moving, the avatar will stand and clap its hands. Note that just as it did before, as soon as the avatar * hanging at its sides when it is not moving, the avatar will stand and clap its hands. Note that just as it did before, as soon as the avatar
* starts to move, the animation will smoothly blend into the walk animation used by the "walkFwd" animation role.</caption> * starts to move, the animation will smoothly blend into the walk animation used by the "walkFwd" animation role.</caption>
* // An animation of the avatar clapping its hands while standing. Restore default after 30s. * // An animation of the avatar clapping its hands while standing. Restore default after 30s.
* var ANIM_URL = "https://apidocs.projectathena.dev/models/ClapHands_Standing.fbx"; * var ANIM_URL = "https://apidocs.vircadia.dev/models/ClapHands_Standing.fbx";
* MyAvatar.overrideRoleAnimation("idleStand", ANIM_URL, 30, true, 0, 53); * MyAvatar.overrideRoleAnimation("idleStand", ANIM_URL, 30, true, 0, 53);
* Script.setTimeout(function () { * Script.setTimeout(function () {
* MyAvatar.restoreRoleAnimation(); * MyAvatar.restoreRoleAnimation();

View file

@ -407,13 +407,13 @@ void MyCharacterController::clearDetailedMotionStates() {
} }
void MyCharacterController::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) { void MyCharacterController::buildPhysicsTransaction(PhysicsEngine::Transaction& transaction) {
for (size_t i = 0; i < _detailedMotionStates.size(); i++) { for (auto& detailedMotionState : _detailedMotionStates) {
_detailedMotionStates[i]->forceActive(); detailedMotionState->forceActive();
} }
if (_pendingFlags & PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION) { if (_pendingFlags & PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION) {
_pendingFlags &= ~PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION; _pendingFlags &= ~PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
for (size_t i = 0; i < _detailedMotionStates.size(); i++) { for (auto& detailedMotionState : _detailedMotionStates) {
transaction.objectsToRemove.push_back(_detailedMotionStates[i]); transaction.objectsToRemove.push_back(detailedMotionState);
} }
// NOTE: the DetailedMotionStates are deleted after being added to PhysicsEngine::Transaction::_objectsToRemove // NOTE: the DetailedMotionStates are deleted after being added to PhysicsEngine::Transaction::_objectsToRemove
// See AvatarManager::handleProcessedPhysicsTransaction() // See AvatarManager::handleProcessedPhysicsTransaction()

View file

@ -116,10 +116,10 @@ void OtherAvatar::updateSpaceProxy(workload::Transaction& transaction) const {
int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) { int OtherAvatar::parseDataFromBuffer(const QByteArray& buffer) {
int32_t bytesRead = Avatar::parseDataFromBuffer(buffer); int32_t bytesRead = Avatar::parseDataFromBuffer(buffer);
for (size_t i = 0; i < _detailedMotionStates.size(); i++) { for (auto& detailedMotionState : _detailedMotionStates) {
// NOTE: we activate _detailedMotionStates is because they are KINEMATIC // NOTE: we activate _detailedMotionStates is because they are KINEMATIC
// and Bullet will automagically call DetailedMotionState::getWorldTransform() when active. // and Bullet will automagically call DetailedMotionState::getWorldTransform() when active.
_detailedMotionStates[i]->forceActive(); detailedMotionState->forceActive();
} }
if (_moving && _motionState) { if (_moving && _motionState) {
_motionState->addDirtyFlags(Simulation::DIRTY_POSITION); _motionState->addDirtyFlags(Simulation::DIRTY_POSITION);

View file

@ -60,10 +60,11 @@ bool ClipboardScriptingInterface::importEntities(
return retVal; return retVal;
} }
QVector<EntityItemID> ClipboardScriptingInterface::pasteEntities(glm::vec3 position) { QVector<EntityItemID> ClipboardScriptingInterface::pasteEntities(glm::vec3 position, const QString& entityHostType) {
QVector<EntityItemID> retVal; QVector<EntityItemID> retVal;
BLOCKING_INVOKE_METHOD(qApp, "pasteEntities", BLOCKING_INVOKE_METHOD(qApp, "pasteEntities",
Q_RETURN_ARG(QVector<EntityItemID>, retVal), Q_RETURN_ARG(QVector<EntityItemID>, retVal),
Q_ARG(const QString&, entityHostType),
Q_ARG(float, position.x), Q_ARG(float, position.x),
Q_ARG(float, position.y), Q_ARG(float, position.y),
Q_ARG(float, position.z)); Q_ARG(float, position.z));

View file

@ -117,10 +117,11 @@ public:
* Pastes the contents of the clipboard into the domain. * Pastes the contents of the clipboard into the domain.
* @function Clipboard.pasteEntities * @function Clipboard.pasteEntities
* @param {Vec3} position - The position to paste the clipboard contents at. * @param {Vec3} position - The position to paste the clipboard contents at.
* @param {Entities.EntityHostType} [entityHostType="domain"] - The type of entities to create.
* @returns {Uuid[]} The IDs of the new entities that were created as a result of the paste operation. If entities couldn't * @returns {Uuid[]} The IDs of the new entities that were created as a result of the paste operation. If entities couldn't
* be created then an empty array is returned. * be created then an empty array is returned.
*/ */
Q_INVOKABLE QVector<EntityItemID> pasteEntities(glm::vec3 position); Q_INVOKABLE QVector<EntityItemID> pasteEntities(glm::vec3 position, const QString& entityHostType = "domain");
}; };
#endif // hifi_ClipboardScriptingInterface_h #endif // hifi_ClipboardScriptingInterface_h

View file

@ -29,6 +29,7 @@
#include "Menu.h" #include "Menu.h"
#include "OffscreenUi.h" #include "OffscreenUi.h"
#include "commerce/QmlCommerce.h" #include "commerce/QmlCommerce.h"
#include "NetworkingConstants.h"
static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation); static const QString DESKTOP_LOCATION = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
static const QString LAST_BROWSE_LOCATION_SETTING = "LastBrowseLocation"; static const QString LAST_BROWSE_LOCATION_SETTING = "LastBrowseLocation";
@ -411,6 +412,10 @@ QString WindowScriptingInterface::checkVersion() {
return QCoreApplication::applicationVersion(); return QCoreApplication::applicationVersion();
} }
QString WindowScriptingInterface::getUserAgent() {
return NetworkingConstants::VIRCADIA_USER_AGENT;
}
QString WindowScriptingInterface::protocolSignature() { QString WindowScriptingInterface::protocolSignature() {
return protocolVersionsSignatureBase64(); return protocolVersionsSignatureBase64();
} }

View file

@ -295,6 +295,13 @@ public slots:
*/ */
QString checkVersion(); QString checkVersion();
/**jsdoc
* Gets Interface's user agent.
* @function Window.getUserAgent
* @returns {string} Interface's user agent.
*/
QString getUserAgent();
/**jsdoc /**jsdoc
* Gets the signature for Interface's protocol version. * Gets the signature for Interface's protocol version.
* @function Window.protocolSignature * @function Window.protocolSignature

View file

@ -4,6 +4,7 @@
// //
// Created by Clement on 1/18/15. // Created by Clement on 1/18/15.
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -109,11 +110,34 @@ void DialogsManager::setDomainConnectionFailureVisibility(bool visible) {
} }
} }
void DialogsManager::setMetaverseLoginState() {
// We're only turning off the domain login trigger but the actual domain auth URL is still saved.
// So we can continue the domain login if desired.
_isDomainLogin = false;
}
void DialogsManager::setDomainLoginState() {
_isDomainLogin = true;
}
void DialogsManager::setDomainLogin(bool isDomainLogin, const QString& domain) {
_isDomainLogin = isDomainLogin;
if (!domain.isEmpty()) {
_domainLoginDomain = domain;
}
}
void DialogsManager::toggleLoginDialog() { void DialogsManager::toggleLoginDialog() {
setDomainLogin(false);
LoginDialog::toggleAction(); LoginDialog::toggleAction();
} }
void DialogsManager::showLoginDialog() { void DialogsManager::showLoginDialog() {
// ####### TODO: May be called from script via DialogsManagerScriptingInterface. Need to handle the case that it's already
// displayed and may be the domain login version.
setDomainLogin(false);
LoginDialog::showWithSelection(); LoginDialog::showWithSelection();
} }
@ -121,10 +145,22 @@ void DialogsManager::hideLoginDialog() {
LoginDialog::hide(); LoginDialog::hide();
} }
void DialogsManager::showDomainLoginDialog(const QString& domain) {
setDomainLogin(true, domain);
LoginDialog::showWithSelection();
}
// #######: TODO: Domain version of toggleLoginDialog()?
// #######: TODO: Domain version of hideLoginDialog()?
void DialogsManager::showUpdateDialog() { void DialogsManager::showUpdateDialog() {
UpdateDialog::show(); UpdateDialog::show();
} }
void DialogsManager::octreeStatsDetails() { void DialogsManager::octreeStatsDetails() {
if (!_octreeStatsDialog) { if (!_octreeStatsDialog) {
_octreeStatsDialog = new OctreeStatsDialog(qApp->getWindow(), qApp->getOcteeSceneStats()); _octreeStatsDialog = new OctreeStatsDialog(qApp->getWindow(), qApp->getOcteeSceneStats());

View file

@ -4,6 +4,7 @@
// //
// Created by Clement on 1/18/15. // Created by Clement on 1/18/15.
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -40,6 +41,10 @@ public:
QPointer<TestingDialog> getTestingDialog() const { return _testingDialog; } QPointer<TestingDialog> getTestingDialog() const { return _testingDialog; }
void emitAddressBarShown(bool visible) { emit addressBarShown(visible); } void emitAddressBarShown(bool visible) { emit addressBarShown(visible); }
void setAddressBarVisible(bool addressBarVisible); void setAddressBarVisible(bool addressBarVisible);
void setMetaverseLoginState();
void setDomainLoginState();
bool getIsDomainLogin() { return _isDomainLogin; }
QString getDomainLoginDomain() { return _domainLoginDomain; }
public slots: public slots:
void showAddressBar(); void showAddressBar();
@ -49,6 +54,7 @@ public slots:
void toggleLoginDialog(); void toggleLoginDialog();
void showLoginDialog(); void showLoginDialog();
void hideLoginDialog(); void hideLoginDialog();
void showDomainLoginDialog(const QString& domain = "");
void octreeStatsDetails(); void octreeStatsDetails();
void lodTools(); void lodTools();
void hmdTools(bool showTools); void hmdTools(bool showTools);
@ -82,6 +88,10 @@ private:
QPointer<DomainConnectionDialog> _domainConnectionDialog; QPointer<DomainConnectionDialog> _domainConnectionDialog;
bool _dialogCreatedWhileShown { false }; bool _dialogCreatedWhileShown { false };
bool _addressBarVisible { false }; bool _addressBarVisible { false };
void setDomainLogin(bool isDomainLogin, const QString& domain = "");
bool _isDomainLogin { false };
QString _domainLoginDomain;
}; };
#endif // hifi_DialogsManager_h #endif // hifi_DialogsManager_h

View file

@ -4,6 +4,7 @@
// //
// Created by Bradley Austin Davis on 2015/04/14 // Created by Bradley Austin Davis on 2015/04/14
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -24,7 +25,9 @@
#include <UserActivityLogger.h> #include <UserActivityLogger.h>
#include "AccountManager.h" #include "AccountManager.h"
#include "DomainAccountManager.h"
#include "DependencyManager.h" #include "DependencyManager.h"
#include "DialogsManager.h"
#include "Menu.h" #include "Menu.h"
#include "Application.h" #include "Application.h"
@ -38,12 +41,17 @@ const QUrl LOGIN_DIALOG = PathUtils::qmlUrl("OverlayLoginDialog.qml");
LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) { LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) {
auto accountManager = DependencyManager::get<AccountManager>(); auto accountManager = DependencyManager::get<AccountManager>();
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
// the login hasn't been dismissed yet if the user isn't logged in and is encouraged to login. // the login hasn't been dismissed yet if the user isn't logged in and is encouraged to login.
#if !defined(Q_OS_ANDROID) #if !defined(Q_OS_ANDROID)
connect(accountManager.data(), &AccountManager::loginComplete, connect(accountManager.data(), &AccountManager::loginComplete,
this, &LoginDialog::handleLoginCompleted); this, &LoginDialog::handleLoginCompleted);
connect(accountManager.data(), &AccountManager::loginFailed, connect(accountManager.data(), &AccountManager::loginFailed,
this, &LoginDialog::handleLoginFailed); this, &LoginDialog::handleLoginFailed);
connect(domainAccountManager.data(), &DomainAccountManager::loginComplete,
this, &LoginDialog::handleLoginCompleted);
connect(domainAccountManager.data(), &DomainAccountManager::loginFailed,
this, &LoginDialog::handleLoginFailed);
connect(qApp, &Application::loginDialogFocusEnabled, this, &LoginDialog::focusEnabled); connect(qApp, &Application::loginDialogFocusEnabled, this, &LoginDialog::focusEnabled);
connect(qApp, &Application::loginDialogFocusDisabled, this, &LoginDialog::focusDisabled); connect(qApp, &Application::loginDialogFocusDisabled, this, &LoginDialog::focusDisabled);
connect(this, SIGNAL(dismissedLoginDialog()), qApp, SLOT(onDismissedLoginDialog())); connect(this, SIGNAL(dismissedLoginDialog()), qApp, SLOT(onDismissedLoginDialog()));
@ -91,14 +99,16 @@ void LoginDialog::toggleAction() {
if (accountManager->isLoggedIn()) { if (accountManager->isLoggedIn()) {
// change the menu item to logout // change the menu item to logout
loginAction->setText("Logout " + accountManager->getAccountInfo().getUsername()); loginAction->setText("Metaverse: Logout " + accountManager->getAccountInfo().getUsername());
connection = connect(loginAction, &QAction::triggered, accountManager.data(), &AccountManager::logout); connection = connect(loginAction, &QAction::triggered, accountManager.data(), &AccountManager::logout);
} else { } else {
// change the menu item to login // change the menu item to login
loginAction->setText("Log In / Sign Up"); loginAction->setText("Metaverse: Log In / Sign Up");
connection = connect(loginAction, &QAction::triggered, [] { connection = connect(loginAction, &QAction::triggered, [] {
// if not in login state, show. // if not in login state, show.
if (!qApp->getLoginDialogPoppedUp()) { if (!qApp->getLoginDialogPoppedUp()) {
auto dialogsManager = DependencyManager::get<DialogsManager>();
dialogsManager->setMetaverseLoginState();
LoginDialog::showWithSelection(); LoginDialog::showWithSelection();
} }
}); });
@ -131,10 +141,15 @@ void LoginDialog::dismissLoginDialog() {
} }
void LoginDialog::login(const QString& username, const QString& password) const { void LoginDialog::login(const QString& username, const QString& password) const {
qDebug() << "Attempting to login " << username; qDebug() << "Attempting to login" << username;
DependencyManager::get<AccountManager>()->requestAccessToken(username, password); DependencyManager::get<AccountManager>()->requestAccessToken(username, password);
} }
void LoginDialog::loginDomain(const QString& username, const QString& password) const {
qDebug() << "Attempting to login" << username << "into a domain";
DependencyManager::get<DomainAccountManager>()->requestAccessToken(username, password);
}
void LoginDialog::loginThroughOculus() { void LoginDialog::loginThroughOculus() {
qDebug() << "Attempting to login through Oculus"; qDebug() << "Attempting to login through Oculus";
if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) { if (auto oculusPlatformPlugin = PluginManager::getInstance()->getOculusPlatformPlugin()) {
@ -410,3 +425,11 @@ void LoginDialog::signupFailed(QNetworkReply* reply) {
emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE); emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE);
} }
} }
bool LoginDialog::getDomainLoginRequested() const {
return DependencyManager::get<DialogsManager>()->getIsDomainLogin();
}
QString LoginDialog::getDomainLoginDomain() const {
return DependencyManager::get<DialogsManager>()->getDomainLoginDomain();
}

View file

@ -4,6 +4,7 @@
// //
// Created by Bradley Austin Davis on 2015/04/14 // Created by Bradley Austin Davis on 2015/04/14
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -71,6 +72,7 @@ protected slots:
Q_INVOKABLE QString oculusUserID() const; Q_INVOKABLE QString oculusUserID() const;
Q_INVOKABLE void login(const QString& username, const QString& password) const; Q_INVOKABLE void login(const QString& username, const QString& password) const;
Q_INVOKABLE void loginDomain(const QString& username, const QString& password) const;
Q_INVOKABLE void loginThroughSteam(); Q_INVOKABLE void loginThroughSteam();
Q_INVOKABLE void linkSteam(); Q_INVOKABLE void linkSteam();
Q_INVOKABLE void createAccountFromSteam(QString username = QString()); Q_INVOKABLE void createAccountFromSteam(QString username = QString());
@ -81,6 +83,10 @@ protected slots:
Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password); Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password);
Q_INVOKABLE bool getLoginDialogPoppedUp() const; Q_INVOKABLE bool getLoginDialogPoppedUp() const;
Q_INVOKABLE bool getDomainLoginRequested() const;
Q_INVOKABLE QString getDomainLoginDomain() const;
}; };
#endif // hifi_LoginDialog_h #endif // hifi_LoginDialog_h

View file

@ -27,6 +27,7 @@
#include <ThreadHelpers.h> #include <ThreadHelpers.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
#include <SharedUtil.h> #include <SharedUtil.h>
const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "skeletons", "attachments" }; const char* MODEL_TYPE_NAMES[] = { "entities", "heads", "skeletons", "skeletons", "attachments" };
@ -225,7 +226,7 @@ void ModelHandler::update() {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest request(url); QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
QNetworkReply* reply = networkAccessManager.head(request); QNetworkReply* reply = networkAccessManager.head(request);
connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); connect(reply, SIGNAL(finished()), SLOT(downloadFinished()));
} }
@ -278,7 +279,7 @@ void ModelHandler::queryNewFiles(QString marker) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest request(url); QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(request); QNetworkReply* reply = networkAccessManager.get(request);
connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); connect(reply, SIGNAL(finished()), SLOT(downloadFinished()));

View file

@ -143,13 +143,13 @@ set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR})
include(CPackComponent) include(CPackComponent)
set(CPACK_PACKAGE_NAME "HQ Launcher") set(CPACK_PACKAGE_NAME "HQ Launcher")
set(CPACK_PACKAGE_VENDOR "High Fidelity") set(CPACK_PACKAGE_VENDOR "Vircadia")
set(CPACK_PACKAGE_VERSION ${BUILD_VERSION}) set(CPACK_PACKAGE_VERSION ${BUILD_VERSION})
set(CPACK_PACKAGE_FILE_NAME "HQ Launcher") set(CPACK_PACKAGE_FILE_NAME "HQ Launcher")
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME}) set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
set(DMG_SUBFOLDER_NAME "High Fidelity") set(DMG_SUBFOLDER_NAME "Vircadia")
set(ESCAPED_DMG_SUBFOLDER_NAME "") set(ESCAPED_DMG_SUBFOLDER_NAME "")
set(DMG_SUBFOLDER_ICON "${CMAKE_SOURCE_DIR}/cmake/installer/install-folder.rsrc") set(DMG_SUBFOLDER_ICON "${CMAKE_SOURCE_DIR}/cmake/installer/install-folder.rsrc")

View file

@ -276,12 +276,12 @@ if (APPLE)
include(CPackComponent) include(CPackComponent)
set(CPACK_PACKAGE_NAME "HQ Launcher") set(CPACK_PACKAGE_NAME "HQ Launcher")
set(CPACK_PACKAGE_VENDOR "High Fidelity") set(CPACK_PACKAGE_VENDOR "Vircadia")
set(CPACK_PACKAGE_FILE_NAME "HQ Launcher") set(CPACK_PACKAGE_FILE_NAME "HQ Launcher")
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME}) set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
set(DMG_SUBFOLDER_NAME "High Fidelity") set(DMG_SUBFOLDER_NAME "Vircadia")
set(ESCAPED_DMG_SUBFOLDER_NAME "") set(ESCAPED_DMG_SUBFOLDER_NAME "")
set(DMG_SUBFOLDER_ICON "${CMAKE_SOURCE_DIR}/cmake/installer/install-folder.rsrc") set(DMG_SUBFOLDER_ICON "${CMAKE_SOURCE_DIR}/cmake/installer/install-folder.rsrc")

View file

@ -2,7 +2,7 @@ import QtQuick 2.3
import QtQuick 2.1 import QtQuick 2.1
Text { Text {
text: "High Fidelity" text: "Vircadia"
font.bold: true font.bold: true
font.family: "Graphik Semibold" font.family: "Graphik Semibold"
font.pixelSize: 17 font.pixelSize: 17

View file

@ -21,7 +21,7 @@ Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) {
_launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get()); _launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get());
_launcherWindow->rootContext()->setContextProperty("PathUtils", new PathUtils()); _launcherWindow->rootContext()->setContextProperty("PathUtils", new PathUtils());
_launcherWindow->rootContext()->setContextProperty("Platform", platform); _launcherWindow->rootContext()->setContextProperty("Platform", platform);
_launcherWindow->setTitle("High Fidelity"); _launcherWindow->setTitle("Vircadia");
_launcherWindow->setFlags(Qt::FramelessWindowHint | Qt::Window); _launcherWindow->setFlags(Qt::FramelessWindowHint | Qt::Window);
_launcherWindow->setLauncherStatePtr(_launcherState); _launcherWindow->setLauncherStatePtr(_launcherState);

View file

@ -12,7 +12,7 @@
#include <QStandardPaths> #include <QStandardPaths>
#include <QFileInfo> #include <QFileInfo>
#include <QFile> #include <QFile>
#include <QDebug> #include <QDebug>
#include <QTime> #include <QTime>
@ -253,7 +253,7 @@ void LauncherInstaller::createApplicationRegistryKeys() {
success = insertRegistryKey(REGISTRY_PATH, "UninstallString", uninstallPath); success = insertRegistryKey(REGISTRY_PATH, "UninstallString", uninstallPath);
success = insertRegistryKey(REGISTRY_PATH, "DisplayVersion", std::string(LAUNCHER_BUILD_VERSION)); success = insertRegistryKey(REGISTRY_PATH, "DisplayVersion", std::string(LAUNCHER_BUILD_VERSION));
success = insertRegistryKey(REGISTRY_PATH, "DisplayIcon", applicationExe); success = insertRegistryKey(REGISTRY_PATH, "DisplayIcon", applicationExe);
success = insertRegistryKey(REGISTRY_PATH, "Publisher", "High Fidelity"); success = insertRegistryKey(REGISTRY_PATH, "Publisher", "Vircadia");
auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now()); auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());

View file

@ -31,7 +31,7 @@ bool hasSuffix(const std::string& path, const std::string& suffix) {
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QCoreApplication::setOrganizationName("High Fidelity"); QCoreApplication::setOrganizationName("Vircadia");
QCoreApplication::setApplicationName("HQ Launcher"); QCoreApplication::setApplicationName("HQ Launcher");
Q_INIT_RESOURCE(resources); Q_INIT_RESOURCE(resources);

View file

@ -310,7 +310,7 @@ BOOL LauncherManager::getAndCreatePaths(PathType type, CString& outPath) {
outPath += DIRECTORY_NAME_INTERFACE; outPath += DIRECTORY_NAME_INTERFACE;
} else if (type == PathType::Content_Directory) { } else if (type == PathType::Content_Directory) {
outPath += DIRECTORY_NAME_CONTENT; outPath += DIRECTORY_NAME_CONTENT;
} }
return (CreateDirectory(outPath, NULL) || ERROR_ALREADY_EXISTS == GetLastError()); return (CreateDirectory(outPath, NULL) || ERROR_ALREADY_EXISTS == GetLastError());
} }
} }
@ -377,7 +377,7 @@ BOOL LauncherManager::createConfigJSON() {
return TRUE; return TRUE;
} }
LauncherUtils::ResponseError LauncherManager::readConfigJSON(CString& version, CString& domain, LauncherUtils::ResponseError LauncherManager::readConfigJSON(CString& version, CString& domain,
CString& content, bool& loggedIn, CString& organizationBuildTag) { CString& content, bool& loggedIn, CString& organizationBuildTag) {
CString configPath; CString configPath;
getAndCreatePaths(PathType::Interface_Directory, configPath); getAndCreatePaths(PathType::Interface_Directory, configPath);
@ -388,7 +388,7 @@ LauncherUtils::ResponseError LauncherManager::readConfigJSON(CString& version, C
} }
Json::Value config; Json::Value config;
configFile >> config; configFile >> config;
if (config["version"].isString() && if (config["version"].isString() &&
config["domain"].isString() && config["domain"].isString() &&
config["content"].isString()) { config["content"].isString()) {
loggedIn = config["loggedIn"].asBool(); loggedIn = config["loggedIn"].asBool();
@ -446,7 +446,7 @@ LauncherUtils::ResponseError LauncherManager::readOrganizationJSON(const CString
CString url = _T("/organizations/") + hash + _T(".json"); CString url = _T("/organizations/") + hash + _T(".json");
LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(getHttpUserAgent(), LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(getHttpUserAgent(),
true, L"orgs.highfidelity.com", url, true, L"orgs.highfidelity.com", url,
contentTypeJson, CStringA(), contentTypeJson, CStringA(),
response, false); response, false);
if (error != LauncherUtils::ResponseError::NoError) { if (error != LauncherUtils::ResponseError::NoError) {
return error; return error;
@ -557,7 +557,7 @@ void LauncherManager::onMostRecentBuildsReceived(const CString& response, Launch
addToLog(_T("Already running most recent build. Launching interface.exe")); addToLog(_T("Already running most recent build. Launching interface.exe"));
} else { } else {
addToLog(_T("Updating the launcher was not allowed --noUpdate")); addToLog(_T("Updating the launcher was not allowed --noUpdate"));
} }
if (isInstalled) { if (isInstalled) {
addToLog(_T("Installed version: ") + currentVersion); addToLog(_T("Installed version: ") + currentVersion);
if (!newInterfaceVersion) { if (!newInterfaceVersion) {
@ -576,7 +576,7 @@ void LauncherManager::onMostRecentBuildsReceived(const CString& response, Launch
} }
} }
_shouldWait = FALSE; _shouldWait = FALSE;
} else { } else {
setFailed(true); setFailed(true);
CString msg; CString msg;
@ -587,7 +587,7 @@ void LauncherManager::onMostRecentBuildsReceived(const CString& response, Launch
} }
} }
LauncherUtils::ResponseError LauncherManager::getAccessTokenForCredentials(const CString& username, LauncherUtils::ResponseError LauncherManager::getAccessTokenForCredentials(const CString& username,
const CString& password) { const CString& password) {
CStringA post = "grant_type=password&username="; CStringA post = "grant_type=password&username=";
post += username; post += username;
@ -599,9 +599,9 @@ LauncherUtils::ResponseError LauncherManager::getAccessTokenForCredentials(const
CString response; CString response;
LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(getHttpUserAgent(), LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(getHttpUserAgent(),
true, true,
L"metaverse.highfidelity.com", L"metaverse.highfidelity.com",
L"/oauth/token", L"/oauth/token",
contentTypeText, post, contentTypeText, post,
response, true); response, true);
if (error != LauncherUtils::ResponseError::NoError) { if (error != LauncherUtils::ResponseError::NoError) {
return error; return error;
@ -629,7 +629,7 @@ BOOL LauncherManager::createApplicationRegistryKeys(int size) {
success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "UninstallString", uninstallPath); success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "UninstallString", uninstallPath);
success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "DisplayVersion", LauncherUtils::cStringToStd(_latestVersion)); success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "DisplayVersion", LauncherUtils::cStringToStd(_latestVersion));
success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "DisplayIcon", applicationExe); success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "DisplayIcon", applicationExe);
success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "Publisher", "High Fidelity"); success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "Publisher", "Vircadia");
success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "InstallDate", LauncherUtils::cStringToStd(CTime::GetCurrentTime().Format("%Y%m%d"))); success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "InstallDate", LauncherUtils::cStringToStd(CTime::GetCurrentTime().Format("%Y%m%d")));
success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "EstimatedSize", (DWORD)size); success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "EstimatedSize", (DWORD)size);
success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "NoModify", (DWORD)1); success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "NoModify", (DWORD)1);
@ -686,9 +686,9 @@ BOOL LauncherManager::extractApplication() {
updateProgress(ProcessType::UnzipApplication, max(progress, 0.0f)); updateProgress(ProcessType::UnzipApplication, max(progress, 0.0f));
}; };
_currentProcess = ProcessType::UnzipApplication; _currentProcess = ProcessType::UnzipApplication;
BOOL success = LauncherUtils::unzipFileOnThread(ProcessType::UnzipApplication, BOOL success = LauncherUtils::unzipFileOnThread(ProcessType::UnzipApplication,
LauncherUtils::cStringToStd(_applicationZipPath), LauncherUtils::cStringToStd(_applicationZipPath),
LauncherUtils::cStringToStd(installPath), LauncherUtils::cStringToStd(installPath),
onExtractFinished, onProgress); onExtractFinished, onProgress);
if (success) { if (success) {
addToLog(_T("Created thread for unzipping application.")); addToLog(_T("Created thread for unzipping application."));
@ -737,7 +737,7 @@ void LauncherManager::restartNewLauncher() {
continueAction = ContinueActionOnStart::ContinueUpdate; continueAction = ContinueActionOnStart::ContinueUpdate;
} else if (_keepLoggingIn) { } else if (_keepLoggingIn) {
continueAction = ContinueActionOnStart::ContinueLogIn; continueAction = ContinueActionOnStart::ContinueLogIn;
} }
CStringW params; CStringW params;
params.Format(_T(" --restart --noUpdate --continueAction %s"), getContinueActionParam(continueAction)); params.Format(_T(" --restart --noUpdate --continueAction %s"), getContinueActionParam(continueAction));
LauncherUtils::launchApplication(_tempLauncherPath, params.GetBuffer()); LauncherUtils::launchApplication(_tempLauncherPath, params.GetBuffer());

View file

@ -26,6 +26,9 @@
static const int MAX_TARGET_MARKERS = 30; static const int MAX_TARGET_MARKERS = 30;
static const float JOINT_CHAIN_INTERP_TIME = 0.5f; static const float JOINT_CHAIN_INTERP_TIME = 0.5f;
static QTime debounceJointWarningsClock;
static const int JOINT_WARNING_DEBOUNCE_TIME = 30000; // 30 seconds
static void lookupJointInfo(const AnimInverseKinematics::JointChainInfo& jointChainInfo, static void lookupJointInfo(const AnimInverseKinematics::JointChainInfo& jointChainInfo,
int indexA, int indexB, int indexA, int indexB,
const AnimInverseKinematics::JointInfo** jointInfoA, const AnimInverseKinematics::JointInfo** jointInfoA,
@ -91,6 +94,7 @@ AnimInverseKinematics::IKTargetVar::IKTargetVar(const IKTargetVar& orig) :
} }
AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimNode::Type::InverseKinematics, id) { AnimInverseKinematics::AnimInverseKinematics(const QString& id) : AnimNode(AnimNode::Type::InverseKinematics, id) {
debounceJointWarningsClock.start();
} }
AnimInverseKinematics::~AnimInverseKinematics() { AnimInverseKinematics::~AnimInverseKinematics() {
@ -158,6 +162,14 @@ void AnimInverseKinematics::setTargetVars(const QString& jointName, const QStrin
} }
} }
bool debounceJointWarnings() {
if (debounceJointWarningsClock.elapsed() >= JOINT_WARNING_DEBOUNCE_TIME) {
debounceJointWarningsClock.restart();
return true;
}
return false;
}
void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses) { void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::vector<IKTarget>& targets, const AnimPoseVec& underPoses) {
_hipsTargetIndex = -1; _hipsTargetIndex = -1;
@ -172,7 +184,7 @@ void AnimInverseKinematics::computeTargets(const AnimVariantMap& animVars, std::
if (jointIndex >= 0) { if (jointIndex >= 0) {
// this targetVar has a valid joint --> cache the indices // this targetVar has a valid joint --> cache the indices
targetVar.jointIndex = jointIndex; targetVar.jointIndex = jointIndex;
} else { } else if (debounceJointWarnings()) {
qCWarning(animation) << "AnimInverseKinematics could not find jointName" << targetVar.jointName << "in skeleton"; qCWarning(animation) << "AnimInverseKinematics could not find jointName" << targetVar.jointName << "in skeleton";
} }
} }

View file

@ -51,7 +51,7 @@ void AutoUpdater::getLatestVersionData() {
QNetworkRequest latestVersionRequest(buildsURL); QNetworkRequest latestVersionRequest(buildsURL);
latestVersionRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); latestVersionRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
latestVersionRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); latestVersionRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(latestVersionRequest); QNetworkReply* reply = networkAccessManager.get(latestVersionRequest);
connect(reply, &QNetworkReply::finished, this, &AutoUpdater::parseLatestVersionData); connect(reply, &QNetworkReply::finished, this, &AutoUpdater::parseLatestVersionData);
} }

View file

@ -14,6 +14,7 @@
#include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkReply>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
#include <SharedUtil.h> #include <SharedUtil.h>
#include <PathUtils.h> #include <PathUtils.h>
@ -62,7 +63,7 @@ void JSBaker::loadScript() {
// setup the request to follow re-directs and always hit the network // setup the request to follow re-directs and always hit the network
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
networkRequest.setUrl(_jsURL); networkRequest.setUrl(_jsURL);

View file

@ -13,6 +13,7 @@
#include <PathUtils.h> #include <PathUtils.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
#include <DependencyManager.h> #include <DependencyManager.h>
#include <hfm/ModelFormatRegistry.h> #include <hfm/ModelFormatRegistry.h>
@ -159,7 +160,7 @@ void ModelBaker::saveSourceModel() {
// setup the request to follow re-directs and always hit the network // setup the request to follow re-directs and always hit the network
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
networkRequest.setUrl(_modelURL); networkRequest.setUrl(_modelURL);

View file

@ -19,6 +19,7 @@
#include <image/TextureProcessing.h> #include <image/TextureProcessing.h>
#include <ktx/KTX.h> #include <ktx/KTX.h>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
#include <SharedUtil.h> #include <SharedUtil.h>
#include <TextureMeta.h> #include <TextureMeta.h>
@ -99,7 +100,7 @@ void TextureBaker::loadTexture() {
// setup the request to follow re-directs and always hit the network // setup the request to follow re-directs and always hit the network
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork); networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::AlwaysNetwork);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
networkRequest.setUrl(_textureURL); networkRequest.setUrl(_textureURL);

View file

@ -1337,7 +1337,7 @@ void ModelEntityRenderer::doRenderUpdateSynchronousTyped(const ScenePointer& sce
_model->setCullWithParent(_cullWithParent); _model->setCullWithParent(_cullWithParent);
_model->setRenderWithZones(_renderWithZones); _model->setRenderWithZones(_renderWithZones);
emit requestRenderUpdate(); emit requestRenderUpdate();
if(didVisualGeometryRequestSucceed) { if (didVisualGeometryRequestSucceed) {
emit DependencyManager::get<scriptable::ModelProviderFactory>()-> emit DependencyManager::get<scriptable::ModelProviderFactory>()->
modelAddedToScene(entity->getEntityItemID(), NestableType::Entity, _model); modelAddedToScene(entity->getEntityItemID(), NestableType::Entity, _model);
} }

View file

@ -199,7 +199,7 @@ float importanceSample3DDimension(float startDim) {
return dimension; return dimension;
} }
ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties, ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createParticle(const Transform& baseTransform, const particle::Properties& particleProperties,
const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource,
const TriangleInfo& triangleInfo) { const TriangleInfo& triangleInfo) {
CpuParticle particle; CpuParticle particle;
@ -217,7 +217,7 @@ ParticleEffectEntityRenderer::CpuParticle ParticleEffectEntityRenderer::createPa
const auto& polarFinish = particleProperties.polar.finish; const auto& polarFinish = particleProperties.polar.finish;
particle.seed = randFloatInRange(-1.0f, 1.0f); particle.seed = randFloatInRange(-1.0f, 1.0f);
particle.expiration = now + (uint64_t)(particleProperties.lifespan * USECS_PER_SECOND); particle.expiration = (uint64_t)(particleProperties.lifespan * USECS_PER_SECOND);
particle.relativePosition = glm::vec3(0.0f); particle.relativePosition = glm::vec3(0.0f);
particle.basePosition = baseTransform.getTranslation(); particle.basePosition = baseTransform.getTranslation();
@ -403,7 +403,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
computeTriangles(geometryResource->getHFMModel()); computeTriangles(geometryResource->getHFMModel());
} }
// emit particle // emit particle
_cpuParticles.push_back(createParticle(now, modelTransform, particleProperties, shapeType, geometryResource, _triangleInfo)); _cpuParticles.push_back(createParticle(modelTransform, particleProperties, shapeType, geometryResource, _triangleInfo));
_timeUntilNextEmit = emitInterval; _timeUntilNextEmit = emitInterval;
if (emitInterval < timeRemaining) { if (emitInterval < timeRemaining) {
timeRemaining -= emitInterval; timeRemaining -= emitInterval;
@ -415,7 +415,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
} }
// Kill any particles that have expired or are over the max size // Kill any particles that have expired or are over the max size
while (_cpuParticles.size() > particleProperties.maxParticles || (!_cpuParticles.empty() && _cpuParticles.front().expiration <= now)) { while (_cpuParticles.size() > particleProperties.maxParticles || (!_cpuParticles.empty() && _cpuParticles.front().expiration == 0)) {
_cpuParticles.pop_front(); _cpuParticles.pop_front();
} }
@ -428,6 +428,7 @@ void ParticleEffectEntityRenderer::stepSimulation() {
} }
particle.basePosition = modelTransform.getTranslation(); particle.basePosition = modelTransform.getTranslation();
} }
particle.expiration = particle.expiration >= interval ? particle.expiration - interval : 0;
particle.integrate(deltaTime); particle.integrate(deltaTime);
} }
_prevEmitterShouldTrail = particleProperties.emission.shouldTrail; _prevEmitterShouldTrail = particleProperties.emission.shouldTrail;

View file

@ -88,7 +88,7 @@ private:
glm::mat4 transform; glm::mat4 transform;
} _triangleInfo; } _triangleInfo;
static CpuParticle createParticle(uint64_t now, const Transform& baseTransform, const particle::Properties& particleProperties, static CpuParticle createParticle(const Transform& baseTransform, const particle::Properties& particleProperties,
const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource, const ShapeType& shapeType, const GeometryResource::Pointer& geometryResource,
const TriangleInfo& triangleInfo); const TriangleInfo& triangleInfo);
void stepSimulation(); void stepSimulation();

View file

@ -45,7 +45,7 @@ private:
PulsePropertyGroup _pulseProperties; PulsePropertyGroup _pulseProperties;
std::shared_ptr<graphics::ProceduralMaterial> _material { std::make_shared<graphics::ProceduralMaterial>() }; std::shared_ptr<graphics::ProceduralMaterial> _material { std::make_shared<graphics::ProceduralMaterial>() };
glm::vec3 _color { NAN }; glm::vec3 _color { NAN };
float _alpha; float _alpha { NAN };
glm::vec3 _position; glm::vec3 _position;
glm::vec3 _dimensions; glm::vec3 _dimensions;

View file

@ -2399,7 +2399,9 @@ signals:
/**jsdoc /**jsdoc
* Triggered when an avatar enters an entity, but only if the entity has an entity method exposed for this event. * Triggered when an avatar enters an entity.
* Note: At the initial loading of the script, if the avatar is already present inside the entity, it might be too late
* to catch this event when the script runs, so it won't trigger. The {@link Entities.preload|preload} signal can be used to handle those cases.
* <p>See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.</p> * <p>See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.</p>
* @function Entities.enterEntity * @function Entities.enterEntity
* @param {Uuid} entityID - The ID of the entity that the avatar entered. * @param {Uuid} entityID - The ID of the entity that the avatar entered.
@ -2408,7 +2410,7 @@ signals:
void enterEntity(const EntityItemID& entityItemID); void enterEntity(const EntityItemID& entityItemID);
/**jsdoc /**jsdoc
* Triggered when an avatar leaves an entity, but only if the entity has an entity method exposed for this event. * Triggered when an avatar leaves an entity.
* <p>See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.</p> * <p>See also, {@link Entities|Entity Methods} and {@link Script.addEventHandler}.</p>
* @function Entities.leaveEntity * @function Entities.leaveEntity
* @param {Uuid} entityID - The ID of the entity that the avatar left. * @param {Uuid} entityID - The ID of the entity that the avatar left.

View file

@ -537,7 +537,7 @@ bool EntityTree::updateEntity(EntityItemPointer entity, const EntityItemProperti
return true; return true;
} }
EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone) { EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone, const bool isImport) {
EntityItemProperties props = properties; EntityItemProperties props = properties;
auto nodeList = DependencyManager::get<NodeList>(); auto nodeList = DependencyManager::get<NodeList>();
@ -548,7 +548,8 @@ EntityItemPointer EntityTree::addEntity(const EntityItemID& entityID, const Enti
if (properties.getEntityHostType() == entity::HostType::DOMAIN && getIsClient() && if (properties.getEntityHostType() == entity::HostType::DOMAIN && getIsClient() &&
!nodeList->getThisNodeCanRez() && !nodeList->getThisNodeCanRezTmp() && !nodeList->getThisNodeCanRez() && !nodeList->getThisNodeCanRezTmp() &&
!nodeList->getThisNodeCanRezCertified() && !nodeList->getThisNodeCanRezTmpCertified() && !_serverlessDomain && !isClone) { !nodeList->getThisNodeCanRezCertified() && !nodeList->getThisNodeCanRezTmpCertified() &&
!_serverlessDomain && !isClone && !isImport) {
return nullptr; return nullptr;
} }
@ -2233,15 +2234,24 @@ void EntityTree::fixupNeedsParentFixups() {
} }
entity->postParentFixup(); entity->postParentFixup();
} else if (getIsServer() || _avatarIDs.contains(entity->getParentID())) { } else {
// this is a child of an avatar, which the entity server will never have bool needsUpdate = getIsServer();
// a SpatiallyNestable object for. Add it to a list for cleanup when the avatar leaves. if (!needsUpdate) {
if (!_childrenOfAvatars.contains(entity->getParentID())) { std::lock_guard<std::mutex> lock(_avatarIDsLock);
_childrenOfAvatars[entity->getParentID()] = QSet<EntityItemID>(); needsUpdate = _avatarIDs.contains(entity->getParentID());
}
if (needsUpdate) {
std::lock_guard<std::mutex> lock(_childrenOfAvatarsLock);
// this is a child of an avatar, which the entity server will never have
// a SpatiallyNestable object for. Add it to a list for cleanup when the avatar leaves.
if (!_childrenOfAvatars.contains(entity->getParentID())) {
_childrenOfAvatars[entity->getParentID()] = QSet<EntityItemID>();
}
_childrenOfAvatars[entity->getParentID()] += entity->getEntityItemID();
doMove = true;
iter.remove(); // and pull it out of the list
} }
_childrenOfAvatars[entity->getParentID()] += entity->getEntityItemID();
doMove = true;
iter.remove(); // and pull it out of the list
} }
if (queryAACubeSuccess && doMove) { if (queryAACubeSuccess && doMove) {
@ -2261,8 +2271,19 @@ void EntityTree::fixupNeedsParentFixups() {
} }
} }
void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) { void EntityTree::knowAvatarID(const QUuid& avatarID) {
QHash<QUuid, QSet<EntityItemID>>::const_iterator itr = _childrenOfAvatars.constFind(avatarID); std::lock_guard<std::mutex> lock(_avatarIDsLock);
_avatarIDs += avatarID;
}
void EntityTree::forgetAvatarID(const QUuid& avatarID) {
std::lock_guard<std::mutex> lock(_avatarIDsLock);
_avatarIDs -= avatarID;
}
void EntityTree::deleteDescendantsOfAvatar(const QUuid& avatarID) {
std::lock_guard<std::mutex> lock(_childrenOfAvatarsLock);
auto itr = _childrenOfAvatars.find(avatarID);
if (itr != _childrenOfAvatars.end()) { if (itr != _childrenOfAvatars.end()) {
if (!itr.value().empty()) { if (!itr.value().empty()) {
std::vector<EntityItemID> ids; std::vector<EntityItemID> ids;
@ -2280,8 +2301,10 @@ void EntityTree::deleteDescendantsOfAvatar(QUuid avatarID) {
void EntityTree::removeFromChildrenOfAvatars(EntityItemPointer entity) { void EntityTree::removeFromChildrenOfAvatars(EntityItemPointer entity) {
QUuid avatarID = entity->getParentID(); QUuid avatarID = entity->getParentID();
if (_childrenOfAvatars.contains(avatarID)) { std::lock_guard<std::mutex> lock(_childrenOfAvatarsLock);
_childrenOfAvatars[avatarID].remove(entity->getID()); auto itr = _childrenOfAvatars.find(avatarID);
if (itr != _childrenOfAvatars.end()) {
itr.value().remove(entity->getID());
} }
} }
@ -2657,11 +2680,12 @@ QByteArray EntityTree::remapActionDataIDs(QByteArray actionData, QHash<EntityIte
} }
QVector<EntityItemID> EntityTree::sendEntities(EntityEditPacketSender* packetSender, EntityTreePointer localTree, QVector<EntityItemID> EntityTree::sendEntities(EntityEditPacketSender* packetSender, EntityTreePointer localTree,
float x, float y, float z) { const QString& entityHostType, float x, float y, float z) {
SendEntitiesOperationArgs args; SendEntitiesOperationArgs args;
args.ourTree = this; args.ourTree = this;
args.otherTree = localTree; args.otherTree = localTree;
args.root = glm::vec3(x, y, z); args.root = glm::vec3(x, y, z);
args.entityHostType = entityHostType;
// If this is called repeatedly (e.g., multiple pastes with the same data), the new elements will clash unless we // If this is called repeatedly (e.g., multiple pastes with the same data), the new elements will clash unless we
// use new identifiers. We need to keep a map so that we can map parent identifiers correctly. // use new identifiers. We need to keep a map so that we can map parent identifiers correctly.
QHash<EntityItemID, EntityItemID> map; QHash<EntityItemID, EntityItemID> map;
@ -2751,6 +2775,11 @@ bool EntityTree::sendEntitiesOperation(const OctreeElementPointer& element, void
EntityItemID newID = getMapped(oldID); EntityItemID newID = getMapped(oldID);
EntityItemProperties properties = item->getProperties(); EntityItemProperties properties = item->getProperties();
properties.setEntityHostTypeFromString(args->entityHostType);
if (properties.getEntityHostType() == entity::HostType::AVATAR) {
properties.setOwningAvatarID(AVATAR_SELF_ID);
}
EntityItemID oldParentID = properties.getParentID(); EntityItemID oldParentID = properties.getParentID();
if (oldParentID.isInvalidID()) { // no parent if (oldParentID.isInvalidID()) { // no parent
properties.setPosition(properties.getPosition() + args->root); properties.setPosition(properties.getPosition() + args->root);
@ -2764,6 +2793,17 @@ bool EntityTree::sendEntitiesOperation(const OctreeElementPointer& element, void
} }
} }
QVector<QUuid> oldRenderWithZones = properties.getRenderWithZones();
if (!oldRenderWithZones.isEmpty()) {
QVector<QUuid> newRenderWithZones;
for (QUuid oldRenderWithZoneID : oldRenderWithZones) {
if (args->ourTree->findEntityByEntityItemID(oldRenderWithZoneID)) {
newRenderWithZones.append(getMapped(oldRenderWithZoneID));
}
}
properties.setRenderWithZones(newRenderWithZones);
}
properties.setXNNeighborID(getMapped(properties.getXNNeighborID())); properties.setXNNeighborID(getMapped(properties.getXNNeighborID()));
properties.setXPNeighborID(getMapped(properties.getXPNeighborID())); properties.setXPNeighborID(getMapped(properties.getXPNeighborID()));
properties.setYNNeighborID(getMapped(properties.getYNNeighborID())); properties.setYNNeighborID(getMapped(properties.getYNNeighborID()));
@ -2926,7 +2966,7 @@ void convertGrabUserDataToProperties(EntityItemProperties& properties) {
} }
bool EntityTree::readFromMap(QVariantMap& map) { bool EntityTree::readFromMap(QVariantMap& map, const bool isImport) {
// These are needed to deal with older content (before adding inheritance modes) // These are needed to deal with older content (before adding inheritance modes)
int contentVersion = map["Version"].toInt(); int contentVersion = map["Version"].toInt();
@ -3096,7 +3136,7 @@ bool EntityTree::readFromMap(QVariantMap& map) {
} }
} }
EntityItemPointer entity = addEntity(entityItemID, properties); EntityItemPointer entity = addEntity(entityItemID, properties, isImport);
if (!entity) { if (!entity) {
qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType(); qCDebug(entities) << "adding Entity failed:" << entityItemID << properties.getType();
success = false; success = false;

View file

@ -40,6 +40,7 @@ public:
class SendEntitiesOperationArgs { class SendEntitiesOperationArgs {
public: public:
glm::vec3 root; glm::vec3 root;
QString entityHostType;
EntityTree* ourTree; EntityTree* ourTree;
EntityTreePointer otherTree; EntityTreePointer otherTree;
QHash<EntityItemID, EntityItemID>* map; QHash<EntityItemID, EntityItemID>* map;
@ -117,7 +118,7 @@ public:
// The newer API... // The newer API...
void postAddEntity(EntityItemPointer entityItem); void postAddEntity(EntityItemPointer entityItem);
EntityItemPointer addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone = false); EntityItemPointer addEntity(const EntityItemID& entityID, const EntityItemProperties& properties, bool isClone = false, const bool isImport = false);
// use this method if you only know the entityID // use this method if you only know the entityID
bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr)); bool updateEntity(const EntityItemID& entityID, const EntityItemProperties& properties, const SharedNodePointer& senderNode = SharedNodePointer(nullptr));
@ -177,7 +178,7 @@ public:
static QByteArray remapActionDataIDs(QByteArray actionData, QHash<EntityItemID, EntityItemID>& map); static QByteArray remapActionDataIDs(QByteArray actionData, QHash<EntityItemID, EntityItemID>& map);
QVector<EntityItemID> sendEntities(EntityEditPacketSender* packetSender, EntityTreePointer localTree, QVector<EntityItemID> sendEntities(EntityEditPacketSender* packetSender, EntityTreePointer localTree,
float x, float y, float z); const QString& entityHostType, float x, float y, float z);
void entityChanged(EntityItemPointer entity); void entityChanged(EntityItemPointer entity);
@ -195,7 +196,7 @@ public:
virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues, virtual bool writeToMap(QVariantMap& entityDescription, OctreeElementPointer element, bool skipDefaultValues,
bool skipThoseWithBadParents) override; bool skipThoseWithBadParents) override;
virtual bool readFromMap(QVariantMap& entityDescription) override; virtual bool readFromMap(QVariantMap& entityDescription, const bool isImport = false) override;
virtual bool writeToJSON(QString& jsonString, const OctreeElementPointer& element) override; virtual bool writeToJSON(QString& jsonString, const OctreeElementPointer& element) override;
@ -239,9 +240,9 @@ public:
Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name) const; Q_INVOKABLE int getJointIndex(const QUuid& entityID, const QString& name) const;
Q_INVOKABLE QStringList getJointNames(const QUuid& entityID) const; Q_INVOKABLE QStringList getJointNames(const QUuid& entityID) const;
void knowAvatarID(QUuid avatarID) { _avatarIDs += avatarID; } void knowAvatarID(const QUuid& avatarID);
void forgetAvatarID(QUuid avatarID) { _avatarIDs -= avatarID; } void forgetAvatarID(const QUuid& avatarID);
void deleteDescendantsOfAvatar(QUuid avatarID); void deleteDescendantsOfAvatar(const QUuid& avatarID);
void removeFromChildrenOfAvatars(EntityItemPointer entity); void removeFromChildrenOfAvatars(EntityItemPointer entity);
void addToNeedsParentFixupList(EntityItemPointer entity); void addToNeedsParentFixupList(EntityItemPointer entity);
@ -364,8 +365,10 @@ protected:
QVector<EntityItemWeakPointer> _needsParentFixup; // entites with a parentID but no (yet) known parent instance QVector<EntityItemWeakPointer> _needsParentFixup; // entites with a parentID but no (yet) known parent instance
mutable QReadWriteLock _needsParentFixupLock; mutable QReadWriteLock _needsParentFixupLock;
std::mutex _avatarIDsLock;
// we maintain a list of avatarIDs to notice when an entity is a child of one. // we maintain a list of avatarIDs to notice when an entity is a child of one.
QSet<QUuid> _avatarIDs; // IDs of avatars connected to entity server QSet<QUuid> _avatarIDs; // IDs of avatars connected to entity server
std::mutex _childrenOfAvatarsLock;
QHash<QUuid, QSet<EntityItemID>> _childrenOfAvatars; // which entities are children of which avatars QHash<QUuid, QSet<EntityItemID>> _childrenOfAvatars; // which entities are children of which avatars
float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME }; float _maxTmpEntityLifetime { DEFAULT_MAX_TMP_ENTITY_LIFETIME };

View file

@ -127,7 +127,7 @@ void appendIndex(MeshData& data, QVector<int>& indices, int index, bool deduplic
glm::vec4 color; glm::vec4 color;
bool hasColors = (data.colors.size() > 1); bool hasColors = (data.colors.size() > 0);
if (hasColors) { if (hasColors) {
int colorIndex = data.colorsByVertex ? vertexIndex : index; int colorIndex = data.colorsByVertex ? vertexIndex : index;
if (data.colorIndices.isEmpty()) { if (data.colorIndices.isEmpty()) {

View file

@ -17,6 +17,7 @@
#include <QNetworkRequest> #include <QNetworkRequest>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
#include <SharedUtil.h> #include <SharedUtil.h>
QVariantHash FSTReader::parseMapping(QIODevice* device) { QVariantHash FSTReader::parseMapping(QIODevice* device) {
@ -253,7 +254,7 @@ QVariantHash FSTReader::downloadMapping(const QString& url) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest networkRequest = QNetworkRequest(url); QNetworkRequest networkRequest = QNetworkRequest(url);
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(networkRequest); QNetworkReply* reply = networkAccessManager.get(networkRequest);
QEventLoop loop; QEventLoop loop;
QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);

View file

@ -1055,6 +1055,11 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
int indicesAccessorIdx = primitive.indices; int indicesAccessorIdx = primitive.indices;
if (indicesAccessorIdx > _file.accessors.size()) {
qWarning(modelformat) << "Indices accessor index is out of bounds for model " << _url;
continue;
}
GLTFAccessor& indicesAccessor = _file.accessors[indicesAccessorIdx]; GLTFAccessor& indicesAccessor = _file.accessors[indicesAccessorIdx];
// Buffers // Buffers
@ -1093,6 +1098,11 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
foreach(auto &key, keys) { foreach(auto &key, keys) {
int accessorIdx = primitive.attributes.values[key]; int accessorIdx = primitive.attributes.values[key];
if (accessorIdx > _file.accessors.size()) {
qWarning(modelformat) << "Accessor index is out of bounds for model " << _url;
continue;
}
GLTFAccessor& accessor = _file.accessors[accessorIdx]; GLTFAccessor& accessor = _file.accessors[accessorIdx];
if (key == "POSITION") { if (key == "POSITION") {
@ -1239,6 +1249,11 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
int v2_index = (indices[n + 1] * 3); int v2_index = (indices[n + 1] * 3);
int v3_index = (indices[n + 2] * 3); int v3_index = (indices[n + 2] * 3);
if (v1_index + 2 >= vertices.size() || v2_index + 2 >= vertices.size() || v3_index + 2 >= vertices.size()) {
qWarning(modelformat) << "Indices out of range for model " << _url;
break;
}
glm::vec3 v1 = glm::vec3(vertices[v1_index], vertices[v1_index + 1], vertices[v1_index + 2]); glm::vec3 v1 = glm::vec3(vertices[v1_index], vertices[v1_index + 1], vertices[v1_index + 2]);
glm::vec3 v2 = glm::vec3(vertices[v2_index], vertices[v2_index + 1], vertices[v2_index + 2]); glm::vec3 v2 = glm::vec3(vertices[v2_index], vertices[v2_index + 1], vertices[v2_index + 2]);
glm::vec3 v3 = glm::vec3(vertices[v3_index], vertices[v3_index + 1], vertices[v3_index + 2]); glm::vec3 v3 = glm::vec3(vertices[v3_index], vertices[v3_index + 1], vertices[v3_index + 2]);
@ -1333,7 +1348,7 @@ bool GLTFSerializer::buildGeometry(HFMModel& hfmModel, const hifi::VariantHash&
} }
if (validatedIndices.size() == 0) { if (validatedIndices.size() == 0) {
qWarning(modelformat) << "Indices out of range for model " << _url; qWarning(modelformat) << "No valid indices for model " << _url;
continue; continue;
} }

View file

@ -15,7 +15,7 @@
#define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f #define OVERSUBSCRIBED_PRESSURE_VALUE 0.95f
#define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f #define UNDERSUBSCRIBED_PRESSURE_VALUE 0.85f
#define DEFAULT_ALLOWED_TEXTURE_MEMORY_MB ((size_t)1024) #define DEFAULT_ALLOWED_TEXTURE_MEMORY_MB ((size_t)2048)
#define MAX_RESOURCE_TEXTURES_PER_FRAME 2 #define MAX_RESOURCE_TEXTURES_PER_FRAME 2
#define NO_BUFFER_WORK_SLEEP_TIME_MS 2 #define NO_BUFFER_WORK_SLEEP_TIME_MS 2
#define THREADED_TEXTURE_BUFFERING 1 #define THREADED_TEXTURE_BUFFERING 1

View file

@ -51,7 +51,7 @@ using ColorType = glm::vec3;
#define HFM_COLOR_ELEMENT gpu::Element::VEC3F_XYZ #define HFM_COLOR_ELEMENT gpu::Element::VEC3F_XYZ
#endif #endif
const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 2048 * 2048; const int MAX_NUM_PIXELS_FOR_FBX_TEXTURE = 8192 * 8192;
using ShapeVertices = std::vector<glm::vec3>; using ShapeVertices = std::vector<glm::vec3>;

View file

@ -41,7 +41,7 @@ using namespace gpu;
static const glm::uvec2 SPARSE_PAGE_SIZE(128); static const glm::uvec2 SPARSE_PAGE_SIZE(128);
static const glm::uvec2 MAX_TEXTURE_SIZE_GLES(2048); static const glm::uvec2 MAX_TEXTURE_SIZE_GLES(2048);
static const glm::uvec2 MAX_TEXTURE_SIZE_GL(4096); static const glm::uvec2 MAX_TEXTURE_SIZE_GL(8192);
bool DEV_DECIMATE_TEXTURES = false; bool DEV_DECIMATE_TEXTURES = false;
std::atomic<size_t> DECIMATED_TEXTURE_COUNT{ 0 }; std::atomic<size_t> DECIMATED_TEXTURE_COUNT{ 0 };
std::atomic<size_t> RECTIFIED_TEXTURE_COUNT{ 0 }; std::atomic<size_t> RECTIFIED_TEXTURE_COUNT{ 0 };

View file

@ -508,7 +508,9 @@ bool AccountManager::checkAndSignalForAccessToken() {
if (!hasToken) { if (!hasToken) {
// emit a signal so somebody can call back to us and request an access token given a username and password // emit a signal so somebody can call back to us and request an access token given a username and password
emit authRequired();
// Dialog can be hidden immediately after showing if we've just teleported to the domain, unless the signal is delayed.
QTimer::singleShot(500, this, [this] { emit this->authRequired(); });
} }
return hasToken; return hasToken;

View file

@ -58,7 +58,7 @@ const auto METAVERSE_SESSION_ID_HEADER = QString("HFM-SessionID").toLocal8Bit();
using UserAgentGetter = std::function<QString()>; using UserAgentGetter = std::function<QString()>;
const auto DEFAULT_USER_AGENT_GETTER = []() -> QString { return HIGH_FIDELITY_USER_AGENT; }; const auto DEFAULT_USER_AGENT_GETTER = []() -> QString { return NetworkingConstants::VIRCADIA_USER_AGENT; };
class AccountManager : public QObject, public Dependency { class AccountManager : public QObject, public Dependency {
Q_OBJECT Q_OBJECT

View file

@ -249,8 +249,9 @@ public slots:
* Takes you to a specified metaverse address. * Takes you to a specified metaverse address.
* @function location.handleLookupString * @function location.handleLookupString
* @param {string} address - The address to go to: a <code>"hifi://"</code> address, an IP address (e.g., * @param {string} address - The address to go to: a <code>"hifi://"</code> address, an IP address (e.g.,
* <code>"127.0.0.1"</code> or <code>"localhost"</code>), a domain name, a named path on a domain (starts with * <code>"127.0.0.1"</code> or <code>"localhost"</code>), a <code>file:///</code> address, a domain name, a named path
* <code>"/"</code>), a position or position and orientation, or a user (starts with <code>"@"</code>). * on a domain (starts with <code>"/"</code>), a position or position and orientation, or a user (starts with
* <code>"@"</code>).
* @param {boolean} [fromSuggestions=false] - Set to <code>true</code> if the address is obtained from the "Goto" dialog. * @param {boolean} [fromSuggestions=false] - Set to <code>true</code> if the address is obtained from the "Goto" dialog.
* Helps ensure that user's location history is correctly maintained. * Helps ensure that user's location history is correctly maintained.
*/ */

View file

@ -0,0 +1,203 @@
//
// DomainAccountManager.cpp
// libraries/networking/src
//
// Created by David Rowe on 23 Jul 2020.
// Copyright 2020 Vircadia contributors.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#include "DomainAccountManager.h"
#include <QTimer>
#include <QtCore/QJsonObject>
#include <QtCore/QJsonDocument>
#include <QtNetwork/QNetworkRequest>
#include <QtNetwork/QNetworkReply>
#include <DependencyManager.h>
#include <SettingHandle.h>
#include "NetworkingConstants.h"
#include "NetworkAccessManager.h"
#include "NetworkLogging.h"
#include "NodeList.h"
// FIXME: Generalize to other OAuth2 sources for domain login.
const bool VERBOSE_HTTP_REQUEST_DEBUGGING = false;
// ####### TODO: Enable and use these?
// ####### TODO: Add storing domain URL and check against it when retrieving values?
// ####### TODO: Add storing _authURL and check against it when retrieving values?
/*
Setting::Handle<QString> domainAccessToken {"private/domainAccessToken", "" };
Setting::Handle<QString> domainAccessRefreshToken {"private/domainAccessToken", "" };
Setting::Handle<int> domainAccessTokenExpiresIn {"private/domainAccessTokenExpiresIn", -1 };
Setting::Handle<QString> domainAccessTokenType {"private/domainAccessTokenType", "" };
*/
DomainAccountManager::DomainAccountManager() :
_authURL(),
_username(),
_access_token(),
_refresh_token(),
_domain_name()
{
connect(this, &DomainAccountManager::loginComplete, this, &DomainAccountManager::sendInterfaceAccessTokenToServer);
}
void DomainAccountManager::setAuthURL(const QUrl& authURL) {
if (_authURL != authURL) {
_authURL = authURL;
qCDebug(networking) << "AccountManager URL for authenticated requests has been changed to" << qPrintable(_authURL.toString());
_access_token = "";
_refresh_token = "";
// ####### TODO: Restore and refresh OAuth2 tokens if have them for this domain.
// ####### TODO: Handle "keep me logged in".
}
}
bool DomainAccountManager::isLoggedIn() {
return !_authURL.isEmpty() && hasValidAccessToken();
}
void DomainAccountManager::requestAccessToken(const QString& username, const QString& password) {
_username = username;
_access_token = "";
_refresh_token = "";
QNetworkRequest request;
request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
// miniOrange WordPress API Authentication plugin:
// - Requires "client_id" parameter.
// - Ignores "state" parameter.
QByteArray formData;
formData.append("grant_type=password&");
formData.append("username=" + QUrl::toPercentEncoding(username) + "&");
formData.append("password=" + QUrl::toPercentEncoding(password) + "&");
formData.append("client_id=" + _clientID);
request.setUrl(_authURL);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkReply* requestReply = networkAccessManager.post(request, formData);
connect(requestReply, &QNetworkReply::finished, this, &DomainAccountManager::requestAccessTokenFinished);
}
void DomainAccountManager::requestAccessTokenFinished() {
QNetworkReply* requestReply = reinterpret_cast<QNetworkReply*>(sender());
QJsonDocument jsonResponse = QJsonDocument::fromJson(requestReply->readAll());
const QJsonObject& rootObject = jsonResponse.object();
auto httpStatus = requestReply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
if (200 <= httpStatus && httpStatus < 300) {
// miniOrange plugin provides no scope.
if (rootObject.contains("access_token")) {
// Success.
auto nodeList = DependencyManager::get<NodeList>();
_domain_name = nodeList->getDomainHandler().getHostname();
QUrl rootURL = requestReply->url();
rootURL.setPath("");
setTokensFromJSON(rootObject, rootURL);
emit loginComplete();
} else {
// Failure.
qCDebug(networking) << "Received a response for password grant that is missing one or more expected values.";
emit loginFailed();
}
} else {
// Failure.
qCDebug(networking) << "Error in response for password grant -" << httpStatus << requestReply->error()
<< "-" << rootObject["error"].toString() << rootObject["error_description"].toString();
emit loginFailed();
}
}
void DomainAccountManager::sendInterfaceAccessTokenToServer() {
emit newTokens();
}
bool DomainAccountManager::accessTokenIsExpired() {
// ####### TODO: accessTokenIsExpired()
return true;
/*
return domainAccessTokenExpiresIn.get() != -1 && domainAccessTokenExpiresIn.get() <= QDateTime::currentMSecsSinceEpoch();
*/
}
bool DomainAccountManager::hasValidAccessToken() {
// ###### TODO: wire this up to actually retrieve a token (based on session or storage) and confirm that it is in fact valid and relevant to the current domain.
// QString currentDomainAccessToken = domainAccessToken.get();
QString currentDomainAccessToken = _access_token;
// if (currentDomainAccessToken.isEmpty() || accessTokenIsExpired()) {
if (currentDomainAccessToken.isEmpty()) {
if (VERBOSE_HTTP_REQUEST_DEBUGGING) {
qCDebug(networking) << "An access token is required for requests to"
<< qPrintable(_authURL.toString());
}
return false;
}
// ####### TODO
// if (!_isWaitingForTokenRefresh && needsToRefreshToken()) {
// refreshAccessToken();
// }
return true;
}
void DomainAccountManager::setTokensFromJSON(const QJsonObject& jsonObject, const QUrl& url) {
_access_token = jsonObject["access_token"].toString();
_refresh_token = jsonObject["refresh_token"].toString();
// ####### TODO: Enable and use these?
// ####### TODO: Protect these per AccountManager?
// ######: TODO: clientID needed?
// qCDebug(networking) << "Storing a domain account with access-token for" << qPrintable(url.toString());
// domainAccessToken.set(jsonObject["access_token"].toString());
// domainAccessRefreshToken.set(jsonObject["refresh_token"].toString());
// domainAccessTokenExpiresIn.set(QDateTime::currentMSecsSinceEpoch() + (jsonObject["expires_in"].toDouble() * 1000));
// domainAccessTokenType.set(jsonObject["token_type"].toString());
}
bool DomainAccountManager::checkAndSignalForAccessToken() {
bool hasToken = hasValidAccessToken();
// ####### TODO: Handle hasToken == true.
// It causes the login dialog not to display (OK) but somewhere the domain server needs to be sent it (and if domain server
// gets error when trying to use it then user should be prompted to login).
hasToken = false;
if (!hasToken) {
// Emit a signal so somebody can call back to us and request an access token given a user name and password.
// Dialog can be hidden immediately after showing if we've just teleported to the domain, unless the signal is delayed.
auto domain = _authURL.host();
QTimer::singleShot(500, this, [this, domain] {
emit this->authRequired(domain);
});
}
return hasToken;
}

View file

@ -0,0 +1,66 @@
//
// DomainAccountManager.h
// libraries/networking/src
//
// Created by David Rowe on 23 Jul 2020.
// Copyright 2020 Vircadia contributors.
//
// 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_DomainAccountManager_h
#define hifi_DomainAccountManager_h
#include <QtCore/QObject>
#include <QtCore/QUrl>
#include <DependencyManager.h>
class DomainAccountManager : public QObject, public Dependency {
Q_OBJECT
public:
DomainAccountManager();
void setAuthURL(const QUrl& authURL);
void setClientID(const QString& clientID) { _clientID = clientID; }
QString getUsername() { return _username; }
QString getAccessToken() { return _access_token; }
QString getRefreshToken() { return _refresh_token; }
QString getAuthedDomain() { return _domain_name; }
bool isLoggedIn();
Q_INVOKABLE bool checkAndSignalForAccessToken();
public slots:
void requestAccessToken(const QString& username, const QString& password);
void requestAccessTokenFinished();
signals:
void authRequired(const QString& domain);
void loginComplete();
void loginFailed();
void logoutComplete();
void newTokens();
private slots:
private:
bool hasValidAccessToken();
bool accessTokenIsExpired();
void setTokensFromJSON(const QJsonObject&, const QUrl& url);
void sendInterfaceAccessTokenToServer();
QUrl _authURL;
QString _clientID;
QString _username; // ####### TODO: Store elsewhere?
QString _access_token; // ####... ""
QString _refresh_token; // ####... ""
QString _domain_name; // ####... ""
};
#endif // hifi_DomainAccountManager_h

View file

@ -4,6 +4,7 @@
// //
// Created by Stephen Birarda on 2/18/2014. // Created by Stephen Birarda on 2/18/2014.
// Copyright 2014 High Fidelity, Inc. // Copyright 2014 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -24,6 +25,7 @@
#include "AddressManager.h" #include "AddressManager.h"
#include "Assignment.h" #include "Assignment.h"
#include "DomainAccountManager.h"
#include "HifiSockAddr.h" #include "HifiSockAddr.h"
#include "NodeList.h" #include "NodeList.h"
#include "udt/Packet.h" #include "udt/Packet.h"
@ -144,7 +146,8 @@ void DomainHandler::hardReset(QString reason) {
bool DomainHandler::isHardRefusal(int reasonCode) { bool DomainHandler::isHardRefusal(int reasonCode) {
return (reasonCode == (int)ConnectionRefusedReason::ProtocolMismatch || return (reasonCode == (int)ConnectionRefusedReason::ProtocolMismatch ||
reasonCode == (int)ConnectionRefusedReason::TooManyUsers || reasonCode == (int)ConnectionRefusedReason::TooManyUsers ||
reasonCode == (int)ConnectionRefusedReason::NotAuthorized || reasonCode == (int)ConnectionRefusedReason::NotAuthorizedMetaverse ||
reasonCode == (int)ConnectionRefusedReason::NotAuthorizedDomain ||
reasonCode == (int)ConnectionRefusedReason::TimedOut); reasonCode == (int)ConnectionRefusedReason::TimedOut);
} }
@ -219,6 +222,8 @@ void DomainHandler::setURLAndID(QUrl domainURL, QUuid domainID) {
QString previousHost = _domainURL.host(); QString previousHost = _domainURL.host();
_domainURL = domainURL; _domainURL = domainURL;
_hasCheckedForDomainAccessToken = false;
if (previousHost != domainURL.host()) { if (previousHost != domainURL.host()) {
qCDebug(networking) << "Updated domain hostname to" << domainURL.host(); qCDebug(networking) << "Updated domain hostname to" << domainURL.host();
@ -489,16 +494,33 @@ void DomainHandler::processICEResponsePacket(QSharedPointer<ReceivedMessage> mes
} }
} }
bool DomainHandler::reasonSuggestsLogin(ConnectionRefusedReason reasonCode) { bool DomainHandler::reasonSuggestsMetaverseLogin(ConnectionRefusedReason reasonCode) {
switch (reasonCode) { switch (reasonCode) {
case ConnectionRefusedReason::LoginError: case ConnectionRefusedReason::LoginErrorMetaverse:
case ConnectionRefusedReason::NotAuthorized: case ConnectionRefusedReason::NotAuthorizedMetaverse:
return true; return true;
default: default:
case ConnectionRefusedReason::Unknown: case ConnectionRefusedReason::Unknown:
case ConnectionRefusedReason::ProtocolMismatch: case ConnectionRefusedReason::ProtocolMismatch:
case ConnectionRefusedReason::TooManyUsers: case ConnectionRefusedReason::TooManyUsers:
case ConnectionRefusedReason::NotAuthorizedDomain:
return false;
}
return false;
}
bool DomainHandler::reasonSuggestsDomainLogin(ConnectionRefusedReason reasonCode) {
switch (reasonCode) {
case ConnectionRefusedReason::LoginErrorDomain:
case ConnectionRefusedReason::NotAuthorizedDomain:
return true;
default:
case ConnectionRefusedReason::Unknown:
case ConnectionRefusedReason::ProtocolMismatch:
case ConnectionRefusedReason::TooManyUsers:
case ConnectionRefusedReason::NotAuthorizedMetaverse:
return false; return false;
} }
return false; return false;
@ -528,7 +550,9 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
// output to the log so the user knows they got a denied connection request // output to the log so the user knows they got a denied connection request
// and check and signal for an access token so that we can make sure they are logged in // and check and signal for an access token so that we can make sure they are logged in
qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage << " extraInfo:" << extraInfo; QString sanitizedExtraInfo = extraInfo.toLower().startsWith("http") ? "" : extraInfo; // Don't log URLs.
qCWarning(networking) << "The domain-server denied a connection request: " << reasonMessage
<< " extraInfo:" << sanitizedExtraInfo;
if (!_domainConnectionRefusals.contains(reasonMessage)) { if (!_domainConnectionRefusals.contains(reasonMessage)) {
_domainConnectionRefusals.insert(reasonMessage); _domainConnectionRefusals.insert(reasonMessage);
@ -541,11 +565,12 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
#endif #endif
} }
auto accountManager = DependencyManager::get<AccountManager>();
// Some connection refusal reasons imply that a login is required. If so, suggest a new login // Some connection refusal reasons imply that a login is required. If so, suggest a new login.
if (reasonSuggestsLogin(reasonCode)) { if (reasonSuggestsMetaverseLogin(reasonCode)) {
qCWarning(networking) << "Make sure you are logged in."; qCWarning(networking) << "Make sure you are logged in to the metaverse.";
auto accountManager = DependencyManager::get<AccountManager>();
if (!_hasCheckedForAccessToken) { if (!_hasCheckedForAccessToken) {
accountManager->checkAndSignalForAccessToken(); accountManager->checkAndSignalForAccessToken();
@ -559,6 +584,23 @@ void DomainHandler::processDomainServerConnectionDeniedPacket(QSharedPointer<Rec
accountManager->generateNewUserKeypair(); accountManager->generateNewUserKeypair();
_connectionDenialsSinceKeypairRegen = 0; _connectionDenialsSinceKeypairRegen = 0;
} }
} else if (reasonSuggestsDomainLogin(reasonCode)) {
qCWarning(networking) << "Make sure you are logged in to the domain.";
auto accountManager = DependencyManager::get<DomainAccountManager>();
if (!extraInfo.isEmpty()) {
auto extraInfoComponents = extraInfo.split("|");
accountManager->setAuthURL(extraInfoComponents.value(0));
accountManager->setClientID(extraInfoComponents.value(1));
}
if (!_hasCheckedForDomainAccessToken) {
accountManager->checkAndSignalForAccessToken();
_hasCheckedForDomainAccessToken = true;
}
// ####### TODO: regenerate key-pair after several failed connection attempts, similar to metaverse login code?
} }
} }

View file

@ -4,6 +4,7 @@
// //
// Created by Stephen Birarda on 2/18/2014. // Created by Stephen Birarda on 2/18/2014.
// Copyright 2014 High Fidelity, Inc. // Copyright 2014 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -131,6 +132,7 @@ public:
bool isConnected() const { return _isConnected; } bool isConnected() const { return _isConnected; }
void setIsConnected(bool isConnected); void setIsConnected(bool isConnected);
bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; } bool isServerless() const { return _domainURL.scheme() != URL_SCHEME_HIFI; }
bool getInterstitialModeEnabled() const; bool getInterstitialModeEnabled() const;
void setInterstitialModeEnabled(bool enableInterstitialMode); void setInterstitialModeEnabled(bool enableInterstitialMode);
@ -179,14 +181,14 @@ public:
* <td>The communications protocols of the domain and your Interface are not the same.</td> * <td>The communications protocols of the domain and your Interface are not the same.</td>
* </tr> * </tr>
* <tr> * <tr>
* <td><strong>LoginError</strong></td> * <td><strong>LoginErrorMetaverse</strong></td>
* <td><code>2</code></td> * <td><code>2</code></td>
* <td>You could not be logged into the domain.</td> * <td>You could not be logged into the domain per your metaverse login.</td>
* </tr> * </tr>
* <tr> * <tr>
* <td><strong>NotAuthorized</strong></td> * <td><strong>NotAuthorizedMetaverse</strong></td>
* <td><code>3</code></td> * <td><code>3</code></td>
* <td>You are not authorized to connect to the domain.</td> * <td>You are not authorized to connect to the domain per your metaverse login.</td>
* </tr> * </tr>
* <tr> * <tr>
* <td><strong>TooManyUsers</strong></td> * <td><strong>TooManyUsers</strong></td>
@ -198,6 +200,16 @@ public:
* <td><code>5</code></td> * <td><code>5</code></td>
* <td>Connecting to the domain timed out.</td> * <td>Connecting to the domain timed out.</td>
* </tr> * </tr>
* <tr>
* <td><strong>LoginErrorDomain</strong></td>
* <td><code>2</code></td>
* <td>You could not be logged into the domain per your domain login.</td>
* </tr>
* <tr>
* <td><strong>NotAuthorizedDomain</strong></td>
* <td><code>6</code></td>
* <td>You are not authorized to connect to the domain per your domain login.</td>
* </tr>
* </tbody> * </tbody>
* </table> * </table>
* @typedef {number} Window.ConnectionRefusedReason * @typedef {number} Window.ConnectionRefusedReason
@ -205,10 +217,12 @@ public:
enum class ConnectionRefusedReason : uint8_t { enum class ConnectionRefusedReason : uint8_t {
Unknown, Unknown,
ProtocolMismatch, ProtocolMismatch,
LoginError, LoginErrorMetaverse,
NotAuthorized, NotAuthorizedMetaverse,
TooManyUsers, TooManyUsers,
TimedOut TimedOut,
LoginErrorDomain,
NotAuthorizedDomain
}; };
public slots: public slots:
@ -254,7 +268,8 @@ signals:
void limitOfSilentDomainCheckInsReached(); void limitOfSilentDomainCheckInsReached();
private: private:
bool reasonSuggestsLogin(ConnectionRefusedReason reasonCode); bool reasonSuggestsMetaverseLogin(ConnectionRefusedReason reasonCode);
bool reasonSuggestsDomainLogin(ConnectionRefusedReason reasonCode);
void sendDisconnectPacket(); void sendDisconnectPacket();
void hardReset(QString reason); void hardReset(QString reason);
@ -285,6 +300,7 @@ private:
QSet<QString> _domainConnectionRefusals; QSet<QString> _domainConnectionRefusals;
bool _hasCheckedForAccessToken { false }; bool _hasCheckedForAccessToken { false };
bool _hasCheckedForDomainAccessToken { false };
int _connectionDenialsSinceKeypairRegen { 0 }; int _connectionDenialsSinceKeypairRegen { 0 };
int _checkInPacketsSinceLastReply { 0 }; int _checkInPacketsSinceLastReply { 0 };

View file

@ -21,6 +21,7 @@
#include "NetworkAccessManager.h" #include "NetworkAccessManager.h"
#include "NetworkLogging.h" #include "NetworkLogging.h"
#include "NetworkingConstants.h"
HTTPResourceRequest::~HTTPResourceRequest() { HTTPResourceRequest::~HTTPResourceRequest() {
if (_reply) { if (_reply) {
@ -54,7 +55,7 @@ void HTTPResourceRequest::doSend() {
QNetworkRequest networkRequest(_url); QNetworkRequest networkRequest(_url);
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
if (_cacheEnabled) { if (_cacheEnabled) {
networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache); networkRequest.setAttribute(QNetworkRequest::CacheLoadControlAttribute, QNetworkRequest::PreferCache);

View file

@ -30,6 +30,14 @@ namespace NetworkingConstants {
// Web Engine requests to this parent domain have an account authorization header added // Web Engine requests to this parent domain have an account authorization header added
const QString AUTH_HOSTNAME_BASE = "highfidelity.com"; const QString AUTH_HOSTNAME_BASE = "highfidelity.com";
const QStringList IS_AUTHABLE_HOSTNAME = { "highfidelity.com", "highfidelity.io" };
// Use a custom User-Agent to avoid ModSecurity filtering, e.g. by hosting providers.
const QByteArray VIRCADIA_USER_AGENT = "Mozilla/5.0 (VircadiaInterface)";
const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (VircadiaInterface)";
const QString METAVERSE_USER_AGENT = "Chrome/48.0 (VircadiaInterface)";
const QString MOBILE_USER_AGENT = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36";
const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml"); const QUrl BUILDS_XML_URL("https://highfidelity.com/builds.xml");
const QUrl MASTER_BUILDS_XML_URL("https://highfidelity.com/dev-builds.xml"); const QUrl MASTER_BUILDS_XML_URL("https://highfidelity.com/dev-builds.xml");

View file

@ -4,6 +4,7 @@
// //
// Created by Stephen Birarda on 2/15/13. // Created by Stephen Birarda on 2/15/13.
// Copyright 2013 High Fidelity, Inc. // Copyright 2013 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -33,6 +34,7 @@
#include "AddressManager.h" #include "AddressManager.h"
#include "Assignment.h" #include "Assignment.h"
#include "AudioHelpers.h" #include "AudioHelpers.h"
#include "DomainAccountManager.h"
#include "HifiSockAddr.h" #include "HifiSockAddr.h"
#include "FingerprintUtils.h" #include "FingerprintUtils.h"
@ -103,6 +105,13 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort)
// clear our NodeList when logout is requested // clear our NodeList when logout is requested
connect(accountManager.data(), &AccountManager::logoutComplete , this, [this]{ reset("Logged out"); }); connect(accountManager.data(), &AccountManager::logoutComplete , this, [this]{ reset("Logged out"); });
// Only used in Interface.
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
if (domainAccountManager) {
_hasDomainAccountManager = true;
connect(domainAccountManager.data(), &DomainAccountManager::newTokens, this, &NodeList::sendDomainServerCheckIn);
}
// anytime we get a new node we will want to attempt to punch to it // anytime we get a new node we will want to attempt to punch to it
connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch); connect(this, &LimitedNodeList::nodeAdded, this, &NodeList::startNodeHolePunch);
connect(this, &LimitedNodeList::nodeSocketUpdated, this, &NodeList::startNodeHolePunch); connect(this, &LimitedNodeList::nodeSocketUpdated, this, &NodeList::startNodeHolePunch);
@ -379,6 +388,7 @@ void NodeList::sendDomainServerCheckIn() {
if (domainPacketType == PacketType::DomainConnectRequest) { if (domainPacketType == PacketType::DomainConnectRequest) {
#if (PR_BUILD || DEV_BUILD) #if (PR_BUILD || DEV_BUILD)
// #######
if (_shouldSendNewerVersion) { if (_shouldSendNewerVersion) {
domainPacket->setVersion(versionForPacketType(domainPacketType) + 1); domainPacket->setVersion(versionForPacketType(domainPacketType) + 1);
} }
@ -466,6 +476,7 @@ void NodeList::sendDomainServerCheckIn() {
packetStream << _ownerType.load() << publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList(); packetStream << _ownerType.load() << publicSockAddr << localSockAddr << _nodeTypesOfInterest.toList();
packetStream << DependencyManager::get<AddressManager>()->getPlaceName(); packetStream << DependencyManager::get<AddressManager>()->getPlaceName();
// ####### TODO: Also send if need to send new domainLogin data?
if (!domainIsConnected) { if (!domainIsConnected) {
DataServerAccountInfo& accountInfo = accountManager->getAccountInfo(); DataServerAccountInfo& accountInfo = accountManager->getAccountInfo();
packetStream << accountInfo.getUsername(); packetStream << accountInfo.getUsername();
@ -474,6 +485,23 @@ void NodeList::sendDomainServerCheckIn() {
if (requiresUsernameSignature && accountManager->getAccountInfo().hasPrivateKey()) { if (requiresUsernameSignature && accountManager->getAccountInfo().hasPrivateKey()) {
const QByteArray& usernameSignature = accountManager->getAccountInfo().getUsernameSignature(connectionToken); const QByteArray& usernameSignature = accountManager->getAccountInfo().getUsernameSignature(connectionToken);
packetStream << usernameSignature; packetStream << usernameSignature;
} else {
// ####### TODO: Only append if are going to send domain username?
packetStream << QString(""); // Placeholder in case have domain username.
}
} else {
// ####### TODO: Only append if are going to send domainUsername?
packetStream << QString("") << QString(""); // Placeholders in case have domain username.
}
// Send domain domain login data from Interface to domain server.
if (_hasDomainAccountManager) {
auto domainAccountManager = DependencyManager::get<DomainAccountManager>();
if (!domainAccountManager->getUsername().isEmpty()) {
packetStream << domainAccountManager->getUsername();
if (!domainAccountManager->getAccessToken().isEmpty()) {
packetStream << (domainAccountManager->getAccessToken() + ":" + domainAccountManager->getRefreshToken());
}
} }
} }

View file

@ -196,6 +196,8 @@ private:
#if (PR_BUILD || DEV_BUILD) #if (PR_BUILD || DEV_BUILD)
bool _shouldSendNewerVersion { false }; bool _shouldSendNewerVersion { false };
#endif #endif
bool _hasDomainAccountManager { false };
}; };
#endif // hifi_NodeList_h #endif // hifi_NodeList_h

View file

@ -4,6 +4,7 @@
// //
// Created by Seth Alves on 2016-6-1. // Created by Seth Alves on 2016-6-1.
// Copyright 2016 High Fidelity, Inc. // Copyright 2016 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -51,6 +52,9 @@ public:
void setVerifiedUserName(QString userName) { _verifiedUserName = userName.toLower(); } void setVerifiedUserName(QString userName) { _verifiedUserName = userName.toLower(); }
const QString& getVerifiedUserName() const { return _verifiedUserName; } const QString& getVerifiedUserName() const { return _verifiedUserName; }
void setVerifiedDomainUserName(QString userName) { _verifiedDomainUserName = userName.toLower(); }
const QString& getVerifiedDomainUserName() const { return _verifiedDomainUserName; }
void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }} void setGroupID(QUuid groupID) { _groupID = groupID; if (!groupID.isNull()) { _groupIDSet = true; }}
QUuid getGroupID() const { return _groupID; } QUuid getGroupID() const { return _groupID; }
bool isGroup() const { return _groupIDSet; } bool isGroup() const { return _groupIDSet; }
@ -99,6 +103,7 @@ protected:
QString _id; QString _id;
QUuid _rankID { QUuid() }; // 0 unless this is for a group QUuid _rankID { QUuid() }; // 0 unless this is for a group
QString _verifiedUserName; QString _verifiedUserName;
QString _verifiedDomainUserName;
bool _groupIDSet { false }; bool _groupIDSet { false };
QUuid _groupID; QUuid _groupID;

View file

@ -39,7 +39,7 @@ QNetworkReply* OAuthNetworkAccessManager::createRequest(QNetworkAccessManager::O
&& req.url().host() == MetaverseAPI::getCurrentMetaverseServerURL().host()) { && req.url().host() == MetaverseAPI::getCurrentMetaverseServerURL().host()) {
QNetworkRequest authenticatedRequest(req); QNetworkRequest authenticatedRequest(req);
authenticatedRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); authenticatedRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); authenticatedRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
authenticatedRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, authenticatedRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER,
accountManager->getAccountInfo().getAccessToken().authorizationHeaderValue()); accountManager->getAccountInfo().getAccessToken().authorizationHeaderValue());

View file

@ -25,6 +25,7 @@
#include "HTTPResourceRequest.h" #include "HTTPResourceRequest.h"
#include "NetworkAccessManager.h" #include "NetworkAccessManager.h"
#include "NetworkLogging.h" #include "NetworkLogging.h"
#include "NetworkingConstants.h"
ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(atpSupportEnabled) { ResourceManager::ResourceManager(bool atpSupportEnabled) : _atpSupportEnabled(atpSupportEnabled) {
_thread.setObjectName("Resource Manager Thread"); _thread.setObjectName("Resource Manager Thread");
@ -157,7 +158,7 @@ bool ResourceManager::resourceExists(const QUrl& url) {
QNetworkRequest request{ url }; QNetworkRequest request{ url };
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
auto reply = networkAccessManager.head(request); auto reply = networkAccessManager.head(request);

View file

@ -22,6 +22,7 @@
#include "NetworkAccessManager.h" #include "NetworkAccessManager.h"
#include "NetworkLogging.h" #include "NetworkLogging.h"
#include "NetworkingConstants.h"
namespace SandboxUtils { namespace SandboxUtils {
@ -29,7 +30,7 @@ QNetworkReply* getStatus() {
auto& networkAccessManager = NetworkAccessManager::getInstance(); auto& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL); QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL);
sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
return networkAccessManager.get(sandboxStatus); return networkAccessManager.get(sandboxStatus);
} }

View file

@ -734,7 +734,8 @@ QString getMarketplaceID(const QString& urlString) {
bool Octree::readFromURL( bool Octree::readFromURL(
const QString& urlString, const QString& urlString,
const bool isObservable, const bool isObservable,
const qint64 callerId const qint64 callerId,
const bool isImport
) { ) {
QString trimmedUrl = urlString.trimmed(); QString trimmedUrl = urlString.trimmed();
QString marketplaceID = getMarketplaceID(trimmedUrl); QString marketplaceID = getMarketplaceID(trimmedUrl);
@ -766,7 +767,7 @@ bool Octree::readFromURL(
} }
QDataStream inputStream(data); QDataStream inputStream(data);
return readFromStream(data.size(), inputStream, marketplaceID); return readFromStream(data.size(), inputStream, marketplaceID, isImport);
} }
bool Octree::readFromByteArray( bool Octree::readFromByteArray(
@ -791,7 +792,8 @@ bool Octree::readFromByteArray(
bool Octree::readFromStream( bool Octree::readFromStream(
uint64_t streamLength, uint64_t streamLength,
QDataStream& inputStream, QDataStream& inputStream,
const QString& marketplaceID const QString& marketplaceID,
const bool isImport
) { ) {
// decide if this is binary SVO or JSON-formatted SVO // decide if this is binary SVO or JSON-formatted SVO
QIODevice *device = inputStream.device(); QIODevice *device = inputStream.device();
@ -804,7 +806,7 @@ bool Octree::readFromStream(
return false; return false;
} else { } else {
qCDebug(octree) << "Reading from JSON SVO Stream length:" << streamLength; qCDebug(octree) << "Reading from JSON SVO Stream length:" << streamLength;
return readJSONFromStream(streamLength, inputStream, marketplaceID); return readJSONFromStream(streamLength, inputStream, marketplaceID, isImport);
} }
} }
@ -834,7 +836,8 @@ const int READ_JSON_BUFFER_SIZE = 2048;
bool Octree::readJSONFromStream( bool Octree::readJSONFromStream(
uint64_t streamLength, uint64_t streamLength,
QDataStream& inputStream, QDataStream& inputStream,
const QString& marketplaceID /*=""*/ const QString& marketplaceID, /*=""*/
const bool isImport
) { ) {
// if the data is gzipped we may not have a useful bytesAvailable() result, so just keep reading until // if the data is gzipped we may not have a useful bytesAvailable() result, so just keep reading until
// we get an eof. Leave streamLength parameter for consistency. // we get an eof. Leave streamLength parameter for consistency.
@ -866,7 +869,7 @@ bool Octree::readJSONFromStream(
addMarketplaceIDToDocumentEntities(asMap, marketplaceID); addMarketplaceIDToDocumentEntities(asMap, marketplaceID);
} }
bool success = readFromMap(asMap); bool success = readFromMap(asMap, isImport);
delete[] rawData; delete[] rawData;
return success; return success;
} }

View file

@ -216,13 +216,12 @@ public:
// Octree importers // Octree importers
bool readFromFile(const char* filename); bool readFromFile(const char* filename);
bool readFromURL(const QString& url, const bool isObservable = true, const qint64 callerId = -1); // will support file urls as well... bool readFromURL(const QString& url, const bool isObservable = true, const qint64 callerId = -1, const bool isImport = false); // will support file urls as well...
bool readFromByteArray(const QString& url, const QByteArray& byteArray); bool readFromByteArray(const QString& url, const QByteArray& byteArray);
bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID=""); bool readFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const bool isImport = false);
bool readSVOFromStream(uint64_t streamLength, QDataStream& inputStream); bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="", const bool isImport = false);
bool readJSONFromStream(uint64_t streamLength, QDataStream& inputStream, const QString& marketplaceID="");
bool readJSONFromGzippedFile(QString qFileName); bool readJSONFromGzippedFile(QString qFileName);
virtual bool readFromMap(QVariantMap& entityDescription) = 0; virtual bool readFromMap(QVariantMap& entityDescription, const bool isImport = false) = 0;
uint64_t getOctreeElementsCount(); uint64_t getOctreeElementsCount();

View file

@ -543,12 +543,8 @@ void CharacterController::setLocalBoundingBox(const glm::vec3& minCorner, const
_minStepHeight = DEFAULT_MIN_STEP_HEIGHT_FACTOR * (_halfHeight + _radius); _minStepHeight = DEFAULT_MIN_STEP_HEIGHT_FACTOR * (_halfHeight + _radius);
_maxStepHeight = DEFAULT_MAX_STEP_HEIGHT_FACTOR * (_halfHeight + _radius); _maxStepHeight = DEFAULT_MAX_STEP_HEIGHT_FACTOR * (_halfHeight + _radius);
if (_physicsEngine) { _pendingFlags |= PENDING_FLAG_UPDATE_SHAPE | PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION |
// must REMOVE from world prior to shape update PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
_pendingFlags |= PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_REMOVE_DETAILED_FROM_SIMULATION;
}
_pendingFlags |= PENDING_FLAG_UPDATE_SHAPE;
_pendingFlags |= PENDING_FLAG_ADD_TO_SIMULATION | PENDING_FLAG_ADD_DETAILED_TO_SIMULATION;
} }
// it's ok to change offset immediately -- there are no thread safety issues here // it's ok to change offset immediately -- there are no thread safety issues here

View file

@ -226,6 +226,7 @@ void Model::updateRenderItems() {
modelTransform.setScale(glm::vec3(1.0f)); modelTransform.setScale(glm::vec3(1.0f));
PrimitiveMode primitiveMode = self->getPrimitiveMode(); PrimitiveMode primitiveMode = self->getPrimitiveMode();
auto renderWithZones = self->getRenderWithZones();
auto renderItemKeyGlobalFlags = self->getRenderItemKeyGlobalFlags(); auto renderItemKeyGlobalFlags = self->getRenderItemKeyGlobalFlags();
bool cauterized = self->isCauterized(); bool cauterized = self->isCauterized();
@ -241,7 +242,8 @@ void Model::updateRenderItems() {
bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning(); bool useDualQuaternionSkinning = self->getUseDualQuaternionSkinning();
transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, meshState, useDualQuaternionSkinning, transaction.updateItem<ModelMeshPartPayload>(itemID, [modelTransform, meshState, useDualQuaternionSkinning,
invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags, cauterized](ModelMeshPartPayload& data) { invalidatePayloadShapeKey, primitiveMode, renderItemKeyGlobalFlags,
cauterized, renderWithZones](ModelMeshPartPayload& data) {
if (useDualQuaternionSkinning) { if (useDualQuaternionSkinning) {
data.updateClusterBuffer(meshState.clusterDualQuaternions); data.updateClusterBuffer(meshState.clusterDualQuaternions);
data.computeAdjustedLocalBound(meshState.clusterDualQuaternions); data.computeAdjustedLocalBound(meshState.clusterDualQuaternions);
@ -268,6 +270,7 @@ void Model::updateRenderItems() {
data.updateTransformForSkinnedMesh(renderTransform, modelTransform); data.updateTransformForSkinnedMesh(renderTransform, modelTransform);
data.setCauterized(cauterized); data.setCauterized(cauterized);
data.setRenderWithZones(renderWithZones);
data.updateKey(renderItemKeyGlobalFlags); data.updateKey(renderItemKeyGlobalFlags);
data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning); data.setShapeKey(invalidatePayloadShapeKey, primitiveMode, useDualQuaternionSkinning);
}); });
@ -282,11 +285,6 @@ void Model::setRenderItemsNeedUpdate() {
emit requestRenderUpdate(); emit requestRenderUpdate();
} }
void Model::setPrimitiveMode(PrimitiveMode primitiveMode) {
_primitiveMode = primitiveMode;
setRenderItemsNeedUpdate();
}
void Model::reset() { void Model::reset() {
if (isLoaded()) { if (isLoaded()) {
const HFMModel& hfmModel = getHFMModel(); const HFMModel& hfmModel = getHFMModel();
@ -960,6 +958,13 @@ void Model::setCauterized(bool cauterized, const render::ScenePointer& scene) {
} }
} }
void Model::setPrimitiveMode(PrimitiveMode primitiveMode) {
if (_primitiveMode != primitiveMode) {
_primitiveMode = primitiveMode;
setRenderItemsNeedUpdate();
}
}
void Model::setCullWithParent(bool cullWithParent) { void Model::setCullWithParent(bool cullWithParent) {
if (_cullWithParent != cullWithParent) { if (_cullWithParent != cullWithParent) {
_cullWithParent = cullWithParent; _cullWithParent = cullWithParent;
@ -977,13 +982,10 @@ void Model::setCullWithParent(bool cullWithParent) {
} }
void Model::setRenderWithZones(const QVector<QUuid>& renderWithZones) { void Model::setRenderWithZones(const QVector<QUuid>& renderWithZones) {
render::Transaction transaction; if (_renderWithZones != renderWithZones) {
for (auto item : _modelMeshRenderItemIDs) { _renderWithZones = renderWithZones;
transaction.updateItem<ModelMeshPartPayload>(item, [renderWithZones](ModelMeshPartPayload& data) { setRenderItemsNeedUpdate();
data.setRenderWithZones(renderWithZones);
});
} }
AbstractViewStateInterface::instance()->getMain3DScene()->enqueueTransaction(transaction);
} }
const render::ItemKey Model::getRenderItemKeyGlobalFlags() const { const render::ItemKey Model::getRenderItemKeyGlobalFlags() const {

View file

@ -121,6 +121,7 @@ public:
void setCullWithParent(bool value); void setCullWithParent(bool value);
void setRenderWithZones(const QVector<QUuid>& renderWithZones); void setRenderWithZones(const QVector<QUuid>& renderWithZones);
const QVector<QUuid>& getRenderWithZones() const { return _renderWithZones; }
// Access the current RenderItemKey Global Flags used by the model and applied to the render items representing the parts of the model. // Access the current RenderItemKey Global Flags used by the model and applied to the render items representing the parts of the model.
const render::ItemKey getRenderItemKeyGlobalFlags() const; const render::ItemKey getRenderItemKeyGlobalFlags() const;
@ -499,6 +500,7 @@ protected:
render::ItemKey _renderItemKeyGlobalFlags; render::ItemKey _renderItemKeyGlobalFlags;
bool _cauterized { false }; bool _cauterized { false };
bool _cullWithParent { false }; bool _cullWithParent { false };
QVector<QUuid> _renderWithZones;
bool shouldInvalidatePayloadShapeKey(int meshIndex); bool shouldInvalidatePayloadShapeKey(int meshIndex);

View file

@ -15,6 +15,7 @@
#include "../StencilMaskPass.h" #include "../StencilMaskPass.h"
#include "NetworkAccessManager.h" #include "NetworkAccessManager.h"
#include "NetworkingConstants.h"
static std::mutex fontMutex; static std::mutex fontMutex;
@ -97,7 +98,7 @@ Font::Pointer Font::load(const QString& family) {
QNetworkRequest networkRequest; QNetworkRequest networkRequest;
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
networkRequest.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); networkRequest.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
networkRequest.setUrl(family); networkRequest.setUrl(family);
auto networkReply = networkAccessManager.get(networkRequest); auto networkReply = networkAccessManager.get(networkRequest);

View file

@ -16,6 +16,7 @@
#include <QDirIterator> #include <QDirIterator>
#include <NetworkAccessManager.h> #include <NetworkAccessManager.h>
#include <NetworkingConstants.h>
#include <PathUtils.h> #include <PathUtils.h>
#include "ScriptEngine.h" #include "ScriptEngine.h"
@ -191,7 +192,7 @@ void ScriptsModel::requestDefaultFiles(QString marker) {
QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance();
QNetworkRequest request(url); QNetworkRequest request(url);
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
QNetworkReply* reply = networkAccessManager.get(request); QNetworkReply* reply = networkAccessManager.get(request);
connect(reply, SIGNAL(finished()), SLOT(downloadFinished())); connect(reply, SIGNAL(finished()), SLOT(downloadFinished()));
} }

View file

@ -62,7 +62,7 @@ void XMLHttpRequestClass::abort() {
} }
void XMLHttpRequestClass::setRequestHeader(const QString& name, const QString& value) { void XMLHttpRequestClass::setRequestHeader(const QString& name, const QString& value) {
_request.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); _request.setHeader(QNetworkRequest::UserAgentHeader, NetworkingConstants::VIRCADIA_USER_AGENT);
_request.setRawHeader(QByteArray(name.toLatin1()), QByteArray(value.toLatin1())); _request.setRawHeader(QByteArray(name.toLatin1()), QByteArray(value.toLatin1()));
} }

View file

@ -28,8 +28,8 @@ const float ARCSECONDS_PER_ARCMINUTE = 60.0f;
const float ARCSECONDS_PER_DEGREE = ARCMINUTES_PER_DEGREE * ARCSECONDS_PER_ARCMINUTE; const float ARCSECONDS_PER_DEGREE = ARCMINUTES_PER_DEGREE * ARCSECONDS_PER_ARCMINUTE;
const float EPSILON = 0.000001f; //smallish positive number - used as margin of error for some computations const float EPSILON = 0.000001f; //smallish positive number - used as margin of error for some computations
const float SQUARE_ROOT_OF_2 = (float)sqrt(2.0f); const float SQUARE_ROOT_OF_2 = 1.414214f;
const float SQUARE_ROOT_OF_3 = (float)sqrt(3.0f); const float SQUARE_ROOT_OF_3 = 1.732051f;
const float METERS_PER_DECIMETER = 0.1f; const float METERS_PER_DECIMETER = 0.1f;
const float METERS_PER_CENTIMETER = 0.01f; const float METERS_PER_CENTIMETER = 0.01f;
const float METERS_PER_MILLIMETER = 0.001f; const float METERS_PER_MILLIMETER = 0.001f;

View file

@ -81,9 +81,6 @@ const int BYTES_PER_FLAGS = 1;
typedef unsigned char colorPart; typedef unsigned char colorPart;
typedef unsigned char nodeColor[BYTES_PER_COLOR + BYTES_PER_FLAGS]; typedef unsigned char nodeColor[BYTES_PER_COLOR + BYTES_PER_FLAGS];
// Use a custom User-Agent to avoid ModSecurity filtering, e.g. by hosting providers.
const QByteArray HIGH_FIDELITY_USER_AGENT = "Mozilla/5.0 (HighFidelityInterface)";
// Equivalent to time_t but in usecs instead of secs // Equivalent to time_t but in usecs instead of secs
quint64 usecTimestampNow(bool wantDebug = false); quint64 usecTimestampNow(bool wantDebug = false);
void usecTimestampNowForceClockSkew(qint64 clockSkew); void usecTimestampNowForceClockSkew(qint64 clockSkew);

View file

@ -16,6 +16,7 @@
#include <QtQml/QQmlContext> #include <QtQml/QQmlContext>
#include "RequestFilters.h" #include "RequestFilters.h"
#include "NetworkingConstants.h"
#if !defined(Q_OS_ANDROID) #if !defined(Q_OS_ANDROID)
static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine";
@ -26,8 +27,7 @@ static std::mutex FileTypeProfile_mutex;
FileTypeProfile::FileTypeProfile(QQmlContext* parent) : FileTypeProfile::FileTypeProfile(QQmlContext* parent) :
ContextAwareProfile(parent) ContextAwareProfile(parent)
{ {
static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)"; setHttpUserAgent(NetworkingConstants::WEB_ENGINE_USER_AGENT);
setHttpUserAgent(WEB_ENGINE_USER_AGENT);
setStorageName(QML_WEB_ENGINE_STORAGE_NAME); setStorageName(QML_WEB_ENGINE_STORAGE_NAME);
setOffTheRecord(false); setOffTheRecord(false);

View file

@ -27,10 +27,10 @@ namespace {
bool isAuthableHighFidelityURL(const QUrl& url) { bool isAuthableHighFidelityURL(const QUrl& url) {
auto metaverseServerURL = MetaverseAPI::getCurrentMetaverseServerURL(); auto metaverseServerURL = MetaverseAPI::getCurrentMetaverseServerURL();
static const QStringList HF_HOSTS = { static QStringList HF_HOSTS = {
"highfidelity.com", "highfidelity.io", metaverseServerURL.toString()
metaverseServerURL.toString(),
}; };
HF_HOSTS << NetworkingConstants::IS_AUTHABLE_HOSTNAME;
const auto& scheme = url.scheme(); const auto& scheme = url.scheme();
const auto& host = url.host(); const auto& host = url.host();
@ -83,9 +83,9 @@ void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info,
} }
} }
static const QString USER_AGENT = "User-Agent"; static const QString USER_AGENT = "User-Agent";
const QString tokenStringMobile{ "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36" }; const QString tokenStringMobile{ NetworkingConstants::MOBILE_USER_AGENT };
const QString tokenStringMetaverse{ "Chrome/48.0 (HighFidelityInterface)" }; const QString tokenStringMetaverse{ NetworkingConstants::METAVERSE_USER_AGENT };
const QString tokenStringLimitedCommerce{ "Chrome/48.0 (HighFidelityInterface limitedCommerce)" }; const QString tokenStringLimitedCommerce{ "Chrome/48.0 (VircadiaInterface limitedCommerce)" };
const QString tokenString = !isAuthable ? tokenStringMobile : (accountManager->getLimitedCommerce() ? tokenStringLimitedCommerce : tokenStringMetaverse); const QString tokenString = !isAuthable ? tokenStringMobile : (accountManager->getLimitedCommerce() ? tokenStringLimitedCommerce : tokenStringMetaverse);
info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit()); info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit());

View file

@ -1,12 +1,13 @@
# #
# Created by Bradley Austin Davis on 2015/11/18 # Created by Bradley Austin Davis on 2015/11/18
# Copyright 2015 High Fidelity, Inc. # Copyright 2015 High Fidelity, Inc.
# Copyright 2020 Vircadia contributors.
# #
# Distributed under the Apache License, Version 2.0. # Distributed under the Apache License, Version 2.0.
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html # See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
# #
if (WIN32 AND (NOT USE_GLES)) if ((WIN32 OR UNIX AND NOT APPLE) AND NOT USE_GLES)
set(TARGET_NAME openvr) set(TARGET_NAME openvr)
setup_hifi_plugin(Gui Qml Multimedia) setup_hifi_plugin(Gui Qml Multimedia)
link_hifi_libraries(shared task gl qml networking controllers ui link_hifi_libraries(shared task gl qml networking controllers ui
@ -15,7 +16,9 @@ if (WIN32 AND (NOT USE_GLES))
include_hifi_library_headers(octree) include_hifi_library_headers(octree)
target_openvr() target_openvr()
target_sranipal() if (WIN32)
target_aristo() target_sranipal()
target_link_libraries(${TARGET_NAME} Winmm.lib) target_aristo()
target_link_libraries(${TARGET_NAME} Winmm.lib)
endif()
endif() endif()

View file

@ -1,6 +1,7 @@
// //
// Created by Bradley Austin Davis on 2015/05/12 // Created by Bradley Austin Davis on 2015/05/12
// Copyright 2015 High Fidelity, Inc. // Copyright 2015 High Fidelity, Inc.
// Copyright 2020 Vircadia contributors.
// //
// Distributed under the Apache License, Version 2.0. // Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
@ -739,6 +740,8 @@ int OpenVrDisplayPlugin::getRequiredThreadCount() const {
QString OpenVrDisplayPlugin::getPreferredAudioInDevice() const { QString OpenVrDisplayPlugin::getPreferredAudioInDevice() const {
QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_RecordingDeviceOverride_String); QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_RecordingDeviceOverride_String);
// FIXME: Address Linux.
#ifdef Q_OS_WIN
if (!device.isEmpty()) { if (!device.isEmpty()) {
static const WCHAR INIT = 0; static const WCHAR INIT = 0;
size_t size = device.size() + 1; size_t size = device.size() + 1;
@ -748,11 +751,14 @@ QString OpenVrDisplayPlugin::getPreferredAudioInDevice() const {
// FIXME: This may not be necessary if vr::k_pch_audio_RecordingDeviceOverride_StringName is used above. // FIXME: This may not be necessary if vr::k_pch_audio_RecordingDeviceOverride_StringName is used above.
device = AudioClient::getWinDeviceName(deviceW.data()); device = AudioClient::getWinDeviceName(deviceW.data());
} }
#endif
return device; return device;
} }
QString OpenVrDisplayPlugin::getPreferredAudioOutDevice() const { QString OpenVrDisplayPlugin::getPreferredAudioOutDevice() const {
QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_PlaybackDeviceOverride_String); QString device = getVrSettingString(vr::k_pch_audio_Section, vr::k_pch_audio_PlaybackDeviceOverride_String);
// FIXME: Address Linux.
#ifdef Q_OS_WIN
if (!device.isEmpty()) { if (!device.isEmpty()) {
static const WCHAR INIT = 0; static const WCHAR INIT = 0;
size_t size = device.size() + 1; size_t size = device.size() + 1;
@ -762,6 +768,7 @@ QString OpenVrDisplayPlugin::getPreferredAudioOutDevice() const {
// FIXME: This may not be necessary if vr::k_pch_audio_PlaybackDeviceOverride_StringName is used above. // FIXME: This may not be necessary if vr::k_pch_audio_PlaybackDeviceOverride_StringName is used above.
device = AudioClient::getWinDeviceName(deviceW.data()); device = AudioClient::getWinDeviceName(deviceW.data());
} }
#endif
return device; return device;
} }

Some files were not shown because too many files have changed in this diff Show more