mirror of
https://github.com/overte-org/overte.git
synced 2025-08-04 14:29:35 +02:00
merge with master
This commit is contained in:
commit
97b9179144
501 changed files with 21750 additions and 7020 deletions
26
BUILD_WIN.md
26
BUILD_WIN.md
|
@ -5,7 +5,7 @@ If you are upgrading from previous versions, do a clean uninstall of those versi
|
|||
|
||||
Note: The prerequisites will require about 10 GB of space on your drive. You will also need a system with at least 8GB of main memory.
|
||||
|
||||
### Step 1. Visual Studio & Python
|
||||
### Step 1. Visual Studio & Python 3.x
|
||||
|
||||
If you don’t have Community or Professional edition of Visual Studio, download [Visual Studio Community 2019](https://visualstudio.microsoft.com/vs/). If you have Visual Studio 2017, you are not required to download Visual Studio 2019.
|
||||
|
||||
|
@ -21,7 +21,7 @@ When selecting components, check "Desktop development with C++". On the right on
|
|||
* MSVC v141 - VS 2017 C++ x64/x86 build tools
|
||||
* MSVC v140 - VS 2015 C++ build tools (v14.00)
|
||||
|
||||
If you do not already have a Python development environment installed, also check "Python Development" in this screen.
|
||||
If you do not already have a Python 3.x development environment installed, also check "Python Development" in this screen.
|
||||
|
||||
If you already have Visual Studio installed and need to add Python, open the "Add or remove programs" control panel and find the "Microsoft Visual Studio Installer". Select it and click "Modify". In the installer, select "Modify" again, then check "Python Development" and allow the installer to apply the changes.
|
||||
|
||||
|
@ -31,9 +31,10 @@ If you do not wish to use the Python installation bundled with Visual Studio, yo
|
|||
|
||||
### Step 2. Installing CMake
|
||||
|
||||
Download and install the latest version of CMake 3.14.
|
||||
Download and install the latest version of CMake 3.15.
|
||||
* Note that earlier versions of CMake will work, but there is a specific bug related to the interaction of Visual Studio 2019 and CMake versions prior to 3.15 that will cause Visual Studio to rebuild far more than it needs to on every build
|
||||
|
||||
Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.14 Version page](https://cmake.org/files/v3.14/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted.
|
||||
Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.15 Version page](https://cmake.org/files/v3.15/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted.
|
||||
|
||||
### Step 3. Create VCPKG environment variable
|
||||
In the next step, you will use CMake to build High Fidelity. By default, the CMake process builds dependency files in Windows' `%TEMP%` directory, which is periodically cleared by the operating system. To prevent you from having to re-build the dependencies in the event that Windows clears that directory, we recommend that you create a `HIFI_VCPKG_BASE` environment variable linked to a directory somewhere on your machine. That directory will contain all dependency files until you manually remove them.
|
||||
|
@ -42,9 +43,18 @@ To create this variable:
|
|||
* Naviagte to 'Edit the System Environment Variables' Through the start menu.
|
||||
* Click on 'Environment Variables'
|
||||
* Select 'New'
|
||||
* Set "Variable name" to HIFI_VCPKG_BASE
|
||||
* Set "Variable name" to `HIFI_VCPKG_BASE`
|
||||
* Set "Variable value" to any directory that you have control over.
|
||||
|
||||
Additionally, if you have Visual Studio 2019 installed and _only_ Visual Studio 2019 (i.e. you do not have Visual Studio 2017 installed) you must add an additional environment variable `HIFI_VCPKG_BOOTSTRAP` that will fix a bug in our `vcpkg` pre-build step.
|
||||
|
||||
To create this variable:
|
||||
* Naviagte to 'Edit the System Environment Variables' Through the start menu.
|
||||
* Click on 'Environment Variables'
|
||||
* Select 'New'
|
||||
* Set "Variable name" to `HIFI_VCPKG_BOOTSTRAP`
|
||||
* Set "Variable value" to `1`
|
||||
|
||||
### Step 4. Running CMake to Generate Build Files
|
||||
|
||||
Run Command Prompt from Start and run the following commands:
|
||||
|
@ -66,12 +76,6 @@ Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
|
|||
|
||||
Change the Solution Configuration (menu ribbon under the menu bar, next to the green play button) from "Debug" to "Release" for best performance.
|
||||
|
||||
Create another environment variable (see Step #3)
|
||||
* Set "Variable name": `PreferredToolArchitecture`
|
||||
* Set "Variable value": `x64`
|
||||
|
||||
Restart Visual Studio for the new variable to take effect.
|
||||
|
||||
Run from the menu bar `Build > Build Solution`.
|
||||
|
||||
### Step 6. Testing Interface
|
||||
|
|
|
@ -10,6 +10,11 @@ endif()
|
|||
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/macros/TargetPython.cmake")
|
||||
target_python()
|
||||
|
||||
if (WIN32 AND NOT HIFI_ANDROID)
|
||||
# Force x64 toolset
|
||||
set(CMAKE_GENERATOR_TOOLSET "host=x64" CACHE STRING "64-bit toolset" FORCE)
|
||||
endif()
|
||||
|
||||
# set our OS X deployment target
|
||||
# (needs to be set before first project() call and before prebuild.py)
|
||||
# Will affect VCPKG dependencies
|
||||
|
|
|
@ -17,7 +17,7 @@ Documentation
|
|||
=========
|
||||
Documentation is available at [docs.highfidelity.com](https://docs.highfidelity.com), if something is missing, please suggest it via a new job on Worklist (add to the hifi-docs project).
|
||||
|
||||
There is also detailed [documentation on our coding standards](https://wiki.highfidelity.com/wiki/Coding_Standards).
|
||||
There is also detailed [documentation on our coding standards](CODING_STANDARD.md).
|
||||
|
||||
Contributor License Agreement (CLA)
|
||||
=========
|
||||
|
|
|
@ -204,10 +204,8 @@ void AvatarMixerClientData::processSetTraitsMessage(ReceivedMessage& message,
|
|||
if (traitType == AvatarTraits::SkeletonModelURL) {
|
||||
// special handling for skeleton model URL, since we need to make sure it is in the whitelist
|
||||
checkSkeletonURLAgainstWhitelist(slaveSharedData, sendingNode, packetTraitVersion);
|
||||
#ifdef AVATAR_POP_CHECK
|
||||
// Deferred for UX work. With no PoP check, no need to get the .fst.
|
||||
_avatar->fetchAvatarFST();
|
||||
#endif
|
||||
}
|
||||
|
||||
anyTraitsChanged = true;
|
||||
|
|
|
@ -157,11 +157,6 @@ qint64 AvatarMixerSlave::addChangedTraitsToBulkPacket(AvatarMixerClientData* lis
|
|||
++simpleReceivedIt;
|
||||
}
|
||||
|
||||
if (bytesWritten > 0 && sendingAvatar->isCertifyFailed()) {
|
||||
// Resend identity packet if certification failed:
|
||||
sendingAvatar->setNeedsIdentityUpdate();
|
||||
}
|
||||
|
||||
// enumerate the received instanced trait versions
|
||||
auto instancedReceivedIt = lastReceivedVersions.instancedCBegin();
|
||||
while (instancedReceivedIt != lastReceivedVersions.instancedCEnd()) {
|
||||
|
|
|
@ -75,7 +75,7 @@ public:
|
|||
void each(std::function<void(AvatarMixerSlave& slave)> functor);
|
||||
|
||||
#ifdef DEBUG_EVENT_QUEUE
|
||||
void AvatarMixerSlavePool::queueStats(QJsonObject& stats);
|
||||
void queueStats(QJsonObject& stats);
|
||||
#endif
|
||||
|
||||
void setNumThreads(int numThreads);
|
||||
|
|
|
@ -71,7 +71,7 @@ void MixerAvatar::fetchAvatarFST() {
|
|||
connect(fstRequest, &ResourceRequest::finished, this, &MixerAvatar::fstRequestComplete);
|
||||
fstRequest->send();
|
||||
} else {
|
||||
qCDebug(avatars) << "Couldn't create FST request for" << avatarURL;
|
||||
qCDebug(avatars) << "Couldn't create FST request for" << avatarURL << getSessionUUID();
|
||||
_verifyState = error;
|
||||
}
|
||||
_needsIdentityUpdate = true;
|
||||
|
@ -84,7 +84,7 @@ void MixerAvatar::fstRequestComplete() {
|
|||
auto result = fstRequest->getResult();
|
||||
if (result != ResourceRequest::Success) {
|
||||
_verifyState = error;
|
||||
qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "failed:" << result;
|
||||
qCDebug(avatars) << "FST request for" << fstRequest->getUrl() << "(user " << getSessionUUID() << ") failed:" << result;
|
||||
} else {
|
||||
_avatarFSTContents = fstRequest->getData();
|
||||
_verifyState = receivedFST;
|
||||
|
@ -178,7 +178,8 @@ void MixerAvatar::ownerRequestComplete() {
|
|||
} else {
|
||||
auto jsonData = QJsonDocument::fromJson(networkReply->readAll())["data"];
|
||||
if (!jsonData.isUndefined() && !jsonData.toObject()["message"].isUndefined()) {
|
||||
qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << ":"
|
||||
qCDebug(avatars) << "Owner lookup failed for" << getDisplayName() << "("
|
||||
<< getSessionUUID() << ") :"
|
||||
<< jsonData.toObject()["message"].toString();
|
||||
_verifyState = error;
|
||||
_pendingEvent = false;
|
||||
|
@ -221,7 +222,7 @@ void MixerAvatar::processCertifyEvents() {
|
|||
} else {
|
||||
_needsIdentityUpdate = true;
|
||||
_pendingEvent = false;
|
||||
qCDebug(avatars) << "Avatar" << getDisplayName() << "FAILED static certification";
|
||||
qCDebug(avatars) << "Avatar" << getDisplayName() << "(" << getSessionUUID() << ") FAILED static certification";
|
||||
}
|
||||
} else { // FST doesn't have a certificate, so noncertified rather than failed:
|
||||
_pendingEvent = false;
|
||||
|
@ -261,6 +262,8 @@ void MixerAvatar::processCertifyEvents() {
|
|||
_pendingEvent = true;
|
||||
} else {
|
||||
_verifyState = error;
|
||||
qCDebug(avatars) << "Get owner status - couldn't parse response for" << getSessionUUID()
|
||||
<< ":" << _dynamicMarketResponse;
|
||||
}
|
||||
} else {
|
||||
qCDebug(avatars) << "Get owner status failed for " << getDisplayName() << _marketplaceIdFromURL <<
|
||||
|
@ -332,7 +335,7 @@ void MixerAvatar::sendOwnerChallenge() {
|
|||
nonceHash.addData(nonce);
|
||||
_challengeNonceHash = nonceHash.result();
|
||||
|
||||
static constexpr int CHALLENGE_TIMEOUT_MS = 5 * 1000; // 5 s
|
||||
static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s
|
||||
if (_challengeTimeout) {
|
||||
_challengeTimeout->deleteLater();
|
||||
}
|
||||
|
@ -344,6 +347,7 @@ void MixerAvatar::sendOwnerChallenge() {
|
|||
_pendingEvent = false;
|
||||
_verifyState = verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
qCDebug(avatars) << "Dynamic verification TIMED-OUT for " << getDisplayName() << getSessionUUID();
|
||||
}
|
||||
});
|
||||
_challengeTimeout->start();
|
||||
|
|
|
@ -97,8 +97,10 @@ public:
|
|||
/**jsdoc
|
||||
* Starts playing an animation on the avatar.
|
||||
* @function Avatar.startAnimation
|
||||
* @param {string} url - The animation file's URL. Animation files need to be in the FBX format but only need to contain
|
||||
* the avatar skeleton and animation data.
|
||||
* @param {string} url - The animation file's URL. Animation files need to be in glTF or FBX format but only need to
|
||||
* contain the avatar skeleton and animation data. glTF models may be in JSON or binary format (".gltf" or ".glb" URLs
|
||||
* respectively).
|
||||
* <p><strong>Warning:</strong> glTF animations currently do not always animate correctly.</p>
|
||||
* @param {number} [fps=30] - The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed.
|
||||
* @param {number} [priority=1] - <em>Not used.</em>
|
||||
* @param {boolean} [loop=false] - <code>true</code> if the animation should loop, <code>false</code> if it shouldn't.
|
||||
|
|
|
@ -160,6 +160,11 @@ bool EntityTreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, O
|
|||
if (sendComplete && nodeData->wantReportInitialCompletion() && _traversal.finished()) {
|
||||
// Dealt with all nearby entities.
|
||||
nodeData->setReportInitialCompletion(false);
|
||||
// initial stats and entity packets are reliable until the initial query is complete
|
||||
// to guarantee all entity data is available for safe landing/physics start. Afterwards
|
||||
// the packets are unreliable for performance.
|
||||
nodeData->stats.getStatsMessage().setReliable(false);
|
||||
nodeData->getPacket().setReliable(false);
|
||||
|
||||
// Send EntityQueryInitialResultsComplete reliable packet ...
|
||||
auto initialCompletion = NLPacket::create(PacketType::EntityQueryInitialResultsComplete,
|
||||
|
|
|
@ -194,13 +194,13 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
|||
|
||||
// actually send it
|
||||
OctreeServer::didCallWriteDatagram(this);
|
||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(statsPacket, *node);
|
||||
DependencyManager::get<NodeList>()->sendPacket(NLPacket::createCopy(statsPacket), *node);
|
||||
} else {
|
||||
// not enough room in the packet, send two packets
|
||||
|
||||
// first packet
|
||||
OctreeServer::didCallWriteDatagram(this);
|
||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(statsPacket, *node);
|
||||
DependencyManager::get<NodeList>()->sendPacket(NLPacket::createCopy(statsPacket), *node);
|
||||
|
||||
int numBytes = statsPacket.getDataSize();
|
||||
_totalBytes += numBytes;
|
||||
|
@ -230,7 +230,7 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
|||
|
||||
// second packet
|
||||
OctreeServer::didCallWriteDatagram(this);
|
||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(sentPacket, *node);
|
||||
DependencyManager::get<NodeList>()->sendPacket(NLPacket::createCopy(sentPacket), *node);
|
||||
|
||||
numBytes = sentPacket.getDataSize();
|
||||
_totalBytes += numBytes;
|
||||
|
@ -263,7 +263,8 @@ int OctreeSendThread::handlePacketSend(SharedNodePointer node, OctreeQueryNode*
|
|||
// just send the octree packet
|
||||
OctreeServer::didCallWriteDatagram(this);
|
||||
NLPacket& sentPacket = nodeData->getPacket();
|
||||
DependencyManager::get<NodeList>()->sendUnreliablePacket(sentPacket, *node);
|
||||
|
||||
DependencyManager::get<NodeList>()->sendPacket(NLPacket::createCopy(sentPacket), *node);
|
||||
|
||||
int numBytes = sentPacket.getDataSize();
|
||||
_totalBytes += numBytes;
|
||||
|
|
|
@ -1292,6 +1292,7 @@ void OctreeServer::aboutToFinish() {
|
|||
for (auto& it : _sendThreads) {
|
||||
auto& sendThread = *it.second;
|
||||
sendThread.setIsShuttingDown();
|
||||
sendThread.terminate();
|
||||
}
|
||||
|
||||
// Clear will destruct all the unique_ptr to OctreeSendThreads which will call the GenericThread's dtor
|
||||
|
|
|
@ -131,8 +131,11 @@ macro(SET_PACKAGING_PARAMETERS)
|
|||
endif ()
|
||||
|
||||
if (DEPLOY_PACKAGE)
|
||||
# for deployed packages always grab the serverless content
|
||||
set(DOWNLOAD_SERVERLESS_CONTENT ON)
|
||||
# For deployed packages we do not grab the serverless content any longer.
|
||||
# Instead, we deploy just the serverless content that is in the interface/resources/serverless
|
||||
# directory. If we ever move back to delivering serverless via a hosted .zip file,
|
||||
# we can re-enable this.
|
||||
set(DOWNLOAD_SERVERLESS_CONTENT OFF)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
|
|
|
@ -52,14 +52,8 @@ macro(setup_qt)
|
|||
message(FATAL_ERROR "VCPKG_QT_CMAKE_PREFIX_PATH should have been set by hifi_vcpkg.py")
|
||||
endif()
|
||||
if (NOT DEV_BUILD)
|
||||
if (UNIX AND DEFINED ENV{QT_CMAKE_PREFIX_PATH} AND NOT APPLE)
|
||||
# HACK: obey QT_CMAKE_PREFIX_PATH to allow UNIX to use older QT libs
|
||||
message("HACK: obey QT_CMAKE_PREFIX_PATH on UNIX")
|
||||
set(QT_CMAKE_PREFIX_PATH $ENV{QT_CMAKE_PREFIX_PATH})
|
||||
else()
|
||||
message("override QT_CMAKE_PREFIX_PATH with VCPKG_QT_CMAKE_PREFIX_PATH")
|
||||
set(QT_CMAKE_PREFIX_PATH ${VCPKG_QT_CMAKE_PREFIX_PATH})
|
||||
endif()
|
||||
message("override QT_CMAKE_PREFIX_PATH with VCPKG_QT_CMAKE_PREFIX_PATH")
|
||||
set(QT_CMAKE_PREFIX_PATH ${VCPKG_QT_CMAKE_PREFIX_PATH})
|
||||
else()
|
||||
# DEV_BUILD
|
||||
if (DEFINED ENV{QT_CMAKE_PREFIX_PATH})
|
||||
|
|
24
cmake/macros/TargetWebRTC.cmake
Normal file
24
cmake/macros/TargetWebRTC.cmake
Normal file
|
@ -0,0 +1,24 @@
|
|||
#
|
||||
# Copyright 2019 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_WEBRTC)
|
||||
if (ANDROID)
|
||||
# I don't yet have working libwebrtc for android
|
||||
# include(SelectLibraryConfigurations)
|
||||
# set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/webrtc/webrtc)
|
||||
# set(WEBRTC_INCLUDE_DIRS "${INSTALL_DIR}/include/webrtc")
|
||||
# set(WEBRTC_LIBRARY_DEBUG ${INSTALL_DIR}/debug/lib/libwebrtc.a)
|
||||
# set(WEBRTC_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libwebrtc.a)
|
||||
# select_library_configurations(WEBRTC)
|
||||
else()
|
||||
set(WEBRTC_INCLUDE_DIRS "${VCPKG_INSTALL_ROOT}/include/webrtc")
|
||||
find_library(WEBRTC_LIBRARY NAMES webrtc PATHS ${VCPKG_INSTALL_ROOT}/lib/ NO_DEFAULT_PATH)
|
||||
target_include_directories(${TARGET_NAME} SYSTEM PUBLIC ${WEBRTC_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${WEBRTC_LIBRARY})
|
||||
endif()
|
||||
|
||||
|
||||
endmacro()
|
|
@ -1,4 +1,4 @@
|
|||
Source: hifi-deps
|
||||
Version: 0.1
|
||||
Description: Collected dependencies for High Fidelity applications
|
||||
Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), tbb (!android&!osx), zlib
|
||||
Build-Depends: bullet3, draco, etc2comp, glm, nvtt, openexr (!android), openssl (windows), tbb (!android&!osx), zlib, webrtc (!android)
|
||||
|
|
3
cmake/ports/webrtc/CONTROL
Normal file
3
cmake/ports/webrtc/CONTROL
Normal file
|
@ -0,0 +1,3 @@
|
|||
Source: webrtc
|
||||
Version: 20190626
|
||||
Description: WebRTC
|
36
cmake/ports/webrtc/portfile.cmake
Normal file
36
cmake/ports/webrtc/portfile.cmake
Normal file
|
@ -0,0 +1,36 @@
|
|||
include(vcpkg_common_functions)
|
||||
set(WEBRTC_VERSION 20190626)
|
||||
set(MASTER_COPY_SOURCE_PATH ${CURRENT_BUILDTREES_DIR}/src)
|
||||
|
||||
if (ANDROID)
|
||||
# this is handled by hifi_android.py
|
||||
elseif (WIN32)
|
||||
vcpkg_download_distfile(
|
||||
WEBRTC_SOURCE_ARCHIVE
|
||||
URLS https://hifi-public.s3.amazonaws.com/seth/webrtc-20190626-windows.zip
|
||||
SHA512 c0848eddb1579b3bb0496b8785e24f30470f3c477145035fd729264a326a467b9467ae9f426aa5d72d168ad9e9bf2c279150744832736bdf39064d24b04de1a3
|
||||
FILENAME webrtc-20190626-windows.zip
|
||||
)
|
||||
elseif (APPLE)
|
||||
vcpkg_download_distfile(
|
||||
WEBRTC_SOURCE_ARCHIVE
|
||||
URLS https://hifi-public.s3.amazonaws.com/seth/webrtc-20190626-osx.tar.gz
|
||||
SHA512 fc70cec1b5ee87395137b7090f424e2fc2300fc17d744d5ffa1cf7aa0e0f1a069a9d72ba1ad2fb4a640ebeb6c218bda24351ba0083e1ff96c4a4b5032648a9d2
|
||||
FILENAME webrtc-20190626-osx.tar.gz
|
||||
)
|
||||
else ()
|
||||
# else Linux desktop
|
||||
vcpkg_download_distfile(
|
||||
WEBRTC_SOURCE_ARCHIVE
|
||||
URLS https://hifi-public.s3.amazonaws.com/seth/webrtc-20190626-linux.tar.gz
|
||||
SHA512 07d7776551aa78cb09a3ef088a8dee7762735c168c243053b262083d90a1d258cec66dc386f6903da5c4461921a3c2db157a1ee106a2b47e7756cb424b66cc43
|
||||
FILENAME webrtc-20190626-linux.tar.gz
|
||||
)
|
||||
endif ()
|
||||
|
||||
vcpkg_extract_source_archive(${WEBRTC_SOURCE_ARCHIVE})
|
||||
|
||||
file(COPY ${MASTER_COPY_SOURCE_PATH}/webrtc/include DESTINATION ${CURRENT_PACKAGES_DIR})
|
||||
file(COPY ${MASTER_COPY_SOURCE_PATH}/webrtc/lib DESTINATION ${CURRENT_PACKAGES_DIR})
|
||||
file(COPY ${MASTER_COPY_SOURCE_PATH}/webrtc/share DESTINATION ${CURRENT_PACKAGES_DIR})
|
||||
file(COPY ${MASTER_COPY_SOURCE_PATH}/webrtc/debug DESTINATION ${CURRENT_PACKAGES_DIR})
|
|
@ -1788,6 +1788,39 @@
|
|||
"default": false
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "installed_content",
|
||||
"label": "Installed Content",
|
||||
"hidden": true,
|
||||
"settings": [
|
||||
{
|
||||
"name": "filename",
|
||||
"content_setting": true,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "name",
|
||||
"content_setting": true,
|
||||
"default": ""
|
||||
},
|
||||
{
|
||||
"name": "creation_time",
|
||||
"content_setting": true,
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"name": "install_time",
|
||||
"type": "int",
|
||||
"content_setting": true,
|
||||
"default": 0
|
||||
},
|
||||
{
|
||||
"name": "installed_by",
|
||||
"content_setting": true,
|
||||
"default": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -4,8 +4,14 @@ $(document).ready(function(){
|
|||
var RESTORE_SETTINGS_FILE_ID = 'restore-settings-file';
|
||||
var UPLOAD_CONTENT_ALLOWED_DIV_ID = 'upload-content-allowed';
|
||||
var UPLOAD_CONTENT_RECOVERING_DIV_ID = 'upload-content-recovering';
|
||||
var INSTALLED_CONTENT_FILENAME_ID = 'installed-content-filename';
|
||||
var INSTALLED_CONTENT_NAME_ID = 'installed-content-name';
|
||||
var INSTALLED_CONTENT_CREATED_ID = 'installed-content-created';
|
||||
var INSTALLED_CONTENT_INSTALLED_ID = 'installed-content-installed';
|
||||
var INSTALLED_CONTENT_INSTALLED_BY_ID = 'installed-content-installed-by';
|
||||
|
||||
var isRestoring = false;
|
||||
var restoreErrorShown = false;
|
||||
|
||||
function progressBarHTML(extraClass, label) {
|
||||
var html = "<div class='progress'>";
|
||||
|
@ -64,11 +70,23 @@ $(document).ready(function(){
|
|||
|
||||
var ajaxObject = $.ajax(ajaxParams);
|
||||
ajaxObject.fail(function (jqXHR, textStatus, errorThrown) {
|
||||
showErrorMessage(
|
||||
"Error",
|
||||
"There was a problem restoring domain content.\n"
|
||||
+ "Please ensure that the content archive or entity file is valid and try again."
|
||||
);
|
||||
// status of 0 means the connection was reset, which
|
||||
// happens after the content is parsed and the server restarts
|
||||
// in the case of json and json.gz files
|
||||
if (jqXHR.status != 0) {
|
||||
showErrorMessage(
|
||||
"Error",
|
||||
"There was a problem restoring domain content.\n"
|
||||
+ "Please ensure that the content archive or entity file is valid and try again."
|
||||
);
|
||||
} else {
|
||||
isRestoring = true;
|
||||
|
||||
// immediately reload backup information since one should be restoring now
|
||||
reloadBackupInformation();
|
||||
|
||||
swal.close();
|
||||
}
|
||||
});
|
||||
|
||||
updateProgressBars($('.upload-content-progress'), (offset + nextChunkSize) * 100 / fileSize);
|
||||
|
@ -103,10 +121,25 @@ $(document).ready(function(){
|
|||
html += "<span class='help-block'>Restore in progress</span>";
|
||||
html += progressBarHTML('recovery', 'Restoring');
|
||||
html += "</div></div>";
|
||||
|
||||
$('#' + Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID + ' .panel-body').html(html);
|
||||
}
|
||||
|
||||
function setupInstalledContentInfo() {
|
||||
var html = "<table class='table table-bordered'><tbody>";
|
||||
html += "<tr class='headers'><td class='data'><strong>Name</strong></td>";
|
||||
html += "<td class='data'><strong>File Name</strong></td>";
|
||||
html += "<td class='data'><strong>Created</strong></td>";
|
||||
html += "<td class='data'><strong>Installed</strong></td>";
|
||||
html += "<td class='data'><strong>Installed By</strong></td></tr>";
|
||||
html += "<tr><td class='data' id='" + INSTALLED_CONTENT_NAME_ID + "'/>";
|
||||
html += "<td class='data' id='" + INSTALLED_CONTENT_FILENAME_ID + "'/>";
|
||||
html += "<td class='data' id='" + INSTALLED_CONTENT_CREATED_ID + "'/>";
|
||||
html += "<td class='data' id='" + INSTALLED_CONTENT_INSTALLED_ID + "'/>";
|
||||
html += "<td class='data' id='" + INSTALLED_CONTENT_INSTALLED_BY_ID + "'/></tr>";
|
||||
html += "</tbody></table>";
|
||||
$('#' + Settings.INSTALLED_CONTENT + ' .panel-body').html(html);
|
||||
}
|
||||
|
||||
// handle content archive or entity file upload
|
||||
|
||||
// when the selected file is changed, enable the button if there's a selected file
|
||||
|
@ -135,6 +168,7 @@ $(document).ready(function(){
|
|||
|
||||
var GENERATE_ARCHIVE_BUTTON_ID = 'generate-archive-button';
|
||||
var CONTENT_ARCHIVES_NORMAL_ID = 'content-archives-success';
|
||||
var CONTENT_ARCHIVES_CONTENT_INFO_ID = 'content-archives-content-info';
|
||||
var CONTENT_ARCHIVES_ERROR_ID = 'content-archives-error';
|
||||
var AUTOMATIC_ARCHIVES_TABLE_ID = 'automatic-archives-table';
|
||||
var AUTOMATIC_ARCHIVES_TBODY_ID = 'automatic-archives-tbody';
|
||||
|
@ -230,13 +264,27 @@ $(document).ready(function(){
|
|||
url: '/api/backups',
|
||||
cache: false
|
||||
}).done(function(data) {
|
||||
|
||||
// split the returned data into manual and automatic manual backups
|
||||
var splitBackups = _.partition(data.backups, function(value, index) {
|
||||
return value.isManualBackup;
|
||||
});
|
||||
|
||||
if (isRestoring && !data.status.isRecovering) {
|
||||
if (data.status.recoveryError && !restoreErrorShown) {
|
||||
restoreErrorShown = true;
|
||||
swal({
|
||||
title: "Error",
|
||||
text: "There was a problem restoring domain content.\n"
|
||||
+ data.status.recoveryError,
|
||||
type: "error",
|
||||
showCancelButton: false,
|
||||
confirmButtonText: "Restart",
|
||||
closeOnConfirm: true,
|
||||
},
|
||||
function () {
|
||||
$.get("/restart");
|
||||
showRestartModal();
|
||||
});
|
||||
}
|
||||
if (isRestoring && !data.status.isRecovering && !data.status.recoveryError) {
|
||||
// we were recovering and we finished - the DS is going to restart so show the restart modal
|
||||
showRestartModal();
|
||||
return;
|
||||
|
@ -327,6 +375,12 @@ $(document).ready(function(){
|
|||
$('#' + UPLOAD_CONTENT_ALLOWED_DIV_ID).toggle(!data.status.isRecovering);
|
||||
$('#' + UPLOAD_CONTENT_RECOVERING_DIV_ID).toggle(data.status.isRecovering);
|
||||
|
||||
$('#' + INSTALLED_CONTENT_NAME_ID).text(data.installed_content.name);
|
||||
$('#' + INSTALLED_CONTENT_FILENAME_ID).text(data.installed_content.filename);
|
||||
$('#' + INSTALLED_CONTENT_CREATED_ID).text(data.installed_content.creation_time ? moment(data.installed_content.creation_time).format('lll') : "");
|
||||
$('#' + INSTALLED_CONTENT_INSTALLED_ID).text(data.installed_content.install_time ? moment(data.installed_content.install_time).format('lll') : "");
|
||||
$('#' + INSTALLED_CONTENT_INSTALLED_BY_ID).text(data.installed_content.installed_by);
|
||||
|
||||
// update the progress bars for current restore status
|
||||
if (data.status.isRecovering) {
|
||||
updateProgressBars($('.recovery.progress-bar'), data.status.recoveryProgress * 100);
|
||||
|
@ -514,6 +568,7 @@ $(document).ready(function(){
|
|||
Settings.afterReloadActions = function() {
|
||||
setupBackupUpload();
|
||||
setupContentArchives();
|
||||
setupInstalledContentInfo();
|
||||
|
||||
// load the latest backups immediately
|
||||
reloadBackupInformation();
|
||||
|
|
|
@ -57,10 +57,14 @@ $(document).ready(function(){
|
|||
// define extra groups to add to setting panels, with their splice index
|
||||
Settings.extraContentGroupsAtIndex = {
|
||||
0: {
|
||||
html_id: Settings.INSTALLED_CONTENT,
|
||||
label: 'Installed Content'
|
||||
},
|
||||
1: {
|
||||
html_id: Settings.CONTENT_ARCHIVES_PANEL_ID,
|
||||
label: 'Content Archives'
|
||||
},
|
||||
1: {
|
||||
2: {
|
||||
html_id: Settings.UPLOAD_CONTENT_BACKUP_PANEL_ID,
|
||||
label: 'Upload Content'
|
||||
}
|
||||
|
|
|
@ -44,7 +44,8 @@ $.extend(Settings, {
|
|||
INVALID_ROW_CLASS: 'invalid-input',
|
||||
DATA_ROW_INDEX: 'data-row-index',
|
||||
CONTENT_ARCHIVES_PANEL_ID: 'content_archives',
|
||||
UPLOAD_CONTENT_BACKUP_PANEL_ID: 'upload_content'
|
||||
UPLOAD_CONTENT_BACKUP_PANEL_ID: 'upload_content',
|
||||
INSTALLED_CONTENT: 'installed_content'
|
||||
});
|
||||
|
||||
var URLs = {
|
||||
|
|
|
@ -278,17 +278,19 @@ void AssetsBackupHandler::createBackup(const QString& backupName, QuaZip& zip) {
|
|||
_backups.emplace_back(backupName, mappings, false);
|
||||
}
|
||||
|
||||
void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) {
|
||||
std::pair<bool, QString> AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) {
|
||||
Q_ASSERT(QThread::currentThread() == thread());
|
||||
|
||||
if (operationInProgress()) {
|
||||
qCWarning(asset_backup) << "There is already a backup/restore in progress.";
|
||||
return;
|
||||
QString errorStr ("There is already a backup/restore in progress. Please wait.");
|
||||
qWarning() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
|
||||
if (_lastMappingsRefresh.time_since_epoch().count() == 0) {
|
||||
qCWarning(asset_backup) << "Current mappings not yet loaded.";
|
||||
return;
|
||||
QString errorStr ("Current mappings not yet loaded. Please wait.");
|
||||
qWarning() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
|
||||
if ((p_high_resolution_clock::now() - _lastMappingsRefresh) > MAX_REFRESH_TIME) {
|
||||
|
@ -301,6 +303,16 @@ void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip)
|
|||
if (it == end(_backups)) {
|
||||
loadBackup(backupName, zip);
|
||||
|
||||
auto emplaced_backup = find_if(begin(_backups), end(_backups), [&](const AssetServerBackup& backup) {
|
||||
return backup.name == backupName;
|
||||
});
|
||||
|
||||
if(emplaced_backup->corruptedBackup) {
|
||||
QString errorStr ("Current mappings file is corrupted.");
|
||||
qWarning() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
|
||||
QuaZipDir zipDir { &zip, ZIP_ASSETS_FOLDER };
|
||||
|
||||
auto assetNames = zipDir.entryList(QDir::Files);
|
||||
|
@ -330,8 +342,9 @@ void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip)
|
|||
});
|
||||
|
||||
if (it == end(_backups)) {
|
||||
qCCritical(asset_backup) << "Failed to recover backup:" << backupName;
|
||||
return;
|
||||
QString errorStr ("Failed to recover backup: " + backupName);
|
||||
qWarning() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -339,6 +352,7 @@ void AssetsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip)
|
|||
computeServerStateDifference(_currentMappings, newMappings);
|
||||
|
||||
restoreAllAssets();
|
||||
return { true, QString() };
|
||||
}
|
||||
|
||||
void AssetsBackupHandler::deleteBackup(const QString& backupName) {
|
||||
|
|
|
@ -38,7 +38,7 @@ public:
|
|||
void loadBackup(const QString& backupName, QuaZip& zip) override;
|
||||
void loadingComplete() override;
|
||||
void createBackup(const QString& backupName, QuaZip& zip) override;
|
||||
void recoverBackup(const QString& backupName, QuaZip& zip) override;
|
||||
std::pair<bool, QString> recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) override;
|
||||
void deleteBackup(const QString& backupName) override;
|
||||
void consolidateBackup(const QString& backupName, QuaZip& zip) override;
|
||||
bool isCorruptedBackup(const QString& backupName) override;
|
||||
|
|
|
@ -30,7 +30,7 @@ public:
|
|||
virtual void loadBackup(const QString& backupName, QuaZip& zip) = 0;
|
||||
virtual void loadingComplete() = 0;
|
||||
virtual void createBackup(const QString& backupName, QuaZip& zip) = 0;
|
||||
virtual void recoverBackup(const QString& backupName, QuaZip& zip) = 0;
|
||||
virtual std::pair<bool, QString> recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) = 0;
|
||||
virtual void deleteBackup(const QString& backupName) = 0;
|
||||
virtual void consolidateBackup(const QString& backupName, QuaZip& zip) = 0;
|
||||
virtual bool isCorruptedBackup(const QString& backupName) = 0;
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
//
|
||||
|
||||
#include "ContentSettingsBackupHandler.h"
|
||||
#include "DomainContentBackupManager.h"
|
||||
|
||||
#if !defined(__clang__) && defined(__GNUC__)
|
||||
#pragma GCC diagnostic push
|
||||
|
@ -24,6 +25,7 @@
|
|||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
|
||||
static const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" };
|
||||
|
||||
ContentSettingsBackupHandler::ContentSettingsBackupHandler(DomainServerSettingsManager& domainServerSettingsManager) :
|
||||
_settingsManager(domainServerSettingsManager)
|
||||
|
@ -41,6 +43,26 @@ void ContentSettingsBackupHandler::createBackup(const QString& backupName, QuaZi
|
|||
DomainServerSettingsManager::IncludeContentSettings, DomainServerSettingsManager::NoDefaultSettings,
|
||||
DomainServerSettingsManager::ForBackup
|
||||
);
|
||||
QString prefixFormat = "(" + QRegExp::escape(AUTOMATIC_BACKUP_PREFIX) + "|" + QRegExp::escape(MANUAL_BACKUP_PREFIX) + ")";
|
||||
QString nameFormat = "(.+)";
|
||||
QString dateTimeFormat = "(" + DATETIME_FORMAT_RE + ")";
|
||||
QRegExp backupNameFormat { prefixFormat + nameFormat + "-" + dateTimeFormat + "\\.zip" };
|
||||
|
||||
QString name{ "" };
|
||||
QDateTime createdAt;
|
||||
|
||||
if (backupNameFormat.exactMatch(backupName)) {
|
||||
name = backupNameFormat.cap(2);
|
||||
auto dateTime = backupNameFormat.cap(3);
|
||||
createdAt = QDateTime::fromString(dateTime, DATETIME_FORMAT);
|
||||
}
|
||||
|
||||
QJsonObject installed_content {
|
||||
{ INSTALLED_CONTENT_NAME, name},
|
||||
{ INSTALLED_CONTENT_CREATION_TIME, createdAt.currentMSecsSinceEpoch()}
|
||||
};
|
||||
|
||||
contentSettingsJSON.insert(INSTALLED_CONTENT, installed_content);
|
||||
|
||||
// make a QJsonDocument using the object
|
||||
QJsonDocument contentSettingsDocument { contentSettingsJSON };
|
||||
|
@ -62,24 +84,48 @@ void ContentSettingsBackupHandler::createBackup(const QString& backupName, QuaZi
|
|||
}
|
||||
}
|
||||
|
||||
void ContentSettingsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) {
|
||||
std::pair<bool, QString> ContentSettingsBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) {
|
||||
if (!zip.setCurrentFile(CONTENT_SETTINGS_BACKUP_FILENAME)) {
|
||||
qWarning() << "Failed to find" << CONTENT_SETTINGS_BACKUP_FILENAME << "while recovering backup";
|
||||
return;
|
||||
QString errorStr("Failed to find " + CONTENT_SETTINGS_BACKUP_FILENAME + " while recovering backup");
|
||||
qWarning() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
|
||||
QuaZipFile zipFile { &zip };
|
||||
if (!zipFile.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open" << CONTENT_SETTINGS_BACKUP_FILENAME << "in backup";
|
||||
return;
|
||||
QString errorStr("Failed to open " + CONTENT_SETTINGS_BACKUP_FILENAME + " in backup");
|
||||
qCritical() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
|
||||
auto rawData = zipFile.readAll();
|
||||
zipFile.close();
|
||||
|
||||
QJsonDocument jsonDocument = QJsonDocument::fromJson(rawData);
|
||||
|
||||
if (!_settingsManager.restoreSettingsFromObject(jsonDocument.object(), ContentSettings)) {
|
||||
qCritical() << "Failed to restore settings from" << CONTENT_SETTINGS_BACKUP_FILENAME << "in content archive";
|
||||
if (zipFile.getZipError() != UNZ_OK) {
|
||||
QString errorStr("Failed to unzip " + CONTENT_SETTINGS_BACKUP_FILENAME + ": " + zipFile.getZipError());
|
||||
qCritical() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
|
||||
QJsonDocument jsonDocument = QJsonDocument::fromJson(rawData);
|
||||
QJsonObject jsonObject = jsonDocument.object();
|
||||
|
||||
auto archiveJson = jsonObject.find(INSTALLED_CONTENT)->toObject();
|
||||
|
||||
QJsonObject installed_content {
|
||||
{ INSTALLED_CONTENT_FILENAME, sourceFilename },
|
||||
{ INSTALLED_CONTENT_NAME, archiveJson[INSTALLED_CONTENT_NAME].toString()},
|
||||
{ INSTALLED_CONTENT_CREATION_TIME, archiveJson[INSTALLED_CONTENT_CREATION_TIME].toVariant().toLongLong() },
|
||||
{ INSTALLED_CONTENT_INSTALL_TIME, QDateTime::currentDateTime().currentMSecsSinceEpoch() },
|
||||
{ INSTALLED_CONTENT_INSTALLED_BY, username }
|
||||
};
|
||||
|
||||
jsonObject.insert(INSTALLED_CONTENT, installed_content);
|
||||
|
||||
if (!_settingsManager.restoreSettingsFromObject(jsonObject, ContentSettings)) {
|
||||
QString errorStr("Failed to restore settings from " + CONTENT_SETTINGS_BACKUP_FILENAME + " in content archive");
|
||||
qCritical() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
return { true, QString() };
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ public:
|
|||
|
||||
void createBackup(const QString& backupName, QuaZip& zip) override;
|
||||
|
||||
void recoverBackup(const QString& backupName, QuaZip& zip) override;
|
||||
std::pair<bool, QString> recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) override;
|
||||
|
||||
void deleteBackup(const QString& backupName) override {}
|
||||
|
||||
|
|
|
@ -42,9 +42,7 @@ const std::chrono::seconds DomainContentBackupManager::DEFAULT_PERSIST_INTERVAL
|
|||
|
||||
// Backup format looks like: daily_backup-TIMESTAMP.zip
|
||||
static const QString DATETIME_FORMAT { "yyyy-MM-dd_HH-mm-ss" };
|
||||
static const QString DATETIME_FORMAT_RE { "\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}" };
|
||||
static const QString AUTOMATIC_BACKUP_PREFIX { "autobackup-" };
|
||||
static const QString MANUAL_BACKUP_PREFIX { "backup-" };
|
||||
static const QString PRE_UPLOAD_SUFFIX{ "pre_upload" };
|
||||
static const QString MANUAL_BACKUP_NAME_RE { "[a-zA-Z0-9\\-_ ]+" };
|
||||
|
||||
void DomainContentBackupManager::addBackupHandler(BackupHandlerPointer handler) {
|
||||
|
@ -52,9 +50,10 @@ void DomainContentBackupManager::addBackupHandler(BackupHandlerPointer handler)
|
|||
}
|
||||
|
||||
DomainContentBackupManager::DomainContentBackupManager(const QString& backupDirectory,
|
||||
const QVariantList& backupRules,
|
||||
DomainServerSettingsManager& domainServerSettingsManager,
|
||||
std::chrono::milliseconds persistInterval,
|
||||
bool debugTimestampNow) :
|
||||
_settingsManager(domainServerSettingsManager),
|
||||
_consolidatedBackupDirectory(PathUtils::generateTemporaryDir()),
|
||||
_backupDirectory(backupDirectory), _persistInterval(persistInterval), _lastCheck(p_high_resolution_clock::now())
|
||||
{
|
||||
|
@ -63,7 +62,8 @@ DomainContentBackupManager::DomainContentBackupManager(const QString& backupDire
|
|||
// Make sure the backup directory exists.
|
||||
QDir(_backupDirectory).mkpath(".");
|
||||
|
||||
parseBackupRules(backupRules);
|
||||
static const QString BACKUP_RULES_KEYPATH = AUTOMATIC_CONTENT_ARCHIVES_GROUP + ".backup_rules";
|
||||
parseBackupRules(_settingsManager.valueOrDefaultValueForKeyPath(BACKUP_RULES_KEYPATH).toList());
|
||||
|
||||
constexpr int CONSOLIDATED_BACKUP_CLEANER_INTERVAL_MSECS = 30 * 1000;
|
||||
_consolidatedBackupCleanupTimer.setInterval(CONSOLIDATED_BACKUP_CLEANER_INTERVAL_MSECS);
|
||||
|
@ -170,7 +170,9 @@ bool DomainContentBackupManager::process() {
|
|||
return handler->getRecoveryStatus().first;
|
||||
});
|
||||
|
||||
if (!isStillRecovering) {
|
||||
// if an error occurred, don't restart the server so that the user
|
||||
// can be notified of the error and take action.
|
||||
if (!isStillRecovering && _recoveryError.isEmpty()) {
|
||||
_isRecovering = false;
|
||||
_recoveryFilename = "";
|
||||
emit recoveryCompleted();
|
||||
|
@ -277,7 +279,7 @@ void DomainContentBackupManager::deleteBackup(MiniPromise::Promise promise, cons
|
|||
});
|
||||
}
|
||||
|
||||
bool DomainContentBackupManager::recoverFromBackupZip(const QString& backupName, QuaZip& zip) {
|
||||
bool DomainContentBackupManager::recoverFromBackupZip(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename, bool rollingBack) {
|
||||
if (!zip.open(QuaZip::Mode::mdUnzip)) {
|
||||
qWarning() << "Failed to unzip file: " << backupName;
|
||||
return false;
|
||||
|
@ -286,7 +288,15 @@ bool DomainContentBackupManager::recoverFromBackupZip(const QString& backupName,
|
|||
_recoveryFilename = backupName;
|
||||
|
||||
for (auto& handler : _backupHandlers) {
|
||||
handler->recoverBackup(backupName, zip);
|
||||
bool success;
|
||||
QString errorStr;
|
||||
std::tie(success, errorStr) = handler->recoverBackup(backupName, zip, username, sourceFilename);
|
||||
if (!success) {
|
||||
if (!rollingBack) {
|
||||
_recoveryError = errorStr;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Successfully started recovering from " << backupName;
|
||||
|
@ -294,7 +304,7 @@ bool DomainContentBackupManager::recoverFromBackupZip(const QString& backupName,
|
|||
}
|
||||
}
|
||||
|
||||
void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, const QString& backupName) {
|
||||
void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise, const QString& backupName, const QString& username) {
|
||||
if (_isRecovering) {
|
||||
promise->resolve({
|
||||
{ "success", false }
|
||||
|
@ -304,12 +314,12 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise,
|
|||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "recoverFromBackup", Q_ARG(MiniPromise::Promise, promise),
|
||||
Q_ARG(const QString&, backupName));
|
||||
Q_ARG(const QString&, backupName), Q_ARG(const QString&, username));
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Recovering from" << backupName;
|
||||
|
||||
_recoveryError = "";
|
||||
bool success { false };
|
||||
QDir backupDir { _backupDirectory };
|
||||
auto backupFilePath { backupDir.filePath(backupName) };
|
||||
|
@ -317,7 +327,7 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise,
|
|||
if (backupFile.open(QIODevice::ReadOnly)) {
|
||||
QuaZip zip { &backupFile };
|
||||
|
||||
success = recoverFromBackupZip(backupName, zip);
|
||||
success = recoverFromBackupZip(backupName, zip, username, backupName);
|
||||
|
||||
backupFile.close();
|
||||
} else {
|
||||
|
@ -330,11 +340,11 @@ void DomainContentBackupManager::recoverFromBackup(MiniPromise::Promise promise,
|
|||
});
|
||||
}
|
||||
|
||||
void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup) {
|
||||
void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup, QString username) {
|
||||
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "recoverFromUploadedBackup", Q_ARG(MiniPromise::Promise, promise),
|
||||
Q_ARG(QByteArray, uploadedBackup));
|
||||
Q_ARG(QByteArray, uploadedBackup), Q_ARG(QString, username));
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -345,29 +355,51 @@ void DomainContentBackupManager::recoverFromUploadedBackup(MiniPromise::Promise
|
|||
QuaZip uploadedZip { &uploadedBackupBuffer };
|
||||
|
||||
QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip";
|
||||
bool success = recoverFromBackupZip(backupName, uploadedZip);
|
||||
bool success = recoverFromBackupZip(backupName, uploadedZip, username, QString());
|
||||
|
||||
promise->resolve({
|
||||
{ "success", success }
|
||||
});
|
||||
}
|
||||
|
||||
void DomainContentBackupManager::recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename) {
|
||||
void DomainContentBackupManager::recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename, const QString username, QString sourceFilename) {
|
||||
if (QThread::currentThread() != thread()) {
|
||||
QMetaObject::invokeMethod(this, "recoverFromUploadedFile", Q_ARG(MiniPromise::Promise, promise),
|
||||
Q_ARG(QString, uploadedFilename));
|
||||
Q_ARG(QString, uploadedFilename), Q_ARG(QString, username), Q_ARG(QString, sourceFilename));
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "Recovering from uploaded file -" << uploadedFilename;
|
||||
qDebug() << "Recovering from uploaded file -" << uploadedFilename << "source" << sourceFilename;
|
||||
bool success;
|
||||
QString path;
|
||||
std::tie(success, path) = createBackup(AUTOMATIC_BACKUP_PREFIX, PRE_UPLOAD_SUFFIX);
|
||||
if(!success) {
|
||||
_recoveryError = "Failed to create backup for " + PRE_UPLOAD_SUFFIX + " at " + path;
|
||||
qCWarning(domain_server) << _recoveryError;
|
||||
} else {
|
||||
QFile uploadedFile(uploadedFilename);
|
||||
QuaZip uploadedZip { &uploadedFile };
|
||||
|
||||
QFile uploadedFile(uploadedFilename);
|
||||
QuaZip uploadedZip { &uploadedFile };
|
||||
QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip";
|
||||
|
||||
QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip";
|
||||
bool success = recoverFromBackupZip(backupName, uploadedZip, username, sourceFilename);
|
||||
|
||||
bool success = recoverFromBackupZip(backupName, uploadedZip);
|
||||
if (!success) {
|
||||
|
||||
// attempt to rollback to
|
||||
QString filename;
|
||||
QDateTime filetime;
|
||||
if (getMostRecentBackup(PRE_UPLOAD_SUFFIX, filename, filetime)) {
|
||||
QFile uploadedFile(uploadedFilename);
|
||||
QuaZip uploadedZip { &uploadedFile };
|
||||
|
||||
QString backupName = MANUAL_BACKUP_PREFIX + "uploaded.zip";
|
||||
recoverFromBackupZip(backupName, uploadedZip, username, sourceFilename, true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
promise->resolve({
|
||||
{ "success", success }
|
||||
});
|
||||
|
@ -455,9 +487,44 @@ void DomainContentBackupManager::getAllBackupsAndStatus(MiniPromise::Promise pro
|
|||
{ "recoveryProgress", recoveryProgress }
|
||||
};
|
||||
|
||||
if(!_recoveryError.isEmpty()) {
|
||||
status["recoveryError"] = _recoveryError;
|
||||
}
|
||||
|
||||
|
||||
QString filename = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_FILENAME).toString();
|
||||
QString name = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_NAME).toString();
|
||||
auto creationTime = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_CREATION_TIME).toULongLong();
|
||||
|
||||
if (name.isEmpty() || creationTime == 0) {
|
||||
QString prefixFormat = "(" + QRegExp::escape(AUTOMATIC_BACKUP_PREFIX) + "|" + QRegExp::escape(MANUAL_BACKUP_PREFIX) + ")";
|
||||
QString nameFormat = "(.+)";
|
||||
QString dateTimeFormat = "(" + DATETIME_FORMAT_RE + ")";
|
||||
QRegExp backupNameFormat { prefixFormat + nameFormat + "-" + dateTimeFormat + "\\.zip" };
|
||||
|
||||
|
||||
if (backupNameFormat.exactMatch(filename)) {
|
||||
if (name.isEmpty()) {
|
||||
name = backupNameFormat.cap(2);
|
||||
}
|
||||
if (creationTime == 0) {
|
||||
auto dateTime = backupNameFormat.cap(3);
|
||||
creationTime = QDateTime::fromString(dateTime, DATETIME_FORMAT).toMSecsSinceEpoch();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap currentArchive;
|
||||
currentArchive["filename"] = filename;
|
||||
currentArchive["name"] = name;
|
||||
currentArchive["creation_time"] = creationTime;
|
||||
currentArchive["install_time"] = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALL_TIME).toULongLong();
|
||||
currentArchive["installed_by"] = _settingsManager.valueForKeyPath(CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALLED_BY).toString();
|
||||
|
||||
QVariantMap info {
|
||||
{ "backups", variantBackups },
|
||||
{ "status", status }
|
||||
{ "status", status },
|
||||
{ "installed_content", currentArchive }
|
||||
};
|
||||
|
||||
promise->resolve(info);
|
||||
|
|
|
@ -28,11 +28,22 @@
|
|||
#include <GenericThread.h>
|
||||
|
||||
#include "BackupHandler.h"
|
||||
#include "DomainServerSettingsManager.h"
|
||||
|
||||
#include <shared/MiniPromises.h>
|
||||
|
||||
#include <PortableHighResolutionClock.h>
|
||||
|
||||
const QString DATETIME_FORMAT_RE { "\\d{4}-\\d{2}-\\d{2}_\\d{2}-\\d{2}-\\d{2}" };
|
||||
const QString AUTOMATIC_BACKUP_PREFIX { "autobackup-" };
|
||||
const QString MANUAL_BACKUP_PREFIX { "backup-" };
|
||||
const QString INSTALLED_CONTENT = "installed_content";
|
||||
const QString INSTALLED_CONTENT_FILENAME = "filename";
|
||||
const QString INSTALLED_CONTENT_NAME = "name";
|
||||
const QString INSTALLED_CONTENT_CREATION_TIME = "creation_time";
|
||||
const QString INSTALLED_CONTENT_INSTALL_TIME = "install_time";
|
||||
const QString INSTALLED_CONTENT_INSTALLED_BY = "installed_by";
|
||||
|
||||
struct BackupItemInfo {
|
||||
BackupItemInfo(QString pId, QString pName, QString pAbsolutePath, QDateTime pCreatedAt, bool pIsManualBackup) :
|
||||
id(pId), name(pName), absolutePath(pAbsolutePath), createdAt(pCreatedAt), isManualBackup(pIsManualBackup) { };
|
||||
|
@ -71,7 +82,7 @@ public:
|
|||
static const std::chrono::seconds DEFAULT_PERSIST_INTERVAL;
|
||||
|
||||
DomainContentBackupManager(const QString& rootBackupDirectory,
|
||||
const QVariantList& settings,
|
||||
DomainServerSettingsManager& domainServerSettingsManager,
|
||||
std::chrono::milliseconds persistInterval = DEFAULT_PERSIST_INTERVAL,
|
||||
bool debugTimestampNow = false);
|
||||
|
||||
|
@ -84,9 +95,9 @@ public:
|
|||
public slots:
|
||||
void getAllBackupsAndStatus(MiniPromise::Promise promise);
|
||||
void createManualBackup(MiniPromise::Promise promise, const QString& name);
|
||||
void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName);
|
||||
void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup);
|
||||
void recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename);
|
||||
void recoverFromBackup(MiniPromise::Promise promise, const QString& backupName, const QString& username);
|
||||
void recoverFromUploadedBackup(MiniPromise::Promise promise, QByteArray uploadedBackup, QString username);
|
||||
void recoverFromUploadedFile(MiniPromise::Promise promise, QString uploadedFilename, QString username, QString sourceFilename);
|
||||
void deleteBackup(MiniPromise::Promise promise, const QString& backupName);
|
||||
|
||||
signals:
|
||||
|
@ -108,13 +119,15 @@ protected:
|
|||
|
||||
std::pair<bool, QString> createBackup(const QString& prefix, const QString& name);
|
||||
|
||||
bool recoverFromBackupZip(const QString& backupName, QuaZip& backupZip);
|
||||
bool recoverFromBackupZip(const QString& backupName, QuaZip& backupZip, const QString& username, const QString& sourceFilename, bool rollingBack = false);
|
||||
|
||||
private slots:
|
||||
void removeOldConsolidatedBackups();
|
||||
void consolidateBackupInternal(QString fileName);
|
||||
|
||||
private:
|
||||
DomainServerSettingsManager& _settingsManager;
|
||||
|
||||
QTimer _consolidatedBackupCleanupTimer;
|
||||
|
||||
const QString _consolidatedBackupDirectory;
|
||||
|
@ -126,6 +139,7 @@ private:
|
|||
std::unordered_map<QString, ConsolidatedBackupInfo> _consolidatedBackups;
|
||||
|
||||
std::atomic<bool> _isRecovering { false };
|
||||
QString _recoveryError;
|
||||
QString _recoveryFilename { };
|
||||
|
||||
p_high_resolution_clock::time_point _lastCheck;
|
||||
|
|
|
@ -307,11 +307,7 @@ DomainServer::DomainServer(int argc, char* argv[]) :
|
|||
}
|
||||
maybeHandleReplacementEntityFile();
|
||||
|
||||
|
||||
static const QString BACKUP_RULES_KEYPATH = AUTOMATIC_CONTENT_ARCHIVES_GROUP + ".backup_rules";
|
||||
auto backupRulesVariant = _settingsManager.valueOrDefaultValueForKeyPath(BACKUP_RULES_KEYPATH);
|
||||
|
||||
_contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), backupRulesVariant.toList()));
|
||||
_contentManager.reset(new DomainContentBackupManager(getContentBackupDir(), _settingsManager));
|
||||
|
||||
connect(_contentManager.get(), &DomainContentBackupManager::started, _contentManager.get(), [this](){
|
||||
_contentManager->addBackupHandler(BackupHandlerPointer(new EntitiesBackupHandler(getEntitiesFilePath(), getEntitiesReplacementFilePath())));
|
||||
|
@ -1961,6 +1957,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
QPointer<HTTPConnection> connectionPtr { connection };
|
||||
|
||||
auto nodeList = DependencyManager::get<LimitedNodeList>();
|
||||
QString username;
|
||||
|
||||
auto getSetting = [this](QString keyPath, QVariant& value) -> bool {
|
||||
|
||||
|
@ -2028,7 +2025,9 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
}
|
||||
|
||||
// all requests below require a cookie to prove authentication so check that first
|
||||
if (!isAuthenticatedRequest(connection, url)) {
|
||||
bool isAuthenticated { false };
|
||||
std::tie(isAuthenticated, username) = isAuthenticatedRequest(connection);
|
||||
if (!isAuthenticated) {
|
||||
// this is not an authenticated request
|
||||
// return true from the handler since it was handled with a 401 or re-direct to auth
|
||||
return true;
|
||||
|
@ -2194,7 +2193,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
|
||||
return true;
|
||||
} else if (url.path() == URI_RESTART) {
|
||||
connection->respond(HTTPConnection::StatusCode200);
|
||||
connection->respond(HTTPConnection::StatusCode204);
|
||||
restart();
|
||||
return true;
|
||||
} else if (url.path() == URI_API_METAVERSE_INFO) {
|
||||
|
@ -2333,8 +2332,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
|
||||
QJsonObject rootJSON;
|
||||
auto success = result["success"].toBool();
|
||||
rootJSON["success"] = success;
|
||||
QJsonDocument docJSON(rootJSON);
|
||||
QJsonDocument docJSON(QJsonObject::fromVariantMap(result));
|
||||
connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(),
|
||||
JSON_MIME_TYPE.toUtf8());
|
||||
});
|
||||
|
@ -2362,12 +2360,11 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
|
||||
QJsonObject rootJSON;
|
||||
auto success = result["success"].toBool();
|
||||
rootJSON["success"] = success;
|
||||
QJsonDocument docJSON(rootJSON);
|
||||
QJsonDocument docJSON(QJsonObject::fromVariantMap(result));
|
||||
connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(),
|
||||
JSON_MIME_TYPE.toUtf8());
|
||||
});
|
||||
_contentManager->recoverFromBackup(deferred, id);
|
||||
_contentManager->recoverFromBackup(deferred, id, username);
|
||||
return true;
|
||||
}
|
||||
} else if (connection->requestOperation() == QNetworkAccessManager::PutOperation) {
|
||||
|
@ -2467,8 +2464,7 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url
|
|||
|
||||
QJsonObject rootJSON;
|
||||
auto success = result["success"].toBool();
|
||||
rootJSON["success"] = success;
|
||||
QJsonDocument docJSON(rootJSON);
|
||||
QJsonDocument docJSON(QJsonObject::fromVariantMap(result));
|
||||
connectionPtr->respond(success ? HTTPConnection::StatusCode200 : HTTPConnection::StatusCode400, docJSON.toJson(),
|
||||
JSON_MIME_TYPE.toUtf8());
|
||||
});
|
||||
|
@ -2564,6 +2560,9 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite
|
|||
int sessionId = sessionIdBytes.toInt();
|
||||
|
||||
bool newUpload = itemName == "restore-file" || itemName == "restore-file-chunk-initial" || itemName == "restore-file-chunk-only";
|
||||
bool isAuthenticated;
|
||||
QString username;
|
||||
std::tie(isAuthenticated, username) = isAuthenticatedRequest(connection);
|
||||
|
||||
if (filename.endsWith(".zip", Qt::CaseInsensitive)) {
|
||||
static const QString TEMPORARY_CONTENT_FILEPATH { QDir::tempPath() + "/hifiUploadContent_XXXXXX.zip" };
|
||||
|
@ -2590,7 +2589,7 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite
|
|||
_pendingFileContent.close();
|
||||
|
||||
// Respond immediately - will timeout if we wait for restore.
|
||||
connection->respond(HTTPConnection::StatusCode200);
|
||||
connection->respond(HTTPConnection::StatusCode204);
|
||||
if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") {
|
||||
auto deferred = makePromise("recoverFromUploadedBackup");
|
||||
|
||||
|
@ -2598,7 +2597,7 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite
|
|||
_pendingContentFiles.erase(sessionId);
|
||||
});
|
||||
|
||||
_contentManager->recoverFromUploadedFile(deferred, _pendingFileContent.fileName());
|
||||
_contentManager->recoverFromUploadedFile(deferred, _pendingFileContent.fileName(), username, filename);
|
||||
}
|
||||
} else if (filename.endsWith(".json", Qt::CaseInsensitive)
|
||||
|| filename.endsWith(".json.gz", Qt::CaseInsensitive)) {
|
||||
|
@ -2608,14 +2607,16 @@ bool DomainServer::processPendingContent(HTTPConnection* connection, QString ite
|
|||
}
|
||||
QByteArray& _pendingUploadedContent = _pendingUploadedContents[sessionId];
|
||||
_pendingUploadedContent += dataChunk;
|
||||
connection->respond(HTTPConnection::StatusCode200);
|
||||
|
||||
if (itemName == "restore-file" || itemName == "restore-file-chunk-final" || itemName == "restore-file-chunk-only") {
|
||||
// invoke our method to hand the new octree file off to the octree server
|
||||
QMetaObject::invokeMethod(this, "handleOctreeFileReplacement",
|
||||
Qt::QueuedConnection, Q_ARG(QByteArray, _pendingUploadedContent));
|
||||
if (!handleOctreeFileReplacement(_pendingUploadedContent, filename, QString(), username)) {
|
||||
connection->respond(HTTPConnection::StatusCode400);
|
||||
return false;
|
||||
}
|
||||
_pendingUploadedContents.erase(sessionId);
|
||||
}
|
||||
connection->respond(HTTPConnection::StatusCode204);
|
||||
} else {
|
||||
connection->respond(HTTPConnection::StatusCode400);
|
||||
return false;
|
||||
|
@ -2685,13 +2686,14 @@ void DomainServer::profileRequestFinished() {
|
|||
}
|
||||
}
|
||||
|
||||
bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url) {
|
||||
std::pair<bool, QString> DomainServer::isAuthenticatedRequest(HTTPConnection* connection) {
|
||||
|
||||
const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
|
||||
const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
|
||||
const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
|
||||
const QString BASIC_AUTH_USERNAME_KEY_PATH = "security.http_username";
|
||||
const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http_password";
|
||||
static const QByteArray HTTP_COOKIE_HEADER_KEY = "Cookie";
|
||||
static const QString ADMIN_USERS_CONFIG_KEY = "admin-users";
|
||||
static const QString ADMIN_ROLES_CONFIG_KEY = "admin-roles";
|
||||
static const QString BASIC_AUTH_USERNAME_KEY_PATH = "security.http_username";
|
||||
static const QString BASIC_AUTH_PASSWORD_KEY_PATH = "security.http_password";
|
||||
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
|
||||
|
||||
const QByteArray UNAUTHENTICATED_BODY = "You do not have permission to access this domain-server.";
|
||||
|
||||
|
@ -2702,7 +2704,6 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
&& (adminUsersVariant.isValid() || adminRolesVariant.isValid())) {
|
||||
QString cookieString = connection->requestHeader(HTTP_COOKIE_HEADER_KEY);
|
||||
|
||||
const QString COOKIE_UUID_REGEX_STRING = HIFI_SESSION_COOKIE_KEY + "=([\\d\\w-]+)($|;)";
|
||||
QRegExp cookieUUIDRegex(COOKIE_UUID_REGEX_STRING);
|
||||
|
||||
QUuid cookieUUID;
|
||||
|
@ -2722,7 +2723,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
|
||||
if (_settingsManager.valueForKeyPath(ADMIN_USERS_CONFIG_KEY).toStringList().contains(profileUsername)) {
|
||||
// this is an authenticated user
|
||||
return true;
|
||||
return { true, profileUsername };
|
||||
}
|
||||
|
||||
// loop the roles of this user and see if they are in the admin-roles array
|
||||
|
@ -2732,7 +2733,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
foreach(const QString& userRole, sessionData.getRoles()) {
|
||||
if (adminRolesArray.contains(userRole)) {
|
||||
// this user has a role that allows them to administer the domain-server
|
||||
return true;
|
||||
return { true, profileUsername };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2740,7 +2741,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY);
|
||||
|
||||
// the user does not have allowed username or role, return 401
|
||||
return false;
|
||||
return { false, QString() };
|
||||
} else {
|
||||
static const QByteArray REQUESTED_WITH_HEADER = "X-Requested-With";
|
||||
static const QString XML_REQUESTED_WITH = "XMLHttpRequest";
|
||||
|
@ -2769,7 +2770,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
}
|
||||
|
||||
// we don't know about this user yet, so they are not yet authenticated
|
||||
return false;
|
||||
return { false, QString() };
|
||||
}
|
||||
} else if (_settingsManager.valueForKeyPath(BASIC_AUTH_USERNAME_KEY_PATH).isValid()) {
|
||||
// config file contains username and password combinations for basic auth
|
||||
|
@ -2798,7 +2799,7 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
"" : QCryptographicHash::hash(headerPassword.toUtf8(), QCryptographicHash::Sha256).toHex();
|
||||
|
||||
if (settingsUsername == headerUsername && hexHeaderPassword == settingsPassword) {
|
||||
return true;
|
||||
return { true, headerUsername };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2820,11 +2821,11 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl
|
|||
HTTPConnection::DefaultContentType, basicAuthHeader);
|
||||
|
||||
// not authenticated, bubble up false
|
||||
return false;
|
||||
return { false, QString() };
|
||||
|
||||
} else {
|
||||
// we don't have an OAuth URL + admin roles/usernames, so all users are authenticated
|
||||
return true;
|
||||
return { true, QString() };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3498,7 +3499,7 @@ void DomainServer::maybeHandleReplacementEntityFile() {
|
|||
}
|
||||
}
|
||||
|
||||
void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) {
|
||||
bool DomainServer::handleOctreeFileReplacement(QByteArray octreeFile, QString sourceFilename, QString name, QString username) {
|
||||
OctreeUtils::RawEntityData data;
|
||||
if (data.readOctreeDataInfoFromData(octreeFile)) {
|
||||
data.resetIdAndVersion();
|
||||
|
@ -3514,19 +3515,41 @@ void DomainServer::handleOctreeFileReplacement(QByteArray octreeFile) {
|
|||
// process it when it comes back up
|
||||
qInfo() << "Wrote octree replacement file to" << replacementFilePath << "- stopping server";
|
||||
|
||||
QJsonObject installed_content {
|
||||
{ INSTALLED_CONTENT_FILENAME, sourceFilename },
|
||||
{ INSTALLED_CONTENT_NAME, name },
|
||||
{ INSTALLED_CONTENT_CREATION_TIME, 0 },
|
||||
{ INSTALLED_CONTENT_INSTALL_TIME, QDateTime::currentDateTime().currentMSecsSinceEpoch() },
|
||||
{ INSTALLED_CONTENT_INSTALLED_BY, username }
|
||||
};
|
||||
|
||||
QJsonObject jsonObject { { INSTALLED_CONTENT, installed_content } };
|
||||
|
||||
_settingsManager.recurseJSONObjectAndOverwriteSettings(jsonObject, ContentSettings);
|
||||
|
||||
QMetaObject::invokeMethod(this, "restart", Qt::QueuedConnection);
|
||||
return true;
|
||||
} else {
|
||||
qWarning() << "Could not write replacement octree data to file - refusing to process";
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Received replacement octree file that is invalid - refusing to process";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static const QString CONTENT_SET_NAME_QUERY_PARAM = "name";
|
||||
|
||||
void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointer<ReceivedMessage> message) {
|
||||
qInfo() << "Received request to replace content from a url";
|
||||
auto node = DependencyManager::get<LimitedNodeList>()->findNodeWithAddr(message->getSenderSockAddr());
|
||||
if (node && node->getCanReplaceContent()) {
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
QString username;
|
||||
if (nodeData) {
|
||||
username = nodeData->getUsername();
|
||||
}
|
||||
// Convert message data into our URL
|
||||
QString url(message->getMessage());
|
||||
QUrl modelsURL = QUrl(url, QUrl::StrictMode);
|
||||
|
@ -3534,16 +3557,19 @@ void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointer<R
|
|||
QNetworkRequest request(modelsURL);
|
||||
QNetworkReply* reply = networkAccessManager.get(request);
|
||||
|
||||
qDebug() << "Downloading JSON from: " << modelsURL;
|
||||
qDebug() << "Downloading JSON from: " << modelsURL.toString(QUrl::FullyEncoded);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, [this, reply, modelsURL]() {
|
||||
connect(reply, &QNetworkReply::finished, [this, reply, modelsURL, username]() {
|
||||
QNetworkReply::NetworkError networkError = reply->error();
|
||||
if (networkError == QNetworkReply::NoError) {
|
||||
if (modelsURL.fileName().endsWith(".json.gz")) {
|
||||
handleOctreeFileReplacement(reply->readAll());
|
||||
QUrlQuery urlQuery(modelsURL.query(QUrl::FullyEncoded));
|
||||
|
||||
QString itemName = urlQuery.queryItemValue(CONTENT_SET_NAME_QUERY_PARAM);
|
||||
handleOctreeFileReplacement(reply->readAll(), modelsURL.fileName(), itemName, username);
|
||||
} else if (modelsURL.fileName().endsWith(".zip")) {
|
||||
auto deferred = makePromise("recoverFromUploadedBackup");
|
||||
_contentManager->recoverFromUploadedBackup(deferred, reply->readAll());
|
||||
_contentManager->recoverFromUploadedBackup(deferred, reply->readAll(), username);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Error downloading JSON from specified file: " << modelsURL;
|
||||
|
@ -3554,7 +3580,12 @@ void DomainServer::handleDomainContentReplacementFromURLRequest(QSharedPointer<R
|
|||
|
||||
void DomainServer::handleOctreeFileReplacementRequest(QSharedPointer<ReceivedMessage> message) {
|
||||
auto node = DependencyManager::get<NodeList>()->nodeWithLocalID(message->getSourceID());
|
||||
if (node->getCanReplaceContent()) {
|
||||
handleOctreeFileReplacement(message->readAll());
|
||||
if (node && node->getCanReplaceContent()) {
|
||||
QString username;
|
||||
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
|
||||
if (nodeData) {
|
||||
username = nodeData->getUsername();
|
||||
}
|
||||
handleOctreeFileReplacement(message->readAll(), QString(), QString(), username);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -99,7 +99,7 @@ private slots:
|
|||
|
||||
void handleDomainContentReplacementFromURLRequest(QSharedPointer<ReceivedMessage> message);
|
||||
void handleOctreeFileReplacementRequest(QSharedPointer<ReceivedMessage> message);
|
||||
void handleOctreeFileReplacement(QByteArray octreeFile);
|
||||
bool handleOctreeFileReplacement(QByteArray octreeFile, QString sourceFilename, QString name, QString username);
|
||||
|
||||
void processOctreeDataRequestMessage(QSharedPointer<ReceivedMessage> message);
|
||||
void processOctreeDataPersistMessage(QSharedPointer<ReceivedMessage> message);
|
||||
|
@ -194,7 +194,7 @@ private:
|
|||
QUrl oauthRedirectURL();
|
||||
QUrl oauthAuthorizationURL(const QUuid& stateUUID = QUuid::createUuid());
|
||||
|
||||
bool isAuthenticatedRequest(HTTPConnection* connection, const QUrl& url);
|
||||
std::pair<bool, QString> isAuthenticatedRequest(HTTPConnection* connection);
|
||||
|
||||
QNetworkReply* profileRequestGivenTokenReply(QNetworkReply* tokenReply);
|
||||
Headers setupCookieHeadersFromProfileReply(QNetworkReply* profileReply);
|
||||
|
|
|
@ -35,6 +35,11 @@ const QString MACHINE_FINGERPRINT_PERMISSIONS_KEYPATH = "security.machine_finger
|
|||
const QString GROUP_PERMISSIONS_KEYPATH = "security.group_permissions";
|
||||
const QString GROUP_FORBIDDENS_KEYPATH = "security.group_forbiddens";
|
||||
const QString AUTOMATIC_CONTENT_ARCHIVES_GROUP = "automatic_content_archives";
|
||||
const QString CONTENT_SETTINGS_INSTALLED_CONTENT_FILENAME = "installed_content.filename";
|
||||
const QString CONTENT_SETTINGS_INSTALLED_CONTENT_NAME = "installed_content.name";
|
||||
const QString CONTENT_SETTINGS_INSTALLED_CONTENT_CREATION_TIME = "installed_content.creation_time";
|
||||
const QString CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALL_TIME = "installed_content.install_time";
|
||||
const QString CONTENT_SETTINGS_INSTALLED_CONTENT_INSTALLED_BY = "installed_content.installed_by";
|
||||
|
||||
using GroupByUUIDKey = QPair<QUuid, QUuid>; // groupID, rankID
|
||||
|
||||
|
|
|
@ -57,36 +57,41 @@ void EntitiesBackupHandler::createBackup(const QString& backupName, QuaZip& zip)
|
|||
}
|
||||
}
|
||||
|
||||
void EntitiesBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip) {
|
||||
std::pair<bool, QString> EntitiesBackupHandler::recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) {
|
||||
if (!zip.setCurrentFile(ENTITIES_BACKUP_FILENAME)) {
|
||||
qWarning() << "Failed to find" << ENTITIES_BACKUP_FILENAME << "while recovering backup";
|
||||
return;
|
||||
QString errorStr("Failed to find " + ENTITIES_BACKUP_FILENAME + " while recovering backup");
|
||||
qWarning() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
QuaZipFile zipFile { &zip };
|
||||
if (!zipFile.open(QIODevice::ReadOnly)) {
|
||||
qCritical() << "Failed to open" << ENTITIES_BACKUP_FILENAME << "in backup";
|
||||
return;
|
||||
QString errorStr("Failed to open " + ENTITIES_BACKUP_FILENAME + " in backup");
|
||||
qCritical() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
auto rawData = zipFile.readAll();
|
||||
|
||||
zipFile.close();
|
||||
|
||||
if (zipFile.getZipError() != UNZ_OK) {
|
||||
QString errorStr("Failed to unzip " + ENTITIES_BACKUP_FILENAME + ": " + zipFile.getZipError());
|
||||
qCritical() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
|
||||
OctreeUtils::RawEntityData data;
|
||||
if (!data.readOctreeDataInfoFromData(rawData)) {
|
||||
qCritical() << "Unable to parse octree data during backup recovery";
|
||||
return;
|
||||
QString errorStr("Unable to parse octree data during backup recovery");
|
||||
qCritical() << errorStr;
|
||||
return { false, errorStr };
|
||||
}
|
||||
|
||||
data.resetIdAndVersion();
|
||||
|
||||
if (zipFile.getZipError() != UNZ_OK) {
|
||||
qCritical().nospace() << "Failed to unzip " << ENTITIES_BACKUP_FILENAME << ": " << zipFile.getZipError();
|
||||
return;
|
||||
}
|
||||
|
||||
QFile entitiesFile { _entitiesReplacementFilePath };
|
||||
|
||||
if (entitiesFile.open(QIODevice::WriteOnly)) {
|
||||
entitiesFile.write(data.toGzippedByteArray());
|
||||
}
|
||||
return { true, QString() };
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ public:
|
|||
void createBackup(const QString& backupName, QuaZip& zip) override;
|
||||
|
||||
// Recover from a full backup
|
||||
void recoverBackup(const QString& backupName, QuaZip& zip) override;
|
||||
std::pair<bool, QString> recoverBackup(const QString& backupName, QuaZip& zip, const QString& username, const QString& sourceFilename) override;
|
||||
|
||||
// Delete a skeleton backup
|
||||
void deleteBackup(const QString& backupName) override {}
|
||||
|
|
|
@ -94,6 +94,10 @@ ANDROID_PACKAGES = {
|
|||
'checksum': 'ddcb23df336b08017042ba4786db1d9e',
|
||||
'sharedLibFolder': 'lib',
|
||||
'includeLibs': {'libbreakpad_client.a'}
|
||||
},
|
||||
'webrtc': {
|
||||
'file': 'webrtc-20190626-android.tar.gz',
|
||||
'checksum': 'e2dccd3d8efdcba6d428c87ba7fb2a53'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -71,16 +71,19 @@ endif()
|
|||
|
||||
if 'Windows' == system:
|
||||
self.exe = os.path.join(self.path, 'vcpkg.exe')
|
||||
self.bootstrapCmd = 'bootstrap-vcpkg.bat'
|
||||
self.vcpkgUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/vcpkg-win32.tar.gz?versionId=YZYkDejDRk7L_hrK_WVFthWvisAhbDzZ'
|
||||
self.vcpkgHash = '3e0ff829a74956491d57666109b3e6b5ce4ed0735c24093884317102387b2cb1b2cd1ff38af9ed9173501f6e32ffa05cc6fe6d470b77a71ca1ffc3e0aa46ab9e'
|
||||
self.hostTriplet = 'x64-windows'
|
||||
elif 'Darwin' == system:
|
||||
self.exe = os.path.join(self.path, 'vcpkg')
|
||||
self.bootstrapCmd = 'bootstrap-vcpkg.sh'
|
||||
self.vcpkgUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/vcpkg-osx.tar.gz?versionId=_fhqSxjfrtDJBvEsQ8L_ODcdUjlpX9cc'
|
||||
self.vcpkgHash = '519d666d02ef22b87c793f016ca412e70f92e1d55953c8f9bd4ee40f6d9f78c1df01a6ee293907718f3bbf24075cc35492fb216326dfc50712a95858e9cbcb4d'
|
||||
self.hostTriplet = 'x64-osx'
|
||||
else:
|
||||
self.exe = os.path.join(self.path, 'vcpkg')
|
||||
self.bootstrapCmd = 'bootstrap-vcpkg.sh'
|
||||
self.vcpkgUrl = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/vcpkg-linux.tar.gz?versionId=97Nazh24etEVKWz33XwgLY0bvxEfZgMU'
|
||||
self.vcpkgHash = '6a1ce47ef6621e699a4627e8821ad32528c82fce62a6939d35b205da2d299aaa405b5f392df4a9e5343dd6a296516e341105fbb2dd8b48864781d129d7fba10d'
|
||||
self.hostTriplet = 'x64-linux'
|
||||
|
@ -141,8 +144,14 @@ endif()
|
|||
downloadVcpkg = True
|
||||
|
||||
if downloadVcpkg:
|
||||
print("Fetching vcpkg from {} to {}".format(self.vcpkgUrl, self.path))
|
||||
hifi_utils.downloadAndExtract(self.vcpkgUrl, self.path, self.vcpkgHash)
|
||||
if "HIFI_VCPKG_BOOTSTRAP" in os.environ:
|
||||
print("Cloning vcpkg from github to {}".format(self.path))
|
||||
hifi_utils.executeSubprocess(['git', 'clone', 'git@github.com:microsoft/vcpkg.git', self.path])
|
||||
print("Bootstrapping vcpkg")
|
||||
hifi_utils.executeSubprocess([self.bootstrapCmd], folder=self.path)
|
||||
else:
|
||||
print("Fetching vcpkg from {} to {}".format(self.vcpkgUrl, self.path))
|
||||
hifi_utils.downloadAndExtract(self.vcpkgUrl, self.path, self.vcpkgHash)
|
||||
|
||||
print("Replacing port files")
|
||||
portsPath = os.path.join(self.path, 'ports')
|
||||
|
@ -259,7 +268,7 @@ endif()
|
|||
url = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/qt5-install-5.12.3-macos3.tar.gz'
|
||||
elif platform.system() == 'Linux':
|
||||
if platform.linux_distribution()[1][:3] == '16.':
|
||||
url = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-16.04.tar.gz'
|
||||
url = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-16.04-with-symbols.tar.gz'
|
||||
elif platform.linux_distribution()[1][:3] == '18.':
|
||||
url = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-18.04.tar.gz'
|
||||
else:
|
||||
|
|
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_agree_headnod.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_agree_headnod.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_agree_headnodyes.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_agree_headnodyes.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_clap01_all.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_clap01_all.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_clap02_all.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_clap02_all.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_clap03_all.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_clap03_all.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_point01_all.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_point01_all.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_raisehand01_all.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_raisehand01_all.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_raisehand03_all.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_raisehand03_all.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/emote_raisehand04_all.fbx
Normal file
BIN
interface/resources/avatar/animations/emote_raisehand04_all.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_emote_clap_all.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_emote_clap_all.fbx
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_idle.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_idle.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_idle03.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_idle03.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_talk02.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_talk02.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_talk03.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_talk03.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/sitting_talk04.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_talk04.fbx
Normal file
Binary file not shown.
BIN
interface/resources/avatar/animations/teleport.fbx
Normal file
BIN
interface/resources/avatar/animations/teleport.fbx
Normal file
Binary file not shown.
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -33,33 +33,6 @@ var EventBridge;
|
|||
// replace the TempEventBridge with the real one.
|
||||
var tempEventBridge = EventBridge;
|
||||
EventBridge = channel.objects.eventBridge;
|
||||
EventBridge.audioOutputDeviceChanged.connect(function(deviceName) {
|
||||
navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(function(mediaStream) {
|
||||
navigator.mediaDevices.enumerateDevices().then(function(devices) {
|
||||
devices.forEach(function(device) {
|
||||
if (device.kind == "audiooutput") {
|
||||
if (device.label == deviceName){
|
||||
console.log("Changing HTML audio output to device " + device.label);
|
||||
var deviceId = device.deviceId;
|
||||
var videos = document.getElementsByTagName("video");
|
||||
for (var i = 0; i < videos.length; i++){
|
||||
videos[i].setSinkId(deviceId);
|
||||
}
|
||||
var audios = document.getElementsByTagName("audio");
|
||||
for (var i = 0; i < audios.length; i++){
|
||||
audios[i].setSinkId(deviceId);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
}).catch(function(err) {
|
||||
console.log("Error getting media devices"+ err.name + ": " + err.message);
|
||||
});
|
||||
}).catch(function(err) {
|
||||
console.log("Error getting user media"+ err.name + ": " + err.message);
|
||||
});
|
||||
});
|
||||
|
||||
// To be able to update the state of the output device selection for every element added to the DOM
|
||||
// we need to listen to events that might precede the addition of this elements.
|
||||
|
|
|
@ -14,7 +14,6 @@ Item {
|
|||
HifiStyles.HifiConstants { id: hifistyles }
|
||||
|
||||
height: 600
|
||||
property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
|
||||
property alias url: webview.url
|
||||
|
||||
property bool canGoBack: webview.canGoBack
|
||||
|
@ -30,6 +29,10 @@ Item {
|
|||
webview.profile = profile;
|
||||
}
|
||||
|
||||
onUrlChanged: {
|
||||
permissionPopupBackground.visible = false;
|
||||
}
|
||||
|
||||
WebEngineView {
|
||||
id: webview
|
||||
objectName: "webEngineView"
|
||||
|
@ -84,7 +87,14 @@ Item {
|
|||
}
|
||||
|
||||
onFeaturePermissionRequested: {
|
||||
grantFeaturePermission(securityOrigin, feature, false);
|
||||
if (permissionPopupBackground.visible === true) {
|
||||
console.log("Browser engine requested a new permission, but user is already being presented with a different permission request. Aborting request for new permission...");
|
||||
return;
|
||||
}
|
||||
permissionPopupBackground.securityOrigin = securityOrigin;
|
||||
permissionPopupBackground.feature = feature;
|
||||
|
||||
permissionPopupBackground.visible = true;
|
||||
}
|
||||
|
||||
onLoadingChanged: {
|
||||
|
@ -122,4 +132,11 @@ Item {
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
HifiControls.PermissionPopupBackground {
|
||||
id: permissionPopupBackground
|
||||
onSendPermission: {
|
||||
webview.grantFeaturePermission(securityOrigin, feature, shouldGivePermission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,7 +30,6 @@ Windows.Window {
|
|||
|
||||
signal selfDestruct();
|
||||
|
||||
property var flags: 0;
|
||||
property var additionalFlags: 0;
|
||||
property var overrideFlags: 0;
|
||||
|
||||
|
@ -158,8 +157,7 @@ Windows.Window {
|
|||
if (Qt.platform.os !== "windows" && (root.additionalFlags & Desktop.ALWAYS_ON_TOP)) {
|
||||
nativeWindowFlags |= Qt.WindowStaysOnTopHint;
|
||||
}
|
||||
root.flags = root.overrideFlags || nativeWindowFlags;
|
||||
nativeWindow.flags = root.flags;
|
||||
nativeWindow.flags = root.overrideFlags || nativeWindowFlags;
|
||||
|
||||
nativeWindow.x = interactiveWindowPosition.x;
|
||||
nativeWindow.y = interactiveWindowPosition.y;
|
||||
|
@ -317,7 +315,7 @@ Windows.Window {
|
|||
// set invisible on close, to make it not re-appear unintended after switching PresentationMode
|
||||
interactiveWindowVisible = false;
|
||||
|
||||
if ((root.flags & Desktop.CLOSE_BUTTON_HIDES) !== Desktop.CLOSE_BUTTON_HIDES) {
|
||||
if ((root.additionalFlags & Desktop.CLOSE_BUTTON_HIDES) !== Desktop.CLOSE_BUTTON_HIDES) {
|
||||
selfDestruct();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,4 +5,6 @@ Text {
|
|||
style: Text.Outline;
|
||||
styleColor: "black";
|
||||
font.pixelSize: 12;
|
||||
font.bold: true;
|
||||
font.family: "monospace";
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import QtWebChannel 1.0
|
|||
import QtQuick.Controls 2.2
|
||||
|
||||
import stylesUit 1.0 as StylesUIt
|
||||
import controlsUit 1.0 as ControlsUit
|
||||
|
||||
Item {
|
||||
id: flick
|
||||
|
@ -28,6 +29,10 @@ Item {
|
|||
|
||||
property bool blurOnCtrlShift: true
|
||||
|
||||
onUrlChanged: {
|
||||
permissionPopupBackground.visible = false;
|
||||
}
|
||||
|
||||
StylesUIt.HifiConstants {
|
||||
id: hifi
|
||||
}
|
||||
|
@ -141,7 +146,15 @@ Item {
|
|||
}
|
||||
|
||||
onFeaturePermissionRequested: {
|
||||
grantFeaturePermission(securityOrigin, feature, false);
|
||||
if (permissionPopupBackground.visible === true) {
|
||||
console.log("Browser engine requested a new permission, but user is already being presented with a different permission request. Aborting request for new permission...");
|
||||
return;
|
||||
}
|
||||
|
||||
permissionPopupBackground.securityOrigin = securityOrigin;
|
||||
permissionPopupBackground.feature = feature;
|
||||
|
||||
permissionPopupBackground.visible = true;
|
||||
}
|
||||
|
||||
//disable popup
|
||||
|
@ -186,4 +199,12 @@ Item {
|
|||
webViewCore.focus = false;
|
||||
}
|
||||
}
|
||||
|
||||
ControlsUit.PermissionPopupBackground {
|
||||
id: permissionPopupBackground
|
||||
onSendPermission: {
|
||||
webViewCore.grantFeaturePermission(securityOrigin, feature, shouldGivePermission);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -10,10 +10,10 @@
|
|||
|
||||
import QtQuick 2.7
|
||||
import QtWebEngine 1.5
|
||||
import controlsUit 1.0 as ControlsUit
|
||||
|
||||
WebEngineView {
|
||||
id: root
|
||||
|
||||
Component.onCompleted: {
|
||||
console.log("Connecting JS messaging to Hifi Logging")
|
||||
// Ensure the JS from the web-engine makes it to our logging
|
||||
|
@ -22,6 +22,10 @@ WebEngineView {
|
|||
});
|
||||
}
|
||||
|
||||
onUrlChanged: {
|
||||
permissionPopupBackground.visible = false;
|
||||
}
|
||||
|
||||
onLoadingChanged: {
|
||||
// Required to support clicking on "hifi://" links
|
||||
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
|
||||
|
@ -37,6 +41,21 @@ WebEngineView {
|
|||
WebSpinner { }
|
||||
|
||||
onFeaturePermissionRequested: {
|
||||
grantFeaturePermission(securityOrigin, feature, false);
|
||||
if (permissionPopupBackground.visible === true) {
|
||||
console.log("Browser engine requested a new permission, but user is already being presented with a different permission request. Aborting request for new permission...");
|
||||
return;
|
||||
}
|
||||
permissionPopupBackground.securityOrigin = securityOrigin;
|
||||
permissionPopupBackground.feature = feature;
|
||||
|
||||
permissionPopupBackground.visible = true;
|
||||
}
|
||||
|
||||
ControlsUit.PermissionPopupBackground {
|
||||
id: permissionPopupBackground
|
||||
z: 100
|
||||
onSendPermission: {
|
||||
root.grantFeaturePermission(securityOrigin, feature, shouldGivePermission);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ FocusScope {
|
|||
property alias editable: comboBox.editable
|
||||
property alias comboBox: comboBox
|
||||
readonly property alias currentText: comboBox.currentText;
|
||||
property alias displayText: comboBox.displayText;
|
||||
property alias currentIndex: comboBox.currentIndex;
|
||||
property int currentHighLightedIndex: comboBox.currentIndex;
|
||||
|
||||
|
|
90
interface/resources/qml/controlsUit/PermissionPopup.qml
Normal file
90
interface/resources/qml/controlsUit/PermissionPopup.qml
Normal file
|
@ -0,0 +1,90 @@
|
|||
import QtQuick 2.5
|
||||
import QtWebEngine 1.5
|
||||
import QtQuick.Layouts 1.3
|
||||
import controlsUit 1.0 as HifiControls
|
||||
import stylesUit 1.0 as HifiStyles
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
width: 750
|
||||
height: 210
|
||||
color: hifi.colors.white
|
||||
|
||||
anchors.centerIn: parent
|
||||
readonly property var permissionLanguage: ({
|
||||
[WebEngineView.MediaAudioCapture]: "access an audio input device",
|
||||
[WebEngineView.MediaVideoCapture]: "access a video device, like your webcam",
|
||||
[WebEngineView.MediaAudioVideoCapture]: "access an audio input device and video device",
|
||||
[WebEngineView.Geolocation]: "access your location",
|
||||
[WebEngineView.DesktopVideoCapture]: "capture video from your desktop",
|
||||
[WebEngineView.DesktopAudioVideoCapture]: "capture audio and video from your desktop",
|
||||
"none": "Undefined permission being requested"
|
||||
})
|
||||
property string currentRequestedPermission
|
||||
signal permissionButtonPressed(int buttonNumber)
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
Layout.preferredHeight: 75
|
||||
Layout.preferredWidth: parent.width
|
||||
|
||||
HifiStyles.RalewayBold {
|
||||
text: "REQUEST FOR DEVICE ACCESS"
|
||||
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
wrapMode: Text.WordWrap
|
||||
color: hifi.colors.black
|
||||
size: 30
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredHeight: 35
|
||||
Layout.preferredWidth: parent.width
|
||||
|
||||
HifiStyles.RalewayLight {
|
||||
text: "This website is attempting to " + root.permissionLanguage[root.currentRequestedPermission] + "."
|
||||
|
||||
anchors.centerIn: parent
|
||||
wrapMode: Text.Wrap
|
||||
size: 20
|
||||
color: hifi.colors.black
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.preferredHeight: 100
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.topMargin: 35
|
||||
property int space: 8
|
||||
|
||||
HifiControls.Button {
|
||||
text: "Don't Allow"
|
||||
|
||||
anchors.right: parent.horizontalCenter
|
||||
anchors.rightMargin: parent.space
|
||||
width: 125
|
||||
color: hifi.buttons.red
|
||||
height: 38
|
||||
onClicked: {
|
||||
root.permissionButtonPressed(0);
|
||||
}
|
||||
}
|
||||
HifiControls.Button {
|
||||
text: "Yes allow access"
|
||||
|
||||
anchors.left: parent.horizontalCenter
|
||||
anchors.leftMargin: parent.space
|
||||
color: hifi.buttons.blue
|
||||
width: 155
|
||||
height: 38
|
||||
onClicked: {
|
||||
root.permissionButtonPressed(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
import QtQuick 2.5
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
color: Qt.rgba(0, 0, 0, 0.5);
|
||||
visible: false
|
||||
property string securityOrigin: 'none'
|
||||
property string feature: 'none'
|
||||
signal sendPermission(string securityOrigin, string feature, bool shouldGivePermission)
|
||||
|
||||
onFeatureChanged: {
|
||||
permissionPopupItem.currentRequestedPermission = feature;
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
propagateComposedEvents: false
|
||||
}
|
||||
|
||||
PermissionPopup {
|
||||
id: permissionPopupItem
|
||||
onPermissionButtonPressed: {
|
||||
if (buttonNumber === 0) {
|
||||
root.sendPermission(securityOrigin, feature, false);
|
||||
} else {
|
||||
root.sendPermission(securityOrigin, feature, true);
|
||||
}
|
||||
root.visible = false;
|
||||
securityOrigin = 'none';
|
||||
feature = 'none';
|
||||
}
|
||||
}
|
||||
}
|
|
@ -15,6 +15,8 @@ Key 1.0 Key.qml
|
|||
Keyboard 1.0 Keyboard.qml
|
||||
Label 1.0 Label.qml
|
||||
QueuedButton 1.0 QueuedButton.qml
|
||||
PermissionPopup 1.0 PermissionPopup.qml
|
||||
PermissionPopupBackground 1.0 PermissionPopupBackground.qml
|
||||
RadioButton 1.0 RadioButton.qml
|
||||
ScrollBar 1.0 ScrollBar.qml
|
||||
Separator 1.0 Separator.qml
|
||||
|
|
|
@ -166,16 +166,16 @@ Rectangle {
|
|||
x: 2 * margins.paddings;
|
||||
width: parent.width;
|
||||
// switch heights + 2 * top margins
|
||||
height: (root.switchHeight) * 3 + 48;
|
||||
height: (root.switchHeight) * 6 + 48;
|
||||
anchors.top: firstSeparator.bottom;
|
||||
anchors.topMargin: 10;
|
||||
|
||||
// mute is in its own row
|
||||
Item {
|
||||
id: switchContainer;
|
||||
x: margins.paddings;
|
||||
width: parent.width / 2;
|
||||
height: parent.height;
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left;
|
||||
HifiControlsUit.Switch {
|
||||
id: muteMic;
|
||||
|
@ -222,12 +222,29 @@ Rectangle {
|
|||
}
|
||||
|
||||
HifiControlsUit.Switch {
|
||||
id: pttSwitch
|
||||
id: acousticEchoCancellationSwitch;
|
||||
height: root.switchHeight;
|
||||
switchWidth: root.switchWidth;
|
||||
anchors.top: noiseReductionSwitch.bottom
|
||||
anchors.topMargin: 24
|
||||
anchors.left: parent.left
|
||||
labelTextOn: "Echo Cancellation";
|
||||
labelTextSize: 16;
|
||||
backgroundOnColor: "#E3E3E3";
|
||||
checked: AudioScriptingInterface.acousticEchoCancellation;
|
||||
onCheckedChanged: {
|
||||
AudioScriptingInterface.acousticEchoCancellation = checked;
|
||||
checked = Qt.binding(function() { return AudioScriptingInterface.acousticEchoCancellation; });
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.Switch {
|
||||
id: pttSwitch
|
||||
height: root.switchHeight;
|
||||
switchWidth: root.switchWidth;
|
||||
anchors.top: acousticEchoCancellationSwitch.bottom;
|
||||
anchors.topMargin: 24
|
||||
anchors.left: parent.left
|
||||
labelTextOn: (bar.currentIndex === 0) ? qsTr("Push To Talk (T)") : qsTr("Push To Talk");
|
||||
labelTextSize: 16;
|
||||
backgroundOnColor: "#E3E3E3";
|
||||
|
@ -298,7 +315,6 @@ Rectangle {
|
|||
checked = Qt.binding(function() { return AudioScriptingInterface.isStereoInput; }); // restore binding
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -39,8 +39,8 @@ Rectangle {
|
|||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
AudioScriptingInterface.noiseGateOpened.connect(function() { gated = false; });
|
||||
AudioScriptingInterface.noiseGateClosed.connect(function() { gated = true; });
|
||||
AudioScriptingInterface.noiseGateOpened.connect(function() { micBar.gated = false; });
|
||||
AudioScriptingInterface.noiseGateClosed.connect(function() { micBar.gated = true; });
|
||||
HMD.displayModeChanged.connect(function() {
|
||||
muted = AudioScriptingInterface.muted;
|
||||
pushToTalk = AudioScriptingInterface.pushToTalk;
|
||||
|
@ -151,7 +151,7 @@ Rectangle {
|
|||
readonly property string yellow: "#C0C000";
|
||||
readonly property string fill: "#55000000";
|
||||
readonly property string border: standalone ? "#80FFFFFF" : "#55FFFFFF";
|
||||
readonly property string icon: (muted || clipping) ? mutedColor : gated ? gatedColor : unmutedColor;
|
||||
readonly property string icon: (muted || clipping) ? mutedColor : micBar.gated ? gatedColor : unmutedColor;
|
||||
}
|
||||
|
||||
Item {
|
||||
|
@ -169,7 +169,7 @@ Rectangle {
|
|||
Image {
|
||||
id: image;
|
||||
source: (pushToTalk) ? pushToTalkIcon : muted ? mutedIcon :
|
||||
clipping ? clippingIcon : gated ? gatedIcon : unmutedIcon;
|
||||
clipping ? clippingIcon : micBar.gated ? gatedIcon : unmutedIcon;
|
||||
width: 29;
|
||||
height: 32;
|
||||
anchors {
|
||||
|
|
|
@ -786,7 +786,7 @@ Rectangle {
|
|||
}
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = function() {
|
||||
Commerce.replaceContentSet(root.itemHref, root.certificateId);
|
||||
Commerce.replaceContentSet(root.itemHref, root.certificateId, root.itemName);
|
||||
lightboxPopup.visible = false;
|
||||
rezzedNotifContainer.visible = true;
|
||||
rezzedNotifContainerTimer.start();
|
||||
|
|
|
@ -36,7 +36,7 @@ Rectangle {
|
|||
break;
|
||||
case "content set":
|
||||
urlHandler.handleUrl("hifi://localhost/0,0,0");
|
||||
Commerce.replaceContentSet(toUrl(resource), "");
|
||||
Commerce.replaceContentSet(toUrl(resource), "", "");
|
||||
break;
|
||||
case "entity":
|
||||
case "wearable":
|
||||
|
|
|
@ -729,7 +729,7 @@ Item {
|
|||
onClicked: {
|
||||
Tablet.playSound(TabletEnums.ButtonClick);
|
||||
if (root.itemType === "contentSet") {
|
||||
sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref, certID: root.certificateId});
|
||||
sendToPurchases({method: 'showReplaceContentLightbox', itemHref: root.itemHref, certID: root.certificateId, itemName: root.itemName});
|
||||
} else if (root.itemType === "avatar") {
|
||||
sendToPurchases({method: 'showChangeAvatarLightbox', itemName: root.itemName, itemHref: root.itemHref});
|
||||
} else if (root.itemType === "app") {
|
||||
|
|
|
@ -609,7 +609,7 @@ Rectangle {
|
|||
}
|
||||
lightboxPopup.button2text = "CONFIRM";
|
||||
lightboxPopup.button2method = function() {
|
||||
Commerce.replaceContentSet(msg.itemHref, msg.certID);
|
||||
Commerce.replaceContentSet(msg.itemHref, msg.certID, msg.itemName);
|
||||
lightboxPopup.visible = false;
|
||||
};
|
||||
lightboxPopup.visible = true;
|
||||
|
|
|
@ -27,11 +27,15 @@ Rectangle {
|
|||
|
||||
HifiConstants { id: hifi }
|
||||
|
||||
readonly property real treeScale: 32768; // ~20 miles.. This is the number of meters of the 0.0 to 1.0 voxel universe
|
||||
readonly property real halfTreeScale: treeScale / 2;
|
||||
|
||||
// This controls the LOD. Larger number will make smaller voxels visible at greater distance.
|
||||
readonly property real defaultOctreeSizeScale: treeScale * 400.0
|
||||
// This controls the LOD. Larger number will make smaller objects visible at greater distance.
|
||||
readonly property real defaultMaxVisibilityDistance: 400.0
|
||||
readonly property real unitElementMaxExtent: Math.sqrt(3.0) * 0.5
|
||||
|
||||
function visibilityDistanceToLODAngleDeg(visibilityDistance) {
|
||||
var lodHalfAngle = Math.atan(unitElementMaxExtent / visibilityDistance);
|
||||
var lodAngle = lodHalfAngle * 2.0;
|
||||
return lodAngle * 180.0 / Math.PI;
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.margins: 10
|
||||
|
@ -71,7 +75,7 @@ Rectangle {
|
|||
id: adjustCheckbox
|
||||
boxSize: 20
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
onCheckedChanged: LODManager.setAutomaticLODAdjust(!checked);
|
||||
onCheckedChanged: LODManager.setAutomaticLODAdjust(!adjustCheckbox.checked);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -89,10 +93,10 @@ Rectangle {
|
|||
anchors.right: parent.right
|
||||
minimumValue: 5
|
||||
maximumValue: 2000
|
||||
value: LODManager.getOctreeSizeScale() / treeScale
|
||||
value: defaultMaxVisibilityDistance
|
||||
tickmarksEnabled: false
|
||||
onValueChanged: {
|
||||
LODManager.setOctreeSizeScale(value * treeScale);
|
||||
LODManager.lodAngleDeg = visibilityDistanceToLODAngleDeg(slider.value);
|
||||
whatYouCanSeeLabel.text = LODManager.getLODFeedbackText()
|
||||
}
|
||||
}
|
||||
|
@ -106,7 +110,7 @@ Rectangle {
|
|||
colorScheme: root.colorScheme
|
||||
height: 30
|
||||
onClicked: {
|
||||
slider.value = defaultOctreeSizeScale/treeScale
|
||||
slider.value = defaultMaxVisibilityDistance
|
||||
adjustCheckbox.checked = false
|
||||
LODManager.setAutomaticLODAdjust(adjustCheckbox.checked);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,379 @@
|
|||
//
|
||||
// GraphicsSettings.qml
|
||||
// qml\hifi\dialogs\graphics
|
||||
//
|
||||
// Created by Zach Fox on 2019-07-10
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import Hifi 1.0 as Hifi
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import QtQuick.Layouts 1.12
|
||||
import stylesUit 1.0 as HifiStylesUit
|
||||
import controlsUit 1.0 as HifiControlsUit
|
||||
import "qrc:////qml//controls" as HifiControls
|
||||
import PerformanceEnums 1.0
|
||||
|
||||
Item {
|
||||
HifiStylesUit.HifiConstants { id: hifi; }
|
||||
|
||||
id: root;
|
||||
anchors.fill: parent
|
||||
|
||||
ColumnLayout {
|
||||
id: graphicsSettingsColumnLayout
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 26
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 26
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: HMD.active ? 80 : 0
|
||||
spacing: 8
|
||||
|
||||
ColumnLayout {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.topMargin: 18
|
||||
spacing: 0
|
||||
|
||||
HifiStylesUit.RalewayRegular {
|
||||
text: "GRAPHICS SETTINGS"
|
||||
Layout.maximumWidth: parent.width
|
||||
height: 30
|
||||
size: 16
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.topMargin: 10
|
||||
Layout.preferredWidth: parent.width
|
||||
spacing: 0
|
||||
|
||||
HifiControlsUit.RadioButton {
|
||||
id: performanceLow
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
height: 18
|
||||
fontSize: 16
|
||||
leftPadding: 0
|
||||
text: "Low"
|
||||
checked: Performance.getPerformancePreset() === PerformanceEnums.LOW
|
||||
onClicked: {
|
||||
Performance.setPerformancePreset(PerformanceEnums.LOW);
|
||||
root.refreshAllDropdowns();
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.RadioButton {
|
||||
id: performanceMedium
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
height: 18
|
||||
fontSize: 16
|
||||
leftPadding: 0
|
||||
text: "Medium"
|
||||
checked: Performance.getPerformancePreset() === PerformanceEnums.MID
|
||||
onClicked: {
|
||||
Performance.setPerformancePreset(PerformanceEnums.MID);
|
||||
root.refreshAllDropdowns();
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.RadioButton {
|
||||
id: performanceHigh
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
height: 18
|
||||
fontSize: 16
|
||||
leftPadding: 0
|
||||
text: "High"
|
||||
checked: Performance.getPerformancePreset() === PerformanceEnums.HIGH
|
||||
onClicked: {
|
||||
Performance.setPerformancePreset(PerformanceEnums.HIGH);
|
||||
root.refreshAllDropdowns();
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.RadioButton {
|
||||
id: performanceCustom
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
height: 18
|
||||
fontSize: 16
|
||||
leftPadding: 0
|
||||
text: "Custom"
|
||||
checked: Performance.getPerformancePreset() === PerformanceEnums.UNKNOWN
|
||||
onClicked: {
|
||||
Performance.setPerformancePreset(PerformanceEnums.UNKNOWN);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
Layout.topMargin: 10
|
||||
Layout.preferredWidth: parent.width
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 35
|
||||
|
||||
HifiStylesUit.RalewayRegular {
|
||||
id: worldDetailHeader
|
||||
text: "World Detail"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 130
|
||||
height: parent.height
|
||||
size: 16
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: worldDetailModel
|
||||
|
||||
ListElement {
|
||||
text: "Low World Detail"
|
||||
worldDetailQualityValue: 0.25
|
||||
}
|
||||
ListElement {
|
||||
text: "Medium World Detail"
|
||||
worldDetailQualityValue: 0.5
|
||||
}
|
||||
ListElement {
|
||||
text: "Full World Detail"
|
||||
worldDetailQualityValue: 0.75
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.ComboBox {
|
||||
id: worldDetailDropdown
|
||||
enabled: performanceCustom.checked
|
||||
anchors.left: worldDetailHeader.right
|
||||
anchors.leftMargin: 20
|
||||
anchors.top: parent.top
|
||||
width: 280
|
||||
height: parent.height
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
model: worldDetailModel
|
||||
currentIndex: -1
|
||||
|
||||
function refreshWorldDetailDropdown() {
|
||||
var currentWorldDetailQuality = LODManager.worldDetailQuality;
|
||||
if (currentWorldDetailQuality <= 0.25) {
|
||||
worldDetailDropdown.currentIndex = 0;
|
||||
} else if (currentWorldDetailQuality <= 0.5) {
|
||||
worldDetailDropdown.currentIndex = 1;
|
||||
} else {
|
||||
worldDetailDropdown.currentIndex = 2;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
worldDetailDropdown.refreshWorldDetailDropdown();
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
LODManager.worldDetailQuality = model.get(currentIndex).worldDetailQualityValue;
|
||||
worldDetailDropdown.displayText = model.get(currentIndex).text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 35
|
||||
Layout.topMargin: 20
|
||||
|
||||
HifiStylesUit.RalewayRegular {
|
||||
id: renderingEffectsHeader
|
||||
text: "Rendering Effects"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 130
|
||||
height: parent.height
|
||||
size: 16
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: renderingEffectsModel
|
||||
|
||||
ListElement {
|
||||
text: "No Rendering Effects"
|
||||
preferredRenderMethod: 1 // "FORWARD"
|
||||
shadowsEnabled: false
|
||||
}
|
||||
ListElement {
|
||||
text: "Local Lights, Fog, Bloom"
|
||||
preferredRenderMethod: 0 // "DEFERRED"
|
||||
shadowsEnabled: false
|
||||
}
|
||||
ListElement {
|
||||
text: "Local Lights, Fog, Bloom, Shadows"
|
||||
preferredRenderMethod: 0 // "DEFERRED"
|
||||
shadowsEnabled: true
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.ComboBox {
|
||||
id: renderingEffectsDropdown
|
||||
enabled: performanceCustom.checked
|
||||
anchors.left: renderingEffectsHeader.right
|
||||
anchors.leftMargin: 20
|
||||
anchors.top: parent.top
|
||||
width: 280
|
||||
height: parent.height
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
model: renderingEffectsModel
|
||||
currentIndex: -1
|
||||
|
||||
function refreshRenderingEffectsDropdownDisplay() {
|
||||
if (Render.shadowsEnabled) {
|
||||
renderingEffectsDropdown.currentIndex = 2;
|
||||
} else if (Render.renderMethod === 0) {
|
||||
renderingEffectsDropdown.currentIndex = 1;
|
||||
} else {
|
||||
renderingEffectsDropdown.currentIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
renderingEffectsDropdown.refreshRenderingEffectsDropdownDisplay();
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
var renderMethodToSet = 1;
|
||||
if (model.get(currentIndex).preferredRenderMethod === 0 &&
|
||||
PlatformInfo.isRenderMethodDeferredCapable()) {
|
||||
renderMethodToSet = 0;
|
||||
}
|
||||
Render.renderMethod = renderMethodToSet;
|
||||
Render.shadowsEnabled = model.get(currentIndex).shadowsEnabled;
|
||||
renderingEffectsDropdown.displayText = model.get(currentIndex).text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 35
|
||||
Layout.topMargin: 20
|
||||
|
||||
HifiStylesUit.RalewayRegular {
|
||||
id: refreshRateHeader
|
||||
text: "Refresh Rate"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 130
|
||||
height: parent.height
|
||||
size: 16
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
ListModel {
|
||||
id: refreshRateModel
|
||||
|
||||
ListElement {
|
||||
text: "Economical"
|
||||
refreshRatePreset: 0 // RefreshRateProfile::ECO
|
||||
}
|
||||
ListElement {
|
||||
text: "Interactive"
|
||||
refreshRatePreset: 1 // RefreshRateProfile::INTERACTIVE
|
||||
}
|
||||
ListElement {
|
||||
text: "Real-Time"
|
||||
refreshRatePreset: 2 // RefreshRateProfile::REALTIME
|
||||
}
|
||||
}
|
||||
|
||||
HifiControlsUit.ComboBox {
|
||||
id: refreshRateDropdown
|
||||
enabled: performanceCustom.checked
|
||||
anchors.left: refreshRateHeader.right
|
||||
anchors.leftMargin: 20
|
||||
anchors.top: parent.top
|
||||
width: 280
|
||||
height: parent.height
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
model: refreshRateModel
|
||||
currentIndex: -1
|
||||
|
||||
function refreshRefreshRateDropdownDisplay() {
|
||||
if (Performance.getRefreshRateProfile() === 0) {
|
||||
refreshRateDropdown.currentIndex = 0;
|
||||
} else if (Performance.getRefreshRateProfile() === 1) {
|
||||
refreshRateDropdown.currentIndex = 1;
|
||||
} else {
|
||||
refreshRateDropdown.currentIndex = 2;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
refreshRateDropdown.refreshRefreshRateDropdownDisplay();
|
||||
}
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
Performance.setRefreshRateProfile(model.get(currentIndex).refreshRatePreset);
|
||||
refreshRateDropdown.displayText = model.get(currentIndex).text;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 35
|
||||
Layout.topMargin: 16
|
||||
|
||||
HifiStylesUit.RalewayRegular {
|
||||
id: resolutionHeader
|
||||
text: "Resolution Scale (" + Number.parseFloat(Render.viewportResolutionScale).toPrecision(3) + ")"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 130
|
||||
height: parent.height
|
||||
size: 16
|
||||
color: "#FFFFFF"
|
||||
}
|
||||
|
||||
HifiControlsUit.Slider {
|
||||
id: resolutionScaleSlider
|
||||
enabled: performanceCustom.checked
|
||||
anchors.left: resolutionHeader.right
|
||||
anchors.leftMargin: 57
|
||||
anchors.top: parent.top
|
||||
width: 150
|
||||
height: parent.height
|
||||
colorScheme: hifi.colorSchemes.dark
|
||||
minimumValue: 0.25
|
||||
maximumValue: 1.0
|
||||
stepSize: 0.02
|
||||
value: Render.viewportResolutionScale
|
||||
live: true
|
||||
|
||||
function updateResolutionScale(sliderValue) {
|
||||
if (Render.viewportResolutionScale !== sliderValue) {
|
||||
Render.viewportResolutionScale = sliderValue;
|
||||
}
|
||||
}
|
||||
|
||||
onValueChanged: {
|
||||
updateResolutionScale(value);
|
||||
}
|
||||
onPressedChanged: {
|
||||
if (!pressed) {
|
||||
updateResolutionScale(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function refreshAllDropdowns() {
|
||||
worldDetailDropdown.refreshWorldDetailDropdown();
|
||||
renderingEffectsDropdown.refreshRenderingEffectsDropdownDisplay();
|
||||
refreshRateDropdown.refreshRefreshRateDropdownDisplay();
|
||||
}
|
||||
}
|
|
@ -91,7 +91,7 @@ Item {
|
|||
|
||||
SimplifiedControls.TextField {
|
||||
id: myDisplayNameText
|
||||
text: MyAvatar.sessionDisplayName === "" ? MyAvatar.displayName : MyAvatar.sessionDisplayName
|
||||
text: MyAvatar.displayName
|
||||
maximumLength: 256
|
||||
clip: true
|
||||
selectByMouse: true
|
||||
|
|
180
interface/resources/qml/hifi/simplifiedUI/helpApp/HelpApp.qml
Normal file
180
interface/resources/qml/hifi/simplifiedUI/helpApp/HelpApp.qml
Normal file
|
@ -0,0 +1,180 @@
|
|||
//
|
||||
// HelpApp.qml
|
||||
//
|
||||
// Created by Zach Fox on 2019-08-07
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import "../simplifiedConstants" as SimplifiedConstants
|
||||
import "../simplifiedControls" as SimplifiedControls
|
||||
import stylesUit 1.0 as HifiStylesUit
|
||||
import "./controls" as HelpControls
|
||||
import "./faq" as HelpFAQ
|
||||
import "./about" as HelpAbout
|
||||
|
||||
Rectangle {
|
||||
property string activeTabView: "controlsTabView"
|
||||
id: root
|
||||
color: simplifiedUI.colors.darkBackground
|
||||
anchors.fill: parent
|
||||
|
||||
SimplifiedConstants.SimplifiedConstants {
|
||||
id: simplifiedUI
|
||||
}
|
||||
|
||||
focus: true
|
||||
|
||||
Component.onCompleted: {
|
||||
root.forceActiveFocus();
|
||||
}
|
||||
|
||||
|
||||
onActiveTabViewChanged: {
|
||||
for (var i = 0; i < tabListModel.count; i++) {
|
||||
if (tabListModel.get(i).tabViewName === activeTabView) {
|
||||
tabListView.currentIndex = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Rectangle {
|
||||
id: tabContainer
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
height: 64
|
||||
color: simplifiedUI.colors.highlightOnDark
|
||||
|
||||
ListModel {
|
||||
id: tabListModel
|
||||
|
||||
ListElement {
|
||||
tabTitle: "Controls"
|
||||
tabViewName: "controlsTabView"
|
||||
}
|
||||
ListElement {
|
||||
tabTitle: "FAQ"
|
||||
tabViewName: "faqTabView"
|
||||
}
|
||||
ListElement {
|
||||
tabTitle: "About"
|
||||
tabViewName: "aboutTabView"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Component {
|
||||
id: highlightBar
|
||||
Rectangle {
|
||||
width: tabListView.currentItem.width
|
||||
height: tabListView.currentItem.height
|
||||
color: simplifiedUI.colors.darkBackground
|
||||
x: tabListView.currentItem.x
|
||||
Behavior on x {
|
||||
SmoothedAnimation {
|
||||
duration: 250
|
||||
}
|
||||
}
|
||||
Behavior on width {
|
||||
SmoothedAnimation {
|
||||
duration: 250
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ListView {
|
||||
id: tabListView
|
||||
anchors.fill: parent
|
||||
contentHeight: parent.height
|
||||
contentWidth: childrenRect.width
|
||||
orientation: ListView.Horizontal
|
||||
model: tabListModel
|
||||
highlight: highlightBar
|
||||
highlightFollowsCurrentItem: false
|
||||
interactive: contentItem.width > width
|
||||
delegate: Item {
|
||||
width: tabTitleText.paintedWidth + 32
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
id: tabTitleText
|
||||
color: simplifiedUI.colors.text.white
|
||||
anchors.fill: parent
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
text: model.tabTitle
|
||||
size: 24
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
root.activeTabView = model.tabViewName;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
id: tabViewContainers
|
||||
anchors.top: tabContainer.bottom
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.bottom: parent.bottom
|
||||
|
||||
|
||||
HelpControls.HelpControls {
|
||||
id: controlsTabViewContainer
|
||||
visible: activeTabView === "controlsTabView"
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
HelpFAQ.HelpFAQ {
|
||||
id: faqTabViewContainer
|
||||
visible: activeTabView === "faqTabView"
|
||||
anchors.fill: parent
|
||||
|
||||
onSendToScript: {
|
||||
root.sendToScript(message);
|
||||
}
|
||||
}
|
||||
|
||||
HelpAbout.HelpAbout {
|
||||
id: aboutTabViewContainer
|
||||
visible: activeTabView === "aboutTabView"
|
||||
anchors.fill: parent
|
||||
}
|
||||
|
||||
SimplifiedControls.VerticalScrollBar {
|
||||
parent: {
|
||||
if (activeTabView === "controlsTabView") {
|
||||
controlsTabViewContainer
|
||||
} else if (activeTabView === "faqTabView") {
|
||||
faqTabViewContainer
|
||||
} else if (activeTabView === "aboutTabView") {
|
||||
aboutTabViewContainer
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function fromScript(message) {
|
||||
switch (message.method) {
|
||||
default:
|
||||
console.log('HelpApp.qml: Unrecognized message from JS');
|
||||
break;
|
||||
}
|
||||
}
|
||||
signal sendToScript(var message);
|
||||
}
|
|
@ -0,0 +1,355 @@
|
|||
//
|
||||
// HelpAbout.qml
|
||||
//
|
||||
// Created by Zach Fox on 2019-08-07
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import "../../simplifiedConstants" as SimplifiedConstants
|
||||
import "../../simplifiedControls" as SimplifiedControls
|
||||
import stylesUit 1.0 as HifiStylesUit
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
Flickable {
|
||||
id: root
|
||||
contentWidth: parent.width
|
||||
contentHeight: aboutColumnLayout.height
|
||||
clip: true
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
root.contentX = 0;
|
||||
root.contentY = 0;
|
||||
|
||||
// When the user clicks the About tab, refresh the audio I/O model so that
|
||||
// the delegate Component.onCompleted handlers fire, which will update
|
||||
// the text that appears in the About screen.
|
||||
audioOutputDevices.model = undefined;
|
||||
audioOutputDevices.model = AudioScriptingInterface.devices.output;
|
||||
audioInputDevices.model = undefined;
|
||||
audioInputDevices.model = AudioScriptingInterface.devices.input;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
SimplifiedConstants.SimplifiedConstants {
|
||||
id: simplifiedUI
|
||||
}
|
||||
|
||||
|
||||
Image {
|
||||
id: accent
|
||||
source: "../images/accent3.svg"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 83
|
||||
height: 156
|
||||
transform: Scale {
|
||||
xScale: -1
|
||||
origin.x: accent.width / 2
|
||||
origin.y: accent.height / 2
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
id: aboutColumnLayout
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 26
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 26
|
||||
anchors.top: parent.top
|
||||
spacing: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: platformInfoContainer
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.bottomMargin: 24
|
||||
spacing: 0
|
||||
|
||||
HifiStylesUit.GraphikSemiBold {
|
||||
text: "About Your Configuration"
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.topMargin: 16
|
||||
Layout.bottomMargin: 8
|
||||
height: paintedHeight
|
||||
size: 22
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Use the button below to get a copy to share with us."
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.bottomMargin: 8
|
||||
height: paintedHeight
|
||||
size: 18
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Version " + Window.checkVersion()
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikSemiBold {
|
||||
text: "Platform Info"
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
height: paintedHeight
|
||||
size: 22
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>Computer Vendor/Model:</b>"
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
Component.onCompleted: {
|
||||
var computer = JSON.parse(PlatformInfo.getComputer());
|
||||
var computerVendor = computer.vendor;
|
||||
if (computerVendor.length === 0) {
|
||||
computerVendor = "Unknown";
|
||||
}
|
||||
var computerModel = computer.model;
|
||||
if (computerModel.length === 0) {
|
||||
computerModel = "Unknown";
|
||||
}
|
||||
|
||||
text = "<b>Computer Vendor/Model:</b> " + computerVendor + "/" + computerModel;
|
||||
}
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>Profiled Platform Tier:</b> " + PlatformInfo.getTierProfiled()
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>OS Type:</b> " + PlatformInfo.getOperatingSystemType()
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>CPU:</b>"
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
Component.onCompleted: {
|
||||
var cpu = JSON.parse(PlatformInfo.getCPU(0));
|
||||
var cpuModel = cpu.model;
|
||||
if (cpuModel.length === 0) {
|
||||
cpuModel = "Unknown";
|
||||
}
|
||||
|
||||
text = "<b>CPU:</b> " + cpuModel;
|
||||
}
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b># CPUs:</b> " + PlatformInfo.getNumCPUs()
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b># CPU Cores:</b> " + PlatformInfo.getNumLogicalCores()
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>RAM:</b> " + PlatformInfo.getTotalSystemMemoryMB() + " MB"
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>GPU:</b> "
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
Component.onCompleted: {
|
||||
var gpu = JSON.parse(PlatformInfo.getGPU(PlatformInfo.getMasterGPU()));
|
||||
var gpuModel = gpu.model;
|
||||
if (gpuModel.length === 0) {
|
||||
gpuModel = "Unknown";
|
||||
}
|
||||
|
||||
text = "<b>GPU:</b> " + gpuModel;
|
||||
}
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>VR Hand Controllers:</b> " + (PlatformInfo.hasRiftControllers() ? "Rift" : (PlatformInfo.hasViveControllers() ? "Vive" : "None"))
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
// This is a bit of a hack to get the name of the currently-selected audio input device
|
||||
// in the current mode (Desktop or VR). The reason this is necessary is because it seems to me like
|
||||
// the only way one can get a human-readable list of the audio I/O devices is by using a ListView
|
||||
// and grabbing the names from the AudioScriptingInterface; you can't do it using a ListModel.
|
||||
// See `AudioDevices.h`, specifically the comment above the declaration of `QVariant data()`.
|
||||
ListView {
|
||||
id: audioInputDevices
|
||||
visible: false
|
||||
property string selectedInputDeviceName
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: contentItem.height
|
||||
interactive: false
|
||||
delegate: Item {
|
||||
Component.onCompleted: {
|
||||
if ((HMD.active && selectedHMD) || (!HMD.active && selectedDesktop)) {
|
||||
audioInputDevices.selectedInputDeviceName = model.devicename
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>Audio Input:</b> " + audioInputDevices.selectedInputDeviceName
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
|
||||
// This is a bit of a hack to get the name of the currently-selected audio output device
|
||||
// in the current mode (Desktop or VR). The reason this is necessary is because it seems to me like
|
||||
// the only way one can get a human-readable list of the audio I/O devices is by using a ListView
|
||||
// and grabbing the names from the AudioScriptingInterface; you can't do it using a ListModel.
|
||||
// See `AudioDevices.h`, specifically the comment above the declaration of `QVariant data()`.
|
||||
ListView {
|
||||
id: audioOutputDevices
|
||||
visible: false
|
||||
property string selectedOutputDeviceName
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: contentItem.height
|
||||
interactive: false
|
||||
delegate: Item {
|
||||
Component.onCompleted: {
|
||||
if ((HMD.active && selectedHMD) || (!HMD.active && selectedDesktop)) {
|
||||
audioOutputDevices.selectedOutputDeviceName = model.devicename
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>Audio Output:</b> " + audioOutputDevices.selectedOutputDeviceName
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
SimplifiedControls.Button {
|
||||
Layout.topMargin: 8
|
||||
width: 200
|
||||
height: 32
|
||||
text: "Copy to Clipboard"
|
||||
temporaryText: "Copied!"
|
||||
|
||||
onClicked: {
|
||||
Window.copyToClipboard(root.buildPlatformInfoTextToCopy());
|
||||
showTemporaryText();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function buildPlatformInfoTextToCopy() {
|
||||
var textToCopy = "**About Interface**\n";
|
||||
textToCopy += "Interface Version: " + Window.checkVersion() + "\n";
|
||||
textToCopy += "\n**Platform Info**\n";
|
||||
|
||||
var computer = JSON.parse(PlatformInfo.getComputer());
|
||||
var computerVendor = computer.vendor;
|
||||
if (computerVendor.length === 0) {
|
||||
computerVendor = "Unknown";
|
||||
}
|
||||
var computerModel = computer.model;
|
||||
if (computerModel.length === 0) {
|
||||
computerModel = "Unknown";
|
||||
}
|
||||
|
||||
textToCopy += "Computer Vendor/Model: " + computerVendor + "/" + computerModel + "\n";
|
||||
textToCopy += "Profiled Platform Tier: " + PlatformInfo.getTierProfiled() + "\n";
|
||||
textToCopy += "OS Type: " + PlatformInfo.getOperatingSystemType() + "\n";
|
||||
|
||||
var cpu = JSON.parse(PlatformInfo.getCPU(0));
|
||||
var cpuModel = cpu.model;
|
||||
if (cpuModel.length === 0) {
|
||||
cpuModel = "Unknown";
|
||||
}
|
||||
|
||||
textToCopy += "CPU: " + cpuModel + "\n";
|
||||
textToCopy += "# CPUs: " + PlatformInfo.getNumCPUs() + "\n";
|
||||
textToCopy += "# CPU Cores: " + PlatformInfo.getNumLogicalCores() + "\n";
|
||||
textToCopy += "RAM: " + PlatformInfo.getTotalSystemMemoryMB() + " MB\n";
|
||||
|
||||
var gpu = JSON.parse(PlatformInfo.getGPU(PlatformInfo.getMasterGPU()));
|
||||
var gpuModel = gpu.model;
|
||||
if (gpuModel.length === 0) {
|
||||
gpuModel = "Unknown";
|
||||
}
|
||||
|
||||
textToCopy += "GPU: " + gpuModel + "\n";
|
||||
textToCopy += "VR Hand Controllers: " + (PlatformInfo.hasRiftControllers() ? "Rift" : (PlatformInfo.hasViveControllers() ? "Vive" : "None")) + "\n";
|
||||
textToCopy += "Audio Input: " + audioInputDevices.selectedInputDeviceName + "\n";
|
||||
textToCopy += "Audio Output: " + audioOutputDevices.selectedOutputDeviceName + "\n";
|
||||
|
||||
textToCopy += "\n**All Platform Info**\n";
|
||||
textToCopy += JSON.stringify(JSON.parse(PlatformInfo.getPlatform()), null, 4);
|
||||
|
||||
return textToCopy;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,779 @@
|
|||
//
|
||||
// ControlsTable.qml
|
||||
//
|
||||
// Created by Zach Fox on 2019-08-16
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Layouts 1.3
|
||||
import stylesUit 1.0 as HifiStylesUit
|
||||
|
||||
Item {
|
||||
id: controlsTableRoot
|
||||
property int column1Width: 80
|
||||
property int column2Width: width - column1Width
|
||||
property int rowHeight: 31
|
||||
property int rowPadding: 8
|
||||
property int mainTextSize: 18
|
||||
property int subTextSize: 14
|
||||
property color separatorColor: "#CCCCCC"
|
||||
Layout.preferredHeight: controlsTableColumnLayout.height
|
||||
|
||||
// Top separator
|
||||
Rectangle {
|
||||
anchors.top: controlsTableColumnLayout.top
|
||||
anchors.left: controlsTableColumnLayout.left
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
// Right separator
|
||||
Rectangle {
|
||||
anchors.top: controlsTableColumnLayout.top
|
||||
anchors.right: controlsTableColumnLayout.right
|
||||
width: 1
|
||||
height: controlsTableColumnLayout.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
anchors.top: controlsTableColumnLayout.top
|
||||
anchors.topMargin: controlsTableRoot.height
|
||||
anchors.left: controlsTableColumnLayout.left
|
||||
width: parent.width
|
||||
height: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
// Left separator
|
||||
Rectangle {
|
||||
anchors.top: controlsTableColumnLayout.top
|
||||
anchors.left: controlsTableColumnLayout.left
|
||||
width: 1
|
||||
height: controlsTableColumnLayout.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
id: controlsTableColumnLayout
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
spacing: 0
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
Image {
|
||||
source: "images/rightClick.svg"
|
||||
anchors.centerIn: parent
|
||||
width: 24
|
||||
height: 24
|
||||
mipmap: true
|
||||
fillMode: Image.PreserveAspectFit
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
Row {
|
||||
width: controlsTableRoot.column2Width
|
||||
height: parent.height
|
||||
spacing: controlsTableRoot.rowPadding
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
id: cameraText
|
||||
text: "Camera"
|
||||
width: paintedWidth
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Right-click and drag to look around"
|
||||
width: parent.width - cameraText.width - parent.spacing - controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
size: controlsTableRoot.subTextSize
|
||||
color: Qt.rgba(255, 255, 255, 0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "W / ↑"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Walk Forward"
|
||||
width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "S / ↓"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Walk Backward"
|
||||
width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "A / ←"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Turn Left"
|
||||
width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "D / →"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Turn Right"
|
||||
width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Q"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Sidestep Left"
|
||||
width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "E"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Sidestep Right"
|
||||
width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Shift"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
Row {
|
||||
width: controlsTableRoot.column2Width
|
||||
height: parent.height
|
||||
spacing: controlsTableRoot.rowPadding
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
id: runText
|
||||
text: "Hold + Direction to Run"
|
||||
width: paintedWidth
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Example: Shift + W"
|
||||
width: parent.width - runText.width - parent.spacing - controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
size: controlsTableRoot.subTextSize
|
||||
color: Qt.rgba(255, 255, 255, 0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Space"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
Row {
|
||||
width: controlsTableRoot.column2Width
|
||||
height: parent.height
|
||||
spacing: controlsTableRoot.rowPadding
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
id: jumpText
|
||||
text: "Jump / Stand Up"
|
||||
width: paintedWidth
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Stand Up only while seated"
|
||||
width: parent.width - jumpText.width - parent.spacing - controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
size: controlsTableRoot.subTextSize
|
||||
color: Qt.rgba(255, 255, 255, 0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "1"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "1st Person View"
|
||||
width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "2"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
Row {
|
||||
width: controlsTableRoot.column2Width
|
||||
height: parent.height
|
||||
spacing: controlsTableRoot.rowPadding
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
id: mirrorText
|
||||
text: "Mirror Mode"
|
||||
width: paintedWidth
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "See your own avatar"
|
||||
width: parent.width - mirrorText.width - parent.spacing - controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
size: controlsTableRoot.subTextSize
|
||||
color: Qt.rgba(255, 255, 255, 0.5)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom separator
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 1
|
||||
color: controlsTableRoot.separatorColor
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: controlsTableRoot.rowHeight
|
||||
|
||||
Item {
|
||||
width: controlsTableRoot.column1Width
|
||||
height: parent.height
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "3"
|
||||
anchors.fill: parent
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: 1
|
||||
height: parent.height
|
||||
color: controlsTableRoot.separatorColor
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
}
|
||||
}
|
||||
|
||||
// Spacer
|
||||
Item {
|
||||
width: controlsTableRoot.rowPadding
|
||||
height: parent.height
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "3rd Person View"
|
||||
width: controlsTableRoot.column2Width - controlsTableRoot.rowPadding * 2
|
||||
height: parent.height
|
||||
horizontalAlignment: Text.AlignLeft
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
size: controlsTableRoot.mainTextSize
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,95 @@
|
|||
//
|
||||
// HelpControls.qml
|
||||
//
|
||||
// Created by Zach Fox on 2019-08-07
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import "../../simplifiedConstants" as SimplifiedConstants
|
||||
import "../../simplifiedControls" as SimplifiedControls
|
||||
import stylesUit 1.0 as HifiStylesUit
|
||||
import QtQuick.Layouts 1.3
|
||||
|
||||
Flickable {
|
||||
id: root
|
||||
contentWidth: parent.width
|
||||
contentHeight: controlsColumnLayout.height
|
||||
clip: true
|
||||
|
||||
onVisibleChanged: {
|
||||
if (visible) {
|
||||
root.contentX = 0;
|
||||
root.contentY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
SimplifiedConstants.SimplifiedConstants {
|
||||
id: simplifiedUI
|
||||
}
|
||||
|
||||
|
||||
Image {
|
||||
id: accent
|
||||
source: "../images/accent1.svg"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 83
|
||||
height: 156
|
||||
transform: Scale {
|
||||
xScale: -1
|
||||
origin.x: accent.width / 2
|
||||
origin.y: accent.height / 2
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
id: controlsColumnLayout
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 26
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 26
|
||||
anchors.top: parent.top
|
||||
spacing: 0
|
||||
|
||||
HifiStylesUit.GraphikSemiBold {
|
||||
text: "HQ Controls"
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.topMargin: 16
|
||||
height: paintedHeight
|
||||
size: 22
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "You can use the following controls to move your avatar around your HQ:"
|
||||
Layout.preferredWidth: parent.width
|
||||
wrapMode: Text.Wrap
|
||||
height: paintedHeight
|
||||
size: 18
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
ControlsTable {
|
||||
Layout.topMargin: 8
|
||||
Layout.preferredWidth: parent.width
|
||||
}
|
||||
|
||||
SimplifiedControls.Button {
|
||||
Layout.topMargin: 14
|
||||
Layout.preferredWidth: 200
|
||||
height: 32
|
||||
text: "VIEW ALL CONTROLS"
|
||||
temporaryText: "Viewing!"
|
||||
|
||||
onClicked: {
|
||||
Qt.openUrlExternally("http://docs.highfidelity.com/explore/get-started/desktop.html");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 23.0.3, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 16 24" style="enable-background:new 0 0 16 24;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{opacity:0.2;enable-background:new ;}
|
||||
.st2{fill:#009EE0;}
|
||||
</style>
|
||||
<path class="st0" d="M16,1.7v20.6c0,0.9-0.8,1.7-1.8,1.7H1.8c-1,0-1.8-0.8-1.8-1.7V1.7C0,0.8,0.8,0,1.8,0h12.4C15.2,0,16,0.8,16,1.7
|
||||
z"/>
|
||||
<path d="M14.2,1.2H9v8.6h5.7V1.7C14.7,1.4,14.5,1.2,14.2,1.2z"/>
|
||||
<path class="st1" d="M7,1.2H1.8c-0.3,0-0.5,0.2-0.5,0.5v8.1H7V1.2z"/>
|
||||
<circle class="st2" cx="11.9" cy="5.3" r="2.5"/>
|
||||
</svg>
|
After Width: | Height: | Size: 805 B |
|
@ -0,0 +1,120 @@
|
|||
//
|
||||
// HelpFAQ.qml
|
||||
//
|
||||
// Created by Zach Fox on 2019-08-08
|
||||
// Copyright 2019 High Fidelity, Inc.
|
||||
//
|
||||
// Distributed under the Apache License, Version 2.0.
|
||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
//
|
||||
|
||||
import QtQuick 2.10
|
||||
import QtQuick.Controls 2.3
|
||||
import "../../simplifiedConstants" as SimplifiedConstants
|
||||
import "../../simplifiedControls" as SimplifiedControls
|
||||
import stylesUit 1.0 as HifiStylesUit
|
||||
import QtQuick.Layouts 1.3
|
||||
import PerformanceEnums 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
|
||||
|
||||
SimplifiedConstants.SimplifiedConstants {
|
||||
id: simplifiedUI
|
||||
}
|
||||
|
||||
|
||||
Image {
|
||||
id: accent
|
||||
source: "../images/accent3.svg"
|
||||
anchors.left: parent.left
|
||||
anchors.top: parent.top
|
||||
width: 83
|
||||
height: 156
|
||||
transform: Scale {
|
||||
xScale: -1
|
||||
origin.x: accent.width / 2
|
||||
origin.y: accent.height / 2
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ColumnLayout {
|
||||
id: faqColumnLayout
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 26
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 26
|
||||
anchors.top: parent.top
|
||||
spacing: 0
|
||||
|
||||
HifiStylesUit.GraphikSemiBold {
|
||||
text: "FAQ"
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: paintedHeight
|
||||
Layout.topMargin: 16
|
||||
size: 22
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "You can find answers to all of our frequently asked questions by clicking the button below."
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: paintedHeight
|
||||
Layout.topMargin: 8
|
||||
size: 18
|
||||
wrapMode: Text.Wrap
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
SimplifiedControls.Button {
|
||||
Layout.topMargin: 8
|
||||
width: 200
|
||||
height: 32
|
||||
text: "VIEW FAQ"
|
||||
temporaryText: "Viewing!"
|
||||
|
||||
onClicked: {
|
||||
Qt.openUrlExternally("https://www.highfidelity.com/knowledge");
|
||||
}
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikSemiBold {
|
||||
text: "Having problems with your audio?"
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: paintedHeight
|
||||
Layout.topMargin: 32
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "Quickly check your audio configuration and make changes to resolve any audio input/output issues."
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: paintedHeight
|
||||
Layout.topMargin: 4
|
||||
size: 18
|
||||
wrapMode: Text.Wrap
|
||||
color: simplifiedUI.colors.text.white
|
||||
}
|
||||
|
||||
SimplifiedControls.Button {
|
||||
Layout.topMargin: 8
|
||||
width: 200
|
||||
height: 32
|
||||
text: "TEST YOUR AUDIO"
|
||||
|
||||
onClicked: {
|
||||
root.sendToScript({
|
||||
"source": "HelpApp.qml",
|
||||
"method": "goToAudioSettings"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
signal sendToScript(var message);
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
<svg width="106" height="200" viewBox="0 0 106 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0L106 -9.26681e-06L83.1739 10.0481L0 0Z" fill="#FFED00"/>
|
||||
<path d="M83.1738 10.0481L106 -1.99552e-06L106 200L83.1738 10.0481Z" fill="#FF42A7"/>
|
||||
</svg>
|
After Width: | Height: | Size: 263 B |
|
@ -0,0 +1,4 @@
|
|||
<svg width="106" height="200" viewBox="0 0 106 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0L106 -9.26681e-06L83.1739 10.0481L0 0Z" fill="#FF42A7"/>
|
||||
<path d="M83.1738 10.0481L106 -1.99552e-06L106 200L83.1738 10.0481Z" fill="#009EE0"/>
|
||||
</svg>
|
After Width: | Height: | Size: 263 B |
|
@ -0,0 +1,4 @@
|
|||
<svg width="106" height="200" viewBox="0 0 106 200" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M0 0L106 -9.26681e-06L83.1739 10.0481L0 0Z" fill="#009EE0"/>
|
||||
<path d="M83.1738 10.0481L106 -1.99552e-06L106 200L83.1738 10.0481Z" fill="#FFED00"/>
|
||||
</svg>
|
After Width: | Height: | Size: 263 B |
|
@ -176,6 +176,7 @@ Rectangle {
|
|||
anchors.left: parent.left
|
||||
width: parent.width
|
||||
height: parent.parent.height
|
||||
mipmap: true
|
||||
}
|
||||
|
||||
ColorOverlay {
|
||||
|
|
|
@ -49,6 +49,15 @@ Rectangle {
|
|||
}
|
||||
}
|
||||
|
||||
onActiveTabViewChanged: {
|
||||
for (var i = 0; i < tabListModel.count; i++) {
|
||||
if (tabListModel.get(i).tabViewName === activeTabView) {
|
||||
tabListView.currentIndex = i;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.forceActiveFocus();
|
||||
}
|
||||
|
@ -138,7 +147,6 @@ Rectangle {
|
|||
MouseArea {
|
||||
anchors.fill: parent
|
||||
onClicked: {
|
||||
tabListView.currentIndex = index;
|
||||
root.activeTabView = model.tabViewName;
|
||||
}
|
||||
}
|
||||
|
@ -207,7 +215,24 @@ Rectangle {
|
|||
|
||||
|
||||
function fromScript(message) {
|
||||
if (message.source !== "simplifiedUI.js") {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (message.method) {
|
||||
case "goToSettingsTab":
|
||||
var tabToGoTo = message.data.settingsTab;
|
||||
switch (tabToGoTo) {
|
||||
case "audio":
|
||||
activeTabView = "audioTabView";
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log("A message was sent to the Settings window to change tabs, but an invalid tab was specified.");
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
console.log('SettingsApp.qml: Unrecognized message from JS');
|
||||
break;
|
||||
|
|
|
@ -70,7 +70,7 @@ Flickable {
|
|||
HifiStylesUit.GraphikSemiBold {
|
||||
text: "Version " + Window.checkVersion()
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -79,7 +79,7 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikSemiBold {
|
||||
text: "Platform Info"
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.topMargin: 8
|
||||
Layout.bottomMargin: 8
|
||||
height: paintedHeight
|
||||
|
@ -90,7 +90,7 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>Computer Vendor/Model:</b>"
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -113,7 +113,7 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>Profiled Platform Tier:</b> " + PlatformInfo.getTierProfiled()
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -122,7 +122,7 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>OS Type:</b> " + PlatformInfo.getOperatingSystemType()
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -131,7 +131,7 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>CPU:</b>"
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -150,7 +150,7 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b># CPUs:</b> " + PlatformInfo.getNumCPUs()
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -159,7 +159,7 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b># CPU Cores:</b> " + PlatformInfo.getNumLogicalCores()
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -168,7 +168,7 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>RAM:</b> " + PlatformInfo.getTotalSystemMemoryMB() + " MB"
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -177,14 +177,14 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>GPU:</b> "
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
Component.onCompleted: {
|
||||
var gpu = JSON.parse(PlatformInfo.getGPU(0));
|
||||
var gpu = JSON.parse(PlatformInfo.getGPU(PlatformInfo.getMasterGPU()));
|
||||
var gpuModel = gpu.model;
|
||||
if (gpuModel.length === 0) {
|
||||
gpuModel = "Unknown";
|
||||
|
@ -196,7 +196,7 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>VR Hand Controllers:</b> " + (PlatformInfo.hasRiftControllers() ? "Rift" : (PlatformInfo.hasViveControllers() ? "Vive" : "None"))
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -228,7 +228,7 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>Audio Input:</b> " + audioInputDevices.selectedInputDeviceName
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -261,7 +261,7 @@ Flickable {
|
|||
|
||||
HifiStylesUit.GraphikRegular {
|
||||
text: "<b>Audio Output:</b> " + audioOutputDevices.selectedOutputDeviceName
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 16
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -313,7 +313,7 @@ Flickable {
|
|||
textToCopy += "# CPU Cores: " + PlatformInfo.getNumLogicalCores() + "\n";
|
||||
textToCopy += "RAM: " + PlatformInfo.getTotalSystemMemoryMB() + " MB\n";
|
||||
|
||||
var gpu = JSON.parse(PlatformInfo.getGPU(0));
|
||||
var gpu = JSON.parse(PlatformInfo.getGPU(PlatformInfo.getMasterGPU()));
|
||||
var gpuModel = gpu.model;
|
||||
if (gpuModel.length === 0) {
|
||||
gpuModel = "Unknown";
|
||||
|
|
|
@ -174,7 +174,7 @@ Flickable {
|
|||
HifiStylesUit.GraphikSemiBold {
|
||||
id: micControlsTitle
|
||||
text: "Default Mute Controls"
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 22
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -222,6 +222,17 @@ Flickable {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
SimplifiedControls.Switch {
|
||||
id: acousticEchoCancellationSwitch
|
||||
Layout.preferredHeight: 18
|
||||
Layout.preferredWidth: parent.width
|
||||
labelTextOn: "Acoustic Echo Cancellation"
|
||||
checked: AudioScriptingInterface.acousticEchoCancellation
|
||||
onClicked: {
|
||||
AudioScriptingInterface.acousticEchoCancellation = !AudioScriptingInterface.acousticEchoCancellation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,7 +244,7 @@ Flickable {
|
|||
HifiStylesUit.GraphikSemiBold {
|
||||
id: inputDeviceTitle
|
||||
text: "Which input device?"
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 22
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
@ -329,7 +340,7 @@ Flickable {
|
|||
HifiStylesUit.GraphikSemiBold {
|
||||
id: outputDeviceTitle
|
||||
text: "Which output device?"
|
||||
Layout.maximumWidth: parent.width
|
||||
Layout.preferredWidth: parent.width
|
||||
height: paintedHeight
|
||||
size: 22
|
||||
color: simplifiedUI.colors.text.white
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue