mirror of
https://github.com/AleziaKurdis/overte.git
synced 2025-08-18 01:33:38 +02:00
Merge branch 'master' into DOC-124
# Conflicts: # interface/src/scripting/Audio.h
This commit is contained in:
commit
789f9c7d67
144 changed files with 6506 additions and 1494 deletions
|
@ -66,12 +66,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)
|
||||
=========
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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)
|
||||
|
|
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'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -142,22 +142,6 @@ endif()
|
|||
if (APPLE)
|
||||
add_executable(${TARGET_NAME} MACOSX_BUNDLE ${INTERFACE_SRCS} ${QM})
|
||||
|
||||
set_from_env(XCODE_DEVELOPMENT_TEAM XCODE_DEVELOPMENT_TEAM "")
|
||||
if ("${XCODE_DEVELOPMENT_TEAM}" STREQUAL "")
|
||||
message(WARNING "XCODE_DEVELOPMENT_TEAM environment variable is not set. Not signing build.")
|
||||
else()
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/interface.entitlements"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Developer ID Application"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS NO
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE "Manual"
|
||||
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "${XCODE_DEVELOPMENT_TEAM}"
|
||||
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES
|
||||
XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--timestamp --deep"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER ""
|
||||
)
|
||||
endif()
|
||||
|
||||
# make sure the output name for the .app bundle is correct
|
||||
# Fix up the rpath so macdeployqt works
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES INSTALL_RPATH "@executable_path/../Frameworks")
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>high-fidelity.hifi</string>
|
||||
</array>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.server</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
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.
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/sitting_idle.fbx
Normal file
BIN
interface/resources/avatar/animations/sitting_idle.fbx
Normal file
Binary file not shown.
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -362,8 +362,8 @@ Rectangle {
|
|||
id: displayModeImage
|
||||
source: HMD.active ? "./images/desktopMode.svg" : "./images/vrMode.svg"
|
||||
anchors.centerIn: parent
|
||||
width: HMD.active ? 25 : 43
|
||||
height: 22
|
||||
width: HMD.active ? 25 : 26
|
||||
height: HMD.active ? 22 : 14
|
||||
visible: false
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1225,8 +1225,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
#endif
|
||||
|
||||
bool isStore = property(hifi::properties::OCULUS_STORE).toBool();
|
||||
|
||||
DependencyManager::get<WalletScriptingInterface>()->setLimitedCommerce(isStore); // Or we could make it a separate arg, or if either arg is set, etc. And should this instead by a hifi::properties?
|
||||
// Or we could make it a separate arg, or if either arg is set, etc. And should this instead by a hifi::properties?
|
||||
DependencyManager::get<WalletScriptingInterface>()->setLimitedCommerce(isStore || property(hifi::properties::STEAM).toBool());
|
||||
|
||||
updateHeartbeat();
|
||||
|
||||
|
@ -2824,6 +2824,7 @@ void Application::cleanupBeforeQuit() {
|
|||
|
||||
// destroy Audio so it and its threads have a chance to go down safely
|
||||
// this must happen after QML, as there are unexplained audio crashes originating in qtwebengine
|
||||
AudioInjector::setLocalAudioInterface(nullptr);
|
||||
DependencyManager::destroy<AudioClient>();
|
||||
DependencyManager::destroy<AudioScriptingInterface>();
|
||||
|
||||
|
@ -7788,9 +7789,15 @@ bool Application::askToWearAvatarAttachmentUrl(const QString& url) {
|
|||
return true;
|
||||
}
|
||||
|
||||
void Application::replaceDomainContent(const QString& url) {
|
||||
static const QString CONTENT_SET_NAME_QUERY_PARAM = "name";
|
||||
|
||||
void Application::replaceDomainContent(const QString& url, const QString& itemName) {
|
||||
qCDebug(interfaceapp) << "Attempting to replace domain content";
|
||||
QByteArray urlData(url.toUtf8());
|
||||
QUrl msgUrl(url);
|
||||
QUrlQuery urlQuery(msgUrl.query());
|
||||
urlQuery.addQueryItem(CONTENT_SET_NAME_QUERY_PARAM, itemName);
|
||||
msgUrl.setQuery(urlQuery.query(QUrl::QUrl::FullyEncoded));
|
||||
QByteArray urlData(msgUrl.toString(QUrl::QUrl::FullyEncoded).toUtf8());
|
||||
auto limitedNodeList = DependencyManager::get<NodeList>();
|
||||
const auto& domainHandler = limitedNodeList->getDomainHandler();
|
||||
|
||||
|
@ -7824,7 +7831,7 @@ bool Application::askToReplaceDomainContent(const QString& url) {
|
|||
QString details;
|
||||
if (static_cast<QMessageBox::StandardButton>(answer.toInt()) == QMessageBox::Yes) {
|
||||
// Given confirmation, send request to domain server to replace content
|
||||
replaceDomainContent(url);
|
||||
replaceDomainContent(url, QString());
|
||||
details = "SuccessfulRequestToReplaceContent";
|
||||
} else {
|
||||
details = "UserDeclinedToReplaceContent";
|
||||
|
|
|
@ -326,7 +326,7 @@ public:
|
|||
bool isInterstitialMode() const { return _interstitialMode; }
|
||||
bool failedToConnectToEntityServer() const { return _failedToConnectToEntityServer; }
|
||||
|
||||
void replaceDomainContent(const QString& url);
|
||||
void replaceDomainContent(const QString& url, const QString& itemName);
|
||||
|
||||
void loadAvatarScripts(const QVector<QString>& urls);
|
||||
void unloadAvatarScripts();
|
||||
|
|
|
@ -52,6 +52,7 @@ protected:
|
|||
|
||||
protected slots:
|
||||
/**jsdoc
|
||||
* Prompts the user to delete a bookmark. The user can select the bookmark to delete in the dialog that is opened.
|
||||
* @function LocationBookmarks.deleteBookmark
|
||||
*/
|
||||
virtual void deleteBookmark();
|
||||
|
|
|
@ -54,6 +54,7 @@ void LODManager::setRenderTimes(float presentTime, float engineRunTime, float ba
|
|||
}
|
||||
|
||||
void LODManager::autoAdjustLOD(float realTimeDelta) {
|
||||
std::lock_guard<std::mutex> { _automaticLODLock };
|
||||
|
||||
// The "render time" is the worse of:
|
||||
// - engineRunTime: Time spent in the render thread in the engine producing the gpu::Frame N
|
||||
|
@ -235,6 +236,7 @@ void LODManager::resetLODAdjust() {
|
|||
}
|
||||
|
||||
void LODManager::setAutomaticLODAdjust(bool value) {
|
||||
std::lock_guard<std::mutex> { _automaticLODLock };
|
||||
_automaticLODAdjust = value;
|
||||
emit autoLODChanged();
|
||||
}
|
||||
|
@ -426,7 +428,6 @@ float LODManager::getWorldDetailQuality() const {
|
|||
return HIGH;
|
||||
}
|
||||
|
||||
|
||||
void LODManager::setLODQualityLevel(float quality) {
|
||||
_lodQualityLevel = quality;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,8 @@
|
|||
#ifndef hifi_LODManager_h
|
||||
#define hifi_LODManager_h
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <NumericalConstants.h>
|
||||
#include <OctreeConstants.h>
|
||||
|
@ -47,11 +49,6 @@ class AABox;
|
|||
* @property {number} presentTime <em>Read-only.</em>
|
||||
* @property {number} engineRunTime <em>Read-only.</em>
|
||||
* @property {number} gpuTime <em>Read-only.</em>
|
||||
* @property {number} avgRenderTime <em>Read-only.</em>
|
||||
* @property {number} fps <em>Read-only.</em>
|
||||
* @property {number} lodLevel <em>Read-only.</em>
|
||||
* @property {number} lodDecreaseFPS <em>Read-only.</em>
|
||||
* @property {number} lodIncreaseFPS <em>Read-only.</em>
|
||||
*/
|
||||
|
||||
class LODManager : public QObject, public Dependency {
|
||||
|
@ -240,6 +237,7 @@ signals:
|
|||
private:
|
||||
LODManager();
|
||||
|
||||
std::mutex _automaticLODLock;
|
||||
bool _automaticLODAdjust = true;
|
||||
|
||||
float _presentTime{ 0.0f }; // msec
|
||||
|
|
|
@ -17,6 +17,9 @@
|
|||
#include "Bookmarks.h"
|
||||
|
||||
/**jsdoc
|
||||
* The <code>LocationBookmarks</code> API provides facilities for working with location bookmarks. A location bookmark
|
||||
* associates a name with a metaverse address.
|
||||
*
|
||||
* @namespace LocationBookmarks
|
||||
*
|
||||
* @hifi-client-entity
|
||||
|
@ -35,28 +38,35 @@ public:
|
|||
static const QString HOME_BOOKMARK;
|
||||
|
||||
/**jsdoc
|
||||
* Gets the metaverse address associated with a bookmark.
|
||||
* @function LocationBookmarks.getAddress
|
||||
* @param {string} bookmarkName Name of the bookmark to get the address for.
|
||||
* @returns {string} The url for the specified bookmark. If the bookmark does not exist, the empty string will be returned.
|
||||
* @param {string} bookmarkName - Name of the bookmark to get the metaverse address for (case sensitive).
|
||||
* @returns {string} The metaverse address for the bookmark. If the bookmark does not exist, <code>""</code> is returned.
|
||||
* @example <caption>Report the "Home" bookmark's metaverse address.</caption>
|
||||
* print("Home bookmark's address: " + LocationBookmarks.getAddress("Home"));
|
||||
*/
|
||||
Q_INVOKABLE QString getAddress(const QString& bookmarkName);
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Prompts the user to bookmark their current location. The user can specify the name of the bookmark in the dialog that is
|
||||
* opened.
|
||||
* @function LocationBookmarks.addBookmark
|
||||
*/
|
||||
void addBookmark();
|
||||
|
||||
/**jsdoc
|
||||
* Sets the metaverse address associated with the "Home" bookmark.
|
||||
* @function LocationBookmarks.setHomeLocationToAddress
|
||||
* @param {string} address
|
||||
* @param {string} address - The metaverse address to set the "Home" bookmark to.
|
||||
*/
|
||||
void setHomeLocationToAddress(const QVariant& address);
|
||||
|
||||
/**jsdoc
|
||||
* Gets the metaverse address associated with the "Home" bookmark.
|
||||
* @function LocationBookmarks.getHomeLocationAddress
|
||||
* @returns {string} The url for the home location bookmark
|
||||
* @returns {string} The metaverse address for the "Home" bookmark.
|
||||
*/
|
||||
QString getHomeLocationAddress();
|
||||
|
||||
|
|
|
@ -125,6 +125,18 @@ QString userRecenterModelToString(MyAvatar::SitStandModelType model) {
|
|||
}
|
||||
}
|
||||
|
||||
static const QStringList REACTION_NAMES = {
|
||||
QString("positive"),
|
||||
QString("negative"),
|
||||
QString("raiseHand"),
|
||||
QString("applaud"),
|
||||
QString("point")
|
||||
};
|
||||
|
||||
static int reactionNameToIndex(const QString& reactionName) {
|
||||
return REACTION_NAMES.indexOf(reactionName);
|
||||
}
|
||||
|
||||
MyAvatar::MyAvatar(QThread* thread) :
|
||||
Avatar(thread),
|
||||
_yawSpeed(YAW_SPEED_DEFAULT),
|
||||
|
@ -5812,6 +5824,53 @@ void MyAvatar::setModelScale(float scale) {
|
|||
}
|
||||
}
|
||||
|
||||
QStringList MyAvatar::getReactions() const {
|
||||
return REACTION_NAMES;
|
||||
}
|
||||
|
||||
bool MyAvatar::triggerReaction(QString reactionName) {
|
||||
int reactionIndex = reactionNameToIndex(reactionName);
|
||||
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_REACTIONS) {
|
||||
std::lock_guard<std::mutex> guard(_reactionLock);
|
||||
_reactionTriggers[reactionIndex] = true;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MyAvatar::beginReaction(QString reactionName) {
|
||||
int reactionIndex = reactionNameToIndex(reactionName);
|
||||
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_REACTIONS) {
|
||||
std::lock_guard<std::mutex> guard(_reactionLock);
|
||||
_reactionEnabledRefCounts[reactionIndex]++;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool MyAvatar::endReaction(QString reactionName) {
|
||||
int reactionIndex = reactionNameToIndex(reactionName);
|
||||
if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_REACTIONS) {
|
||||
std::lock_guard<std::mutex> guard(_reactionLock);
|
||||
_reactionEnabledRefCounts[reactionIndex]--;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void MyAvatar::updateRigControllerParameters(Rig::ControllerParameters& params) {
|
||||
std::lock_guard<std::mutex> guard(_reactionLock);
|
||||
for (int i = 0; i < NUM_AVATAR_REACTIONS; i++) {
|
||||
|
||||
// copy current state into params.
|
||||
params.reactionEnabledFlags[i] = _reactionEnabledRefCounts[i] > 0;
|
||||
params.reactionTriggers[i] = _reactionTriggers[i];
|
||||
|
||||
// clear reaction triggers here as well
|
||||
_reactionTriggers[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
SpatialParentTree* MyAvatar::getParentTree() const {
|
||||
auto entityTreeRenderer = qApp->getEntities();
|
||||
EntityTreePointer entityTree = entityTreeRenderer ? entityTreeRenderer->getTree() : nullptr;
|
||||
|
@ -6148,3 +6207,51 @@ void MyAvatar::sendPacket(const QUuid& entityID) const {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::setSitDriveKeysStatus(bool enabled) {
|
||||
const std::vector<DriveKeys> DISABLED_DRIVE_KEYS_DURING_SIT = {
|
||||
DriveKeys::TRANSLATE_X,
|
||||
DriveKeys::TRANSLATE_Y,
|
||||
DriveKeys::TRANSLATE_Z,
|
||||
DriveKeys::STEP_TRANSLATE_X,
|
||||
DriveKeys::STEP_TRANSLATE_Y,
|
||||
DriveKeys::STEP_TRANSLATE_Z
|
||||
};
|
||||
for (auto key : DISABLED_DRIVE_KEYS_DURING_SIT) {
|
||||
if (enabled) {
|
||||
enableDriveKey(key);
|
||||
} else {
|
||||
disableDriveKey(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MyAvatar::beginSit(const glm::vec3& position, const glm::quat& rotation) {
|
||||
_characterController.setSeated(true);
|
||||
setCollisionsEnabled(false);
|
||||
setHMDLeanRecenterEnabled(false);
|
||||
// Disable movement
|
||||
setSitDriveKeysStatus(false);
|
||||
centerBody();
|
||||
int hipIndex = getJointIndex("Hips");
|
||||
clearPinOnJoint(hipIndex);
|
||||
goToLocation(position, true, rotation, false, false);
|
||||
pinJoint(hipIndex, position, rotation);
|
||||
}
|
||||
|
||||
void MyAvatar::endSit(const glm::vec3& position, const glm::quat& rotation) {
|
||||
if (_characterController.getSeated()) {
|
||||
clearPinOnJoint(getJointIndex("Hips"));
|
||||
_characterController.setSeated(false);
|
||||
setCollisionsEnabled(true);
|
||||
setHMDLeanRecenterEnabled(true);
|
||||
centerBody();
|
||||
goToLocation(position, true, rotation, false, false);
|
||||
float TIME_BEFORE_DRIVE_ENABLED_MS = 150.0f;
|
||||
QTimer::singleShot(TIME_BEFORE_DRIVE_ENABLED_MS, [this]() {
|
||||
// Enable movement again
|
||||
setSitDriveKeysStatus(true);
|
||||
});
|
||||
}
|
||||
|
||||
}
|
|
@ -1835,10 +1835,30 @@ public:
|
|||
*/
|
||||
Q_INVOKABLE QVariantList getCollidingFlowJoints();
|
||||
|
||||
/**jsdoc
|
||||
* Starts a sitting action for the avatar
|
||||
* @function MyAvatar.beginSit
|
||||
* @param {Vec3} position - The point in space where the avatar will sit.
|
||||
* @param {Quat} rotation - Initial absolute orientation of the avatar once is seated.
|
||||
*/
|
||||
Q_INVOKABLE void beginSit(const glm::vec3& position, const glm::quat& rotation);
|
||||
|
||||
/**jsdoc
|
||||
* Ends a sitting action for the avatar
|
||||
* @function MyAvatar.endSit
|
||||
* @param {Vec3} position - The position of the avatar when standing up.
|
||||
* @param {Quat} rotation - The absolute rotation of the avatar once the sitting action ends.
|
||||
*/
|
||||
Q_INVOKABLE void endSit(const glm::vec3& position, const glm::quat& rotation);
|
||||
|
||||
int getOverrideJointCount() const;
|
||||
bool getFlowActive() const;
|
||||
bool getNetworkGraphActive() const;
|
||||
|
||||
// sets the reaction enabled and triggered parameters of the passed in params
|
||||
// also clears internal reaction triggers
|
||||
void updateRigControllerParameters(Rig::ControllerParameters& params);
|
||||
|
||||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
|
@ -2194,6 +2214,33 @@ public slots:
|
|||
*/
|
||||
virtual void setModelScale(float scale) override;
|
||||
|
||||
/**jsdoc
|
||||
* MyAvatar.getReactions
|
||||
* @returns {string[]} Array of reaction names.
|
||||
*/
|
||||
QStringList getReactions() const;
|
||||
|
||||
/**jsdoc
|
||||
* MyAvatar.triggerReaction
|
||||
* @param {string} reactionName - reaction name
|
||||
* @returns {bool} false if the given reaction is not supported.
|
||||
*/
|
||||
bool triggerReaction(QString reactionName);
|
||||
|
||||
/**jsdoc
|
||||
* MyAvatar.beginReaction
|
||||
* @param {string} reactionName - reaction name
|
||||
* @returns {bool} false if the given reaction is not supported.
|
||||
*/
|
||||
bool beginReaction(QString reactionName);
|
||||
|
||||
/**jsdoc
|
||||
* MyAvatar.endReaction
|
||||
* @param {string} reactionName - reaction name
|
||||
* @returns {bool} false if the given reaction is not supported.
|
||||
*/
|
||||
bool endReaction(QString reactionName);
|
||||
|
||||
signals:
|
||||
|
||||
/**jsdoc
|
||||
|
@ -2492,6 +2539,7 @@ private:
|
|||
|
||||
virtual void updatePalms() override {}
|
||||
void lateUpdatePalms();
|
||||
void setSitDriveKeysStatus(bool enabled);
|
||||
|
||||
void clampTargetScaleToDomainLimits();
|
||||
void clampScaleChangeToDomainLimits(float desiredScale);
|
||||
|
@ -2825,6 +2873,10 @@ private:
|
|||
mutable std::mutex _scriptEngineLock;
|
||||
QScriptEngine* _scriptEngine { nullptr };
|
||||
bool _needToSaveAvatarEntitySettings { false };
|
||||
|
||||
int _reactionEnabledRefCounts[NUM_AVATAR_REACTIONS] { 0, 0, 0, 0, 0 };
|
||||
bool _reactionTriggers[NUM_AVATAR_REACTIONS] { false, false, false, false, false };
|
||||
mutable std::mutex _reactionLock;
|
||||
};
|
||||
|
||||
QScriptValue audioListenModeToScriptValue(QScriptEngine* engine, const AudioListenerMode& audioListenerMode);
|
||||
|
|
|
@ -32,6 +32,8 @@ Rig::CharacterControllerState convertCharacterControllerState(CharacterControlle
|
|||
return Rig::CharacterControllerState::InAir;
|
||||
case CharacterController::State::Hover:
|
||||
return Rig::CharacterControllerState::Hover;
|
||||
case CharacterController::State::Seated:
|
||||
return Rig::CharacterControllerState::Seated;
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -294,8 +296,6 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
_prevIsEstimatingHips = false;
|
||||
}
|
||||
|
||||
params.isTalking = head->getTimeWithoutTalking() <= 1.5f;
|
||||
|
||||
// pass detailed torso k-dops to rig.
|
||||
int hipsJoint = _rig.indexOfJoint("Hips");
|
||||
if (hipsJoint >= 0) {
|
||||
|
@ -314,6 +314,10 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
|||
params.spine2ShapeInfo = hfmModel.joints[spine2Joint].shapeInfo;
|
||||
}
|
||||
|
||||
params.isTalking = head->getTimeWithoutTalking() <= 1.5f;
|
||||
|
||||
myAvatar->updateRigControllerParameters(params);
|
||||
|
||||
_rig.updateFromControllerParameters(params, deltaTime);
|
||||
|
||||
Rig::CharacterControllerState ccState = convertCharacterControllerState(myAvatar->getCharacterController()->getState());
|
||||
|
|
|
@ -259,7 +259,7 @@ void QmlCommerce::authorizeAssetTransfer(const QString& couponID,
|
|||
ledger->authorizeAssetTransfer(key, couponID, certificateID, amount, optionalMessage);
|
||||
}
|
||||
|
||||
void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID) {
|
||||
void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& certificateID, const QString& itemName) {
|
||||
if (!certificateID.isEmpty()) {
|
||||
auto ledger = DependencyManager::get<Ledger>();
|
||||
ledger->updateLocation(
|
||||
|
@ -267,7 +267,7 @@ void QmlCommerce::replaceContentSet(const QString& itemHref, const QString& cert
|
|||
DependencyManager::get<AddressManager>()->getPlaceName(),
|
||||
true);
|
||||
}
|
||||
qApp->replaceDomainContent(itemHref);
|
||||
qApp->replaceDomainContent(itemHref, itemName);
|
||||
QJsonObject messageProperties = {
|
||||
{ "status", "SuccessfulRequestToReplaceContent" },
|
||||
{ "content_set_url", itemHref } };
|
||||
|
|
|
@ -90,7 +90,7 @@ protected:
|
|||
Q_INVOKABLE void transferAssetToUsername(const QString& username, const QString& certificateID, const int& amount, const QString& optionalMessage);
|
||||
Q_INVOKABLE void authorizeAssetTransfer(const QString& couponID, const QString& certificateID, const int& amount, const QString& optionalMessage);
|
||||
|
||||
Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID);
|
||||
Q_INVOKABLE void replaceContentSet(const QString& itemHref, const QString& certificateID, const QString& itemName);
|
||||
|
||||
Q_INVOKABLE QString getInstalledApps(const QString& justInstalledAppID = "");
|
||||
Q_INVOKABLE bool installApp(const QString& appHref, const bool& alsoOpenImmediately = false);
|
||||
|
|
|
@ -26,6 +26,7 @@ QString Audio::HMD { "VR" };
|
|||
|
||||
Setting::Handle<bool> enableNoiseReductionSetting { QStringList { Audio::AUDIO, "NoiseReduction" }, true };
|
||||
Setting::Handle<bool> enableWarnWhenMutedSetting { QStringList { Audio::AUDIO, "WarnWhenMuted" }, true };
|
||||
Setting::Handle<bool> enableAcousticEchoCancellationSetting { QStringList { Audio::AUDIO, "AcousticEchoCancellation" }, true };
|
||||
|
||||
|
||||
float Audio::loudnessToLevel(float loudness) {
|
||||
|
@ -40,12 +41,14 @@ Audio::Audio() : _devices(_contextIsHMD) {
|
|||
connect(client, &AudioClient::muteToggled, this, &Audio::setMuted);
|
||||
connect(client, &AudioClient::noiseReductionChanged, this, &Audio::enableNoiseReduction);
|
||||
connect(client, &AudioClient::warnWhenMutedChanged, this, &Audio::enableWarnWhenMuted);
|
||||
connect(client, &AudioClient::acousticEchoCancellationChanged, this, &Audio::enableAcousticEchoCancellation);
|
||||
connect(client, &AudioClient::inputLoudnessChanged, this, &Audio::onInputLoudnessChanged);
|
||||
connect(client, &AudioClient::inputVolumeChanged, this, &Audio::setInputVolume);
|
||||
connect(this, &Audio::contextChanged, &_devices, &AudioDevices::onContextChanged);
|
||||
connect(this, &Audio::pushingToTalkChanged, this, &Audio::handlePushedToTalk);
|
||||
enableNoiseReduction(enableNoiseReductionSetting.get());
|
||||
enableWarnWhenMuted(enableWarnWhenMutedSetting.get());
|
||||
enableAcousticEchoCancellation(enableAcousticEchoCancellationSetting.get());
|
||||
onContextChanged();
|
||||
}
|
||||
|
||||
|
@ -277,6 +280,28 @@ void Audio::enableWarnWhenMuted(bool enable) {
|
|||
}
|
||||
}
|
||||
|
||||
bool Audio::acousticEchoCancellationEnabled() const {
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return _enableAcousticEchoCancellation;
|
||||
});
|
||||
}
|
||||
|
||||
void Audio::enableAcousticEchoCancellation(bool enable) {
|
||||
bool changed = false;
|
||||
withWriteLock([&] {
|
||||
if (_enableAcousticEchoCancellation != enable) {
|
||||
_enableAcousticEchoCancellation = enable;
|
||||
auto client = DependencyManager::get<AudioClient>().data();
|
||||
QMetaObject::invokeMethod(client, "setAcousticEchoCancellation", Q_ARG(bool, enable), Q_ARG(bool, false));
|
||||
enableAcousticEchoCancellationSetting.set(enable);
|
||||
changed = true;
|
||||
}
|
||||
});
|
||||
if (changed) {
|
||||
emit acousticEchoCancellationChanged(enable);
|
||||
}
|
||||
}
|
||||
|
||||
float Audio::getInputVolume() const {
|
||||
return resultWithReadLock<bool>([&] {
|
||||
return _inputVolume;
|
||||
|
|
|
@ -76,6 +76,9 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
|
|||
* @property {number} systemInjectorGain - The gain (relative volume in dB) that system sounds are played at.
|
||||
* @property {number} pushingToTalkOutputGainDesktop - The gain (relative volume in dB) that all sounds are played at when
|
||||
* the user is holding the push-to-talk key in desktop mode.
|
||||
* @property {boolean} acousticEchoCancellation - <code>true</code> if acoustic echo cancellation is enabled, otherwise
|
||||
* <code>false</code>. When enabled, sound from the audio output is suppressed when it echos back to the input audio
|
||||
* signal.
|
||||
*
|
||||
* @comment The following properties are from AudioScriptingInterface.h.
|
||||
* @property {boolean} isStereoInput - <code>true</code> if the input audio is being used in stereo, otherwise
|
||||
|
@ -89,6 +92,8 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable {
|
|||
Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged)
|
||||
Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged)
|
||||
Q_PROPERTY(bool warnWhenMuted READ warnWhenMutedEnabled WRITE enableWarnWhenMuted NOTIFY warnWhenMutedChanged)
|
||||
Q_PROPERTY(bool acousticEchoCancellation
|
||||
READ acousticEchoCancellationEnabled WRITE enableAcousticEchoCancellation NOTIFY acousticEchoCancellationChanged)
|
||||
Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged)
|
||||
Q_PROPERTY(float inputLevel READ getInputLevel NOTIFY inputLevelChanged)
|
||||
Q_PROPERTY(bool clipping READ isClipping NOTIFY clippingChanged)
|
||||
|
@ -119,6 +124,7 @@ public:
|
|||
bool isMuted() const;
|
||||
bool noiseReductionEnabled() const;
|
||||
bool warnWhenMutedEnabled() const;
|
||||
bool acousticEchoCancellationEnabled() const;
|
||||
float getInputVolume() const;
|
||||
float getInputLevel() const;
|
||||
bool isClipping() const;
|
||||
|
@ -400,6 +406,14 @@ signals:
|
|||
*/
|
||||
void warnWhenMutedChanged(bool isEnabled);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when acoustic echo cancellation is enabled or disabled.
|
||||
* @function Audio.acousticEchoCancellationChanged
|
||||
* @param {boolean} isEnabled - <code>true</code> if acoustic echo cancellation is enabled, otherwise <code>false</code>.
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void acousticEchoCancellationChanged(bool isEnabled);
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the input audio volume changes.
|
||||
* @function Audio.inputVolumeChanged
|
||||
|
@ -498,6 +512,7 @@ private slots:
|
|||
void setMuted(bool muted);
|
||||
void enableNoiseReduction(bool enable);
|
||||
void enableWarnWhenMuted(bool enable);
|
||||
void enableAcousticEchoCancellation(bool enable);
|
||||
void setInputVolume(float volume);
|
||||
void onInputLoudnessChanged(float loudness, bool isClipping);
|
||||
|
||||
|
@ -516,6 +531,7 @@ private:
|
|||
bool _isClipping { false };
|
||||
bool _enableNoiseReduction { true }; // Match default value of AudioClient::_isNoiseGateEnabled.
|
||||
bool _enableWarnWhenMuted { true };
|
||||
bool _enableAcousticEchoCancellation { true }; // AudioClient::_isAECEnabled
|
||||
bool _contextIsHMD { false };
|
||||
AudioDevices* getDevices() { return &_devices; }
|
||||
AudioDevices _devices;
|
||||
|
|
|
@ -69,6 +69,14 @@ void QmlWindowProxy::parentNativeWindowToMainWindow() {
|
|||
#endif
|
||||
}
|
||||
|
||||
void InteractiveWindowProxy::emitScriptEvent(const QVariant& scriptMessage){
|
||||
emit scriptEventReceived(scriptMessage);
|
||||
}
|
||||
|
||||
void InteractiveWindowProxy::emitWebEvent(const QVariant& webMessage) {
|
||||
emit webEventReceived(webMessage);
|
||||
}
|
||||
|
||||
static void qmlWindowProxyDeleter(QmlWindowProxy* qmlWindowProxy) {
|
||||
qmlWindowProxy->deleteLater();
|
||||
}
|
||||
|
@ -128,6 +136,16 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
|
|||
if (properties.contains(PRESENTATION_MODE_PROPERTY)) {
|
||||
presentationMode = (InteractiveWindowPresentationMode) properties[PRESENTATION_MODE_PROPERTY].toInt();
|
||||
}
|
||||
|
||||
_interactiveWindowProxy = std::unique_ptr<InteractiveWindowProxy,
|
||||
std::function<void(InteractiveWindowProxy*)>>(new InteractiveWindowProxy, [](InteractiveWindowProxy *p) {
|
||||
p->deleteLater();
|
||||
});
|
||||
|
||||
connect(_interactiveWindowProxy.get(), &InteractiveWindowProxy::webEventReceived,
|
||||
this, &InteractiveWindow::emitWebEvent, Qt::QueuedConnection);
|
||||
connect(this, &InteractiveWindow::scriptEventReceived, _interactiveWindowProxy.get(),
|
||||
&InteractiveWindowProxy::emitScriptEvent, Qt::QueuedConnection);
|
||||
|
||||
if (properties.contains(DOCKED_PROPERTY) && presentationMode == InteractiveWindowPresentationMode::Native) {
|
||||
QVariantMap nativeWindowInfo = properties[DOCKED_PROPERTY].toMap();
|
||||
|
@ -147,7 +165,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
|
|||
_dockWidget = std::shared_ptr<DockWidget>(new DockWidget(title, mainWindow), dockWidgetDeleter);
|
||||
auto quickView = _dockWidget->getQuickView();
|
||||
|
||||
Application::setupQmlSurface(quickView->rootContext() , true);
|
||||
Application::setupQmlSurface(quickView->rootContext(), true);
|
||||
|
||||
//add any whitelisted callbacks
|
||||
OffscreenUi::applyWhiteList(sourceUrl, quickView->rootContext());
|
||||
|
@ -182,13 +200,17 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
QObject::connect(quickView.get(), &QQuickView::statusChanged, [&, this] (QQuickView::Status status) {
|
||||
if (status == QQuickView::Ready) {
|
||||
QQuickItem* rootItem = _dockWidget->getRootItem();
|
||||
_dockWidget->getQuickView()->rootContext()->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
|
||||
_dockWidget->getQuickView()->rootContext()->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy.get());
|
||||
// The qmlToScript method handles the thread-safety of this call. Because the QVariant argument
|
||||
// passed to the sendToScript signal may wrap an externally managed and thread-unsafe QJSValue,
|
||||
// qmlToScript needs to be called directly, so the QJSValue can be immediately converted to a plain QVariant.
|
||||
QObject::connect(rootItem, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)),
|
||||
Qt::QueuedConnection);
|
||||
Qt::DirectConnection);
|
||||
QObject::connect(rootItem, SIGNAL(keyPressEvent(int, int)), this, SLOT(forwardKeyPressEvent(int, int)),
|
||||
Qt::QueuedConnection);
|
||||
QObject::connect(rootItem, SIGNAL(keyReleaseEvent(int, int)), this, SLOT(forwardKeyReleaseEvent(int, int)),
|
||||
|
@ -204,7 +226,7 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
|
|||
// Build the event bridge and wrapper on the main thread
|
||||
offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) {
|
||||
_qmlWindowProxy = std::shared_ptr<QmlWindowProxy>(new QmlWindowProxy(object, nullptr), qmlWindowProxyDeleter);
|
||||
context->setContextProperty(EVENT_BRIDGE_PROPERTY, this);
|
||||
context->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy.get());
|
||||
if (properties.contains(ADDITIONAL_FLAGS_PROPERTY)) {
|
||||
object->setProperty(ADDITIONAL_FLAGS_PROPERTY, properties[ADDITIONAL_FLAGS_PROPERTY].toUInt());
|
||||
}
|
||||
|
@ -229,7 +251,10 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap
|
|||
object->setProperty(VISIBLE_PROPERTY, properties[INTERACTIVE_WINDOW_VISIBLE_PROPERTY].toBool());
|
||||
}
|
||||
|
||||
connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection);
|
||||
// The qmlToScript method handles the thread-safety of this call. Because the QVariant argument
|
||||
// passed to the sendToScript signal may wrap an externally managed and thread-unsafe QJSValue,
|
||||
// qmlToScript needs to be called directly, so the QJSValue can be immediately converted to a plain QVariant.
|
||||
connect(object, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::DirectConnection);
|
||||
QObject::connect(object, SIGNAL(keyPressEvent(int, int)), this, SLOT(forwardKeyPressEvent(int, int)),
|
||||
Qt::QueuedConnection);
|
||||
QObject::connect(object, SIGNAL(keyReleaseEvent(int, int)), this, SLOT(forwardKeyReleaseEvent(int, int)),
|
||||
|
@ -263,6 +288,7 @@ InteractiveWindow::~InteractiveWindow() {
|
|||
}
|
||||
|
||||
void InteractiveWindow::sendToQml(const QVariant& message) {
|
||||
|
||||
// Forward messages received from the script on to QML
|
||||
if (_dockWidget) {
|
||||
QQuickItem* rootItem = _dockWidget->getRootItem();
|
||||
|
@ -301,6 +327,7 @@ void InteractiveWindow::close() {
|
|||
}
|
||||
_dockWidget = nullptr;
|
||||
_qmlWindowProxy = nullptr;
|
||||
_interactiveWindowProxy = nullptr;
|
||||
}
|
||||
|
||||
void InteractiveWindow::show() {
|
||||
|
@ -315,13 +342,21 @@ void InteractiveWindow::raise() {
|
|||
}
|
||||
}
|
||||
|
||||
void InteractiveWindow::qmlToScript(const QVariant& message) {
|
||||
void InteractiveWindow::qmlToScript(const QVariant& originalMessage) {
|
||||
QVariant message = originalMessage;
|
||||
if (message.canConvert<QJSValue>()) {
|
||||
emit fromQml(qvariant_cast<QJSValue>(message).toVariant());
|
||||
message = qvariant_cast<QJSValue>(message).toVariant();
|
||||
} else if (message.canConvert<QString>()) {
|
||||
emit fromQml(message.toString());
|
||||
message = message.toString();
|
||||
} else {
|
||||
qWarning() << "Unsupported message type " << message;
|
||||
return;
|
||||
}
|
||||
|
||||
if (thread() != QThread::currentThread()) {
|
||||
QMetaObject::invokeMethod(this, "fromQml", Q_ARG(const QVariant&, message));
|
||||
} else {
|
||||
emit fromQml(message);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,10 +34,24 @@ public:
|
|||
QObject* getQmlWindow() const { return _qmlWindow; }
|
||||
private:
|
||||
QObject* _qmlWindow;
|
||||
|
||||
};
|
||||
|
||||
|
||||
class InteractiveWindowProxy : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
InteractiveWindowProxy(){}
|
||||
public slots:
|
||||
|
||||
void emitScriptEvent(const QVariant& scriptMessage);
|
||||
void emitWebEvent(const QVariant& webMessage);
|
||||
|
||||
signals:
|
||||
|
||||
void scriptEventReceived(const QVariant& message);
|
||||
void webEventReceived(const QVariant& message);
|
||||
};
|
||||
|
||||
namespace InteractiveWindowEnums {
|
||||
Q_NAMESPACE
|
||||
|
||||
|
@ -309,6 +323,7 @@ protected slots:
|
|||
private:
|
||||
std::shared_ptr<QmlWindowProxy> _qmlWindowProxy;
|
||||
std::shared_ptr<DockWidget> _dockWidget { nullptr };
|
||||
std::unique_ptr<InteractiveWindowProxy, std::function<void(InteractiveWindowProxy*)>> _interactiveWindowProxy;
|
||||
};
|
||||
|
||||
typedef InteractiveWindow* InteractiveWindowPointer;
|
||||
|
|
|
@ -11,11 +11,52 @@
|
|||
#include "ResourceImageItem.h"
|
||||
|
||||
#include <gl/Config.h>
|
||||
|
||||
#include <gl/GLHelpers.h>
|
||||
#include <QOpenGLFramebufferObjectFormat>
|
||||
#include <QOpenGLShaderProgram>
|
||||
|
||||
#include <plugins/DisplayPlugin.h>
|
||||
|
||||
|
||||
static const char* VERTEX_SHADER = R"SHADER(
|
||||
#version 450 core
|
||||
|
||||
out vec2 vTexCoord;
|
||||
|
||||
void main(void) {
|
||||
const float depth = 0.0;
|
||||
const vec4 UNIT_QUAD[4] = vec4[4](
|
||||
vec4(-1.0, -1.0, depth, 1.0),
|
||||
vec4(1.0, -1.0, depth, 1.0),
|
||||
vec4(-1.0, 1.0, depth, 1.0),
|
||||
vec4(1.0, 1.0, depth, 1.0)
|
||||
);
|
||||
vec4 pos = UNIT_QUAD[gl_VertexID];
|
||||
|
||||
gl_Position = pos;
|
||||
vTexCoord = (pos.xy + 1.0) * 0.5;
|
||||
}
|
||||
)SHADER";
|
||||
|
||||
static const char* FRAGMENT_SHADER = R"SHADER(
|
||||
#version 450 core
|
||||
|
||||
uniform sampler2D sampler;
|
||||
|
||||
in vec2 vTexCoord;
|
||||
|
||||
out vec4 FragColor;
|
||||
|
||||
vec3 color_LinearTosRGB(vec3 lrgb) {
|
||||
return mix(vec3(1.055) * pow(vec3(lrgb), vec3(0.41666)) - vec3(0.055), vec3(lrgb) * vec3(12.92), vec3(lessThan(lrgb, vec3(0.0031308))));
|
||||
}
|
||||
|
||||
void main() {
|
||||
FragColor = vec4(color_LinearTosRGB(texture(sampler, vTexCoord).rgb), 1.0);
|
||||
}
|
||||
)SHADER";
|
||||
|
||||
|
||||
ResourceImageItem::ResourceImageItem() : QQuickFramebufferObject() {
|
||||
auto textureCache = DependencyManager::get<TextureCache>();
|
||||
connect(textureCache.data(), SIGNAL(spectatorCameraFramebufferReset()), this, SLOT(update()));
|
||||
|
@ -95,16 +136,29 @@ void ResourceImageItemRenderer::render() {
|
|||
}
|
||||
if (_ready) {
|
||||
_fboMutex.lock();
|
||||
_copyFbo->bind();
|
||||
QOpenGLFramebufferObject::blitFramebuffer(framebufferObject(), _copyFbo, GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT, GL_NEAREST);
|
||||
|
||||
// this clears the copyFbo texture
|
||||
// so next frame starts fresh - helps
|
||||
// when aspect ratio changes
|
||||
_copyFbo->takeTexture();
|
||||
|
||||
if (!_shader) {
|
||||
_shader = new QOpenGLShaderProgram();
|
||||
_shader->addCacheableShaderFromSourceCode(QOpenGLShader::Vertex, VERTEX_SHADER);
|
||||
_shader->addCacheableShaderFromSourceCode(QOpenGLShader::Fragment, FRAGMENT_SHADER);
|
||||
_shader->link();
|
||||
glGenVertexArrays(1, &_vao);
|
||||
}
|
||||
framebufferObject()->bind();
|
||||
_shader->bind();
|
||||
|
||||
auto sourceTextureId = _copyFbo->takeTexture();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
glBindTexture(GL_TEXTURE_2D, sourceTextureId);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
|
||||
glBindVertexArray(_vao);
|
||||
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
|
||||
glDeleteTextures(1, &sourceTextureId);
|
||||
|
||||
_copyFbo->bind();
|
||||
_copyFbo->release();
|
||||
|
||||
_fboMutex.unlock();
|
||||
}
|
||||
glFlush();
|
||||
|
|
|
@ -22,6 +22,9 @@
|
|||
|
||||
#include <TextureCache.h>
|
||||
|
||||
class QOpenGLFramebufferObject;
|
||||
class QOpenGLShaderProgram;
|
||||
|
||||
class ResourceImageItemRenderer : public QObject, public QQuickFramebufferObject::Renderer {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
@ -30,14 +33,16 @@ public:
|
|||
void synchronize(QQuickFramebufferObject* item) override;
|
||||
void render() override;
|
||||
private:
|
||||
bool _ready;
|
||||
bool _ready{ false };
|
||||
QString _url;
|
||||
bool _visible;
|
||||
bool _visible{ false };
|
||||
|
||||
NetworkTexturePointer _networkTexture;
|
||||
QQuickWindow* _window;
|
||||
QQuickWindow* _window{ nullptr };
|
||||
QMutex _fboMutex;
|
||||
uint32_t _vao{ 0 };
|
||||
QOpenGLFramebufferObject* _copyFbo { nullptr };
|
||||
QOpenGLShaderProgram* _shader{ nullptr };
|
||||
GLsync _fenceSync { 0 };
|
||||
QTimer _updateTimer;
|
||||
public slots:
|
||||
|
|
|
@ -41,7 +41,6 @@
|
|||
#include "MainWindow.h"
|
||||
#include "Snapshot.h"
|
||||
#include "SnapshotUploader.h"
|
||||
#include "ToneMappingEffect.h"
|
||||
|
||||
// filename format: hifi-snap-by-%username%-on-%date%_%time%_@-%location%.jpg
|
||||
// %1 <= username, %2 <= date and time, %3 <= current location
|
||||
|
|
|
@ -2,6 +2,7 @@ cmake_minimum_required(VERSION 3.0)
|
|||
set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.9)
|
||||
project(HQLauncher)
|
||||
set (CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/")
|
||||
include("cmake/macros/SetPackagingParameters.cmake")
|
||||
set(src_files
|
||||
src/Launcher.h
|
||||
src/Launcher.m
|
||||
|
@ -59,43 +60,27 @@ set_target_properties(${this_target} PROPERTIES
|
|||
set(MACOSX_BUNDLE_ICON_FILE "interface.icns")
|
||||
|
||||
function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE)
|
||||
if (NOT DEFINED ${_RESULT_NAME})
|
||||
if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "")
|
||||
set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE)
|
||||
else()
|
||||
set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE)
|
||||
endif()
|
||||
if (NOT DEFINED ${_RESULT_NAME})
|
||||
if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "")
|
||||
set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE)
|
||||
else()
|
||||
set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
set_packaging_parameters()
|
||||
|
||||
add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files})
|
||||
|
||||
set_from_env(XCODE_DEVELOPMENT_TEAM XCODE_DEVELOPMENT_TEAM "")
|
||||
if ("${XCODE_DEVELOPMENT_TEAM}" STREQUAL "")
|
||||
message(WARNING "XCODE_DEVELOPMENT_TEAM environmental variable is not set. Not signing build.")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME}
|
||||
MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME}
|
||||
)
|
||||
else()
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME}
|
||||
MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME}
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "HQ Launcher.entitlements"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "Developer ID Application"
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_INJECT_BASE_ENTITLEMENTS NO
|
||||
XCODE_ATTRIBUTE_CODE_SIGN_STYLE "Manual"
|
||||
XCODE_ATTRIBUTE_DEVELOPMENT_TEAM "${XCODE_DEVELOPMENT_TEAM}"
|
||||
XCODE_ATTRIBUTE_ENABLE_HARDENED_RUNTIME YES
|
||||
XCODE_ATTRIBUTE_OTHER_CODE_SIGN_FLAGS "--timestamp"
|
||||
XCODE_ATTRIBUTE_PROVISIONING_PROFILE_SPECIFIER ""
|
||||
)
|
||||
endif()
|
||||
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME}
|
||||
MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME})
|
||||
set_from_env(LAUNCHER_HMAC_SECRET LAUNCHER_HMAC_SECRET "")
|
||||
if ("${LAUNCHER_HMAC_SECRET}" STREQUAL "")
|
||||
message(FATAL_ERROR "LAUNCHER_HMAC_SECRET is not set")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_HMAC_SECRET="${LAUNCHER_HMAC_SECRET}")
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}")
|
||||
|
||||
file(GLOB NIB_FILES "nib/*.xib")
|
||||
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>high-fidelity.hifi</string>
|
||||
</array>
|
||||
<key>com.apple.security.network.client</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
45
launchers/darwin/cmake/macros/SetPackagingParameters.cmake
Normal file
45
launchers/darwin/cmake/macros/SetPackagingParameters.cmake
Normal file
|
@ -0,0 +1,45 @@
|
|||
#
|
||||
# SetPackagingParameters.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Leonardo Murillo on 07/14/2015.
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
# This macro checks some Jenkins defined environment variables to determine the origin of this build
|
||||
# and decides how targets should be packaged.
|
||||
|
||||
macro(SET_PACKAGING_PARAMETERS)
|
||||
set(PR_BUILD 0)
|
||||
set(PRODUCTION_BUILD 0)
|
||||
set(DEV_BUILD 0)
|
||||
set(BUILD_NUMBER 0)
|
||||
|
||||
set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV")
|
||||
set_from_env(RELEASE_NUMBER RELEASE_NUMBER "")
|
||||
set_from_env(STABLE_BUILD STABLE_BUILD 0)
|
||||
|
||||
message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}")
|
||||
set(BUILD_NUMBER ${RELEASE_NUMBER})
|
||||
|
||||
if (RELEASE_TYPE STREQUAL "PRODUCTION")
|
||||
set(PRODUCTION_BUILD 1)
|
||||
set(BUILD_VERSION ${RELEASE_NUMBER})
|
||||
|
||||
# add definition for this release type
|
||||
add_definitions(-DPRODUCTION_BUILD)
|
||||
|
||||
elseif (RELEASE_TYPE STREQUAL "PR")
|
||||
set(PR_BUILD 1)
|
||||
set(BUILD_VERSION "PR${RELEASE_NUMBER}")
|
||||
|
||||
# add definition for this release type
|
||||
add_definitions(-DPR_BUILD)
|
||||
else ()
|
||||
set(DEV_BUILD 1)
|
||||
set(BUILD_VERSION "dev")
|
||||
endif ()
|
||||
|
||||
endmacro(SET_PACKAGING_PARAMETERS)
|
|
@ -102,12 +102,22 @@
|
|||
<action selector="termsOfService:" target="YVh-OH-vU8" id="bgc-08-8Lj"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="oJs-u5-OOJ">
|
||||
<rect key="frame" x="380" y="0.0" width="130" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Label" id="H6o-Xs-wK1">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="138.5" y="154"/>
|
||||
</customView>
|
||||
<customObject id="YVh-OH-vU8" customClass="DisplayNameScreen">
|
||||
<connections>
|
||||
<outlet property="backgroundImage" destination="aus-lo-eVi" id="SRc-pV-lXG"/>
|
||||
<outlet property="buildVersion" destination="oJs-u5-OOJ" id="avj-j2-5P6"/>
|
||||
<outlet property="displayName" destination="Vhg-rq-xUH" id="Fb5-im-2hx"/>
|
||||
<outlet property="smallLogo" destination="j8K-TD-h7e" id="OVd-p3-nu6"/>
|
||||
</connections>
|
||||
|
|
|
@ -69,12 +69,22 @@
|
|||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="CBD-Vk-Xd4"/>
|
||||
</imageView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Omk-5j-C6r">
|
||||
<rect key="frame" x="381" y="0.0" width="130" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Label" id="xpG-oP-agI">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="138.5" y="152"/>
|
||||
</customView>
|
||||
<customObject id="nWn-x7-LxT" customClass="ErrorViewController">
|
||||
<connections>
|
||||
<outlet property="backgroundImage" destination="eih-a8-Pqa" id="2xh-8r-1Qu"/>
|
||||
<outlet property="buildVersion" destination="Omk-5j-C6r" id="aTp-c3-RVy"/>
|
||||
<outlet property="smallLogo" destination="ugn-Hk-gWL" id="EVI-d3-mf5"/>
|
||||
<outlet property="voxelImage" destination="jmW-5q-Mf3" id="NiI-cY-tAf"/>
|
||||
</connections>
|
||||
|
|
|
@ -119,12 +119,22 @@
|
|||
<action selector="havingTrouble:" target="NkF-nk-81S" id="tsf-tC-aqq"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="EsT-hn-six">
|
||||
<rect key="frame" x="380" y="0.0" width="130" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Label" id="AGt-jH-mX2">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="138.5" y="154"/>
|
||||
</customView>
|
||||
<customObject id="NkF-nk-81S" customClass="LoginScreen">
|
||||
<connections>
|
||||
<outlet property="backgroundImage" destination="L56-Jv-0N8" id="INT-rB-YtG"/>
|
||||
<outlet property="buildVersion" destination="EsT-hn-six" id="Z4k-Gv-U5g"/>
|
||||
<outlet property="button" destination="jKE-fV-Tjv" id="or6-tG-r6R"/>
|
||||
<outlet property="header" destination="hIC-qf-Abj" id="sVQ-rl-cvR"/>
|
||||
<outlet property="orginization" destination="L7W-3n-OKy" id="TiL-wn-Z2b"/>
|
||||
|
|
|
@ -53,6 +53,15 @@
|
|||
<rect key="frame" x="68" y="78" width="394" height="20"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
</progressIndicator>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="U3I-TA-xRz">
|
||||
<rect key="frame" x="380" y="0.0" width="130" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Label" id="8YH-Td-daK">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="138.5" y="154"/>
|
||||
</customView>
|
||||
|
@ -60,6 +69,7 @@
|
|||
<connections>
|
||||
<outlet property="background" destination="kuY-e2-Hqb" id="CBc-bD-ux7"/>
|
||||
<outlet property="boldStatus" destination="EMF-E4-qLL" id="udm-8B-7lt"/>
|
||||
<outlet property="buildVersion" destination="U3I-TA-xRz" id="HDI-tW-1cC"/>
|
||||
<outlet property="progressView" destination="aEr-fi-fkV" id="OUy-Qp-tiP"/>
|
||||
<outlet property="smallLogo" destination="uh2-4K-n56" id="pYg-hP-nr5"/>
|
||||
<outlet property="smallStatus" destination="BSg-lp-njL" id="ziz-ek-Lq4"/>
|
||||
|
|
|
@ -26,11 +26,21 @@
|
|||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
|
||||
<imageCell key="cell" refusesFirstResponder="YES" alignment="left" imageScaling="proportionallyDown" id="qC6-tI-Uwf"/>
|
||||
</imageView>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="EgF-VK-Hfo">
|
||||
<rect key="frame" x="380" y="0.0" width="130" height="14"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<textFieldCell key="cell" lineBreakMode="clipping" alignment="right" title="Label" id="gWS-UL-cjB">
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
<color key="textColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="systemGrayColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
</subviews>
|
||||
<point key="canvasLocation" x="119.5" y="134"/>
|
||||
</customView>
|
||||
<customObject id="iJ0-FI-XIf" customClass="SplashScreen">
|
||||
<connections>
|
||||
<outlet property="buildVersion" destination="EgF-VK-Hfo" id="lVp-Ua-9Mt"/>
|
||||
<outlet property="imageView" destination="qtD-mb-qqq" id="rCt-Gd-Uux"/>
|
||||
<outlet property="logoImage" destination="2i5-Zw-nH7" id="7tM-sX-cvR"/>
|
||||
</connections>
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
@property (nonatomic, assign) IBOutlet NSImageView* backgroundImage;
|
||||
@property (nonatomic, assign) IBOutlet NSImageView* smallLogo;
|
||||
@property (nonatomic, assign) IBOutlet NSTextField* displayName;
|
||||
@property (nonatomic, assign) IBOutlet NSTextField* buildVersion;
|
||||
@end
|
||||
|
||||
@implementation DisplayNameScreen
|
||||
|
@ -13,10 +14,9 @@
|
|||
[self.backgroundImage setImage: [NSImage imageNamed:hifiBackgroundFilename]];
|
||||
[self.smallLogo setImage: [NSImage imageNamed:hifiSmallLogoFilename]];
|
||||
NSMutableAttributedString* displayNameString = [[NSMutableAttributedString alloc] initWithString:@"Display Name"];
|
||||
|
||||
[self.buildVersion setStringValue: [@"V." stringByAppendingString:@LAUNCHER_BUILD_VERSION]];
|
||||
[displayNameString addAttribute:NSForegroundColorAttributeName value:[NSColor grayColor] range:NSMakeRange(0, displayNameString.length)];
|
||||
[displayNameString addAttribute:NSFontAttributeName value:[NSFont systemFontOfSize:18] range:NSMakeRange(0,displayNameString.length)];
|
||||
|
||||
[self.displayName setPlaceholderAttributedString:displayNameString];
|
||||
[self.displayName setTarget:self];
|
||||
[self.displayName setAction:@selector(login:)];
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
@property (nonatomic, assign) IBOutlet NSImageView* backgroundImage;
|
||||
@property (nonatomic, assign) IBOutlet NSImageView* smallLogo;
|
||||
@property (nonatomic, assign) IBOutlet NSImageView* voxelImage;
|
||||
@property (nonatomic, assign) IBOutlet NSTextField* buildVersion;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -16,6 +17,7 @@
|
|||
[self.backgroundImage setImage:[NSImage imageNamed:hifiBackgroundFilename]];
|
||||
[self.smallLogo setImage:[NSImage imageNamed:hifiSmallLogoFilename]];
|
||||
[self.voxelImage setImage:[NSImage imageNamed:hifiVoxelFilename]];
|
||||
[self.buildVersion setStringValue: [@"V." stringByAppendingString:@LAUNCHER_BUILD_VERSION]];
|
||||
}
|
||||
|
||||
-(IBAction)resartLauncher:(id)sender
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
@property (nonatomic, assign) IBOutlet NSTextField* smallHeader;
|
||||
@property (nonatomic, assign) IBOutlet NSTextField* trouble;
|
||||
@property (nonatomic, assign) IBOutlet NSButton* button;
|
||||
@property (nonatomic, assign) IBOutlet NSTextField* buildVersion;
|
||||
@end
|
||||
|
||||
@implementation LoginScreen
|
||||
|
@ -36,6 +37,7 @@
|
|||
[self.button setTitle:@"TRY AGAIN"];
|
||||
}
|
||||
|
||||
[self.buildVersion setStringValue: [@"V." stringByAppendingString:@LAUNCHER_BUILD_VERSION]];
|
||||
[self.backgroundImage setImage:[NSImage imageNamed:hifiBackgroundFilename]];
|
||||
[self.smallLogo setImage:[NSImage imageNamed:hifiSmallLogoFilename]];
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
@property (nonatomic, assign) IBOutlet NSTextField* boldStatus;
|
||||
@property (nonatomic, assign) IBOutlet NSTextField* smallStatus;
|
||||
@property (nonatomic, assign) IBOutlet NSProgressIndicator* progressView;
|
||||
@property (nonatomic, assign) IBOutlet NSTextField* buildVersion;
|
||||
@end
|
||||
|
||||
@implementation ProcessScreen
|
||||
|
@ -37,6 +38,7 @@
|
|||
default:
|
||||
break;
|
||||
}
|
||||
[self.buildVersion setStringValue: [@"V." stringByAppendingString:@LAUNCHER_BUILD_VERSION]];
|
||||
[self.background setImage: [NSImage imageNamed:hifiBackgroundFilename]];
|
||||
[self.smallLogo setImage: [NSImage imageNamed:hifiSmallLogoFilename]];
|
||||
[self.voxelImage setImage: [NSImage imageNamed:hifiVoxelFilename]];
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
@property (nonatomic, assign) IBOutlet NSImageView* imageView;
|
||||
@property (nonatomic, assign) IBOutlet NSImageView* logoImage;
|
||||
@property (nonatomic, assign) IBOutlet NSButton* button;
|
||||
@property (nonatomic, assign) IBOutlet NSTextField* buildVersion;
|
||||
@end
|
||||
|
||||
@implementation SplashScreen
|
||||
|
@ -15,5 +16,6 @@
|
|||
-(void)awakeFromNib {
|
||||
[self.imageView setImage:[NSImage imageNamed:hifiBackgroundFilename]];
|
||||
[self.logoImage setImage:[NSImage imageNamed:hifiLargeLogoFilename]];
|
||||
[self.buildVersion setStringValue: [@"V." stringByAppendingString:@LAUNCHER_BUILD_VERSION]];
|
||||
}
|
||||
@end
|
||||
|
|
|
@ -10,6 +10,7 @@ set(CMAKE_MFC_FLAG 1)
|
|||
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
|
||||
include("cmake/macros/SetPackagingParameters.cmake")
|
||||
|
||||
add_executable(HQLauncher
|
||||
WIN32
|
||||
|
@ -49,6 +50,8 @@ function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE)
|
|||
endif()
|
||||
endfunction()
|
||||
|
||||
set_packaging_parameters()
|
||||
|
||||
set_from_env(LAUNCHER_HMAC_SECRET LAUNCHER_HMAC_SECRET "")
|
||||
|
||||
if (LAUNCHER_HMAC_SECRET STREQUAL "")
|
||||
|
@ -56,7 +59,7 @@ if (LAUNCHER_HMAC_SECRET STREQUAL "")
|
|||
endif()
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_HMAC_SECRET="${LAUNCHER_HMAC_SECRET}")
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}")
|
||||
|
||||
# Preprocessor definitions
|
||||
target_compile_definitions(HQLauncher PRIVATE
|
||||
|
|
|
@ -92,8 +92,8 @@ STYLE DS_SETFONT | DS_FIXEDSYS | WS_POPUP | WS_VISIBLE | WS_CAPTION
|
|||
EXSTYLE WS_EX_APPWINDOW
|
||||
FONT 10, "MS Shell Dlg", 400, 0, 0x0
|
||||
BEGIN
|
||||
CONTROL "",IDC_VOXEL,"Static",SS_BLACKRECT,65,3,174,123, NOT WS_VISIBLE
|
||||
CONTROL "", IDC_PROGRESS, "Static", SS_BLACKRECT, 35, 170, 239, 4, NOT WS_VISIBLE
|
||||
CONTROL "",IDC_VOXEL,"Static",SS_BLACKRECT,65,3,174,123
|
||||
CONTROL "",IDC_PROGRESS,"Static",SS_BLACKRECT,35,170,239,4
|
||||
EDITTEXT IDC_ORGNAME,44,68,219,12,ES_AUTOHSCROLL | NOT WS_VISIBLE | NOT WS_BORDER
|
||||
EDITTEXT IDC_USERNAME,44,95,219,12,ES_AUTOHSCROLL | NOT WS_VISIBLE | NOT WS_BORDER
|
||||
EDITTEXT IDC_PASSWORD,44,122,219,12,ES_PASSWORD | ES_AUTOHSCROLL | NOT WS_VISIBLE | NOT WS_BORDER
|
||||
|
@ -107,6 +107,7 @@ BEGIN
|
|||
RTEXT "",IDC_TERMS,15,172,180,15,NOT WS_VISIBLE
|
||||
CONTROL "",IDC_TERMS_LINK,"Button",BS_OWNERDRAW | BS_FLAT | NOT WS_VISIBLE | WS_TABSTOP,197,172,80,15
|
||||
CTEXT "",IDC_TROUBLE,65,203,174,15,NOT WS_VISIBLE
|
||||
RTEXT "",IDC_VERSION,100,205,205,10
|
||||
CONTROL "NEXT",IDC_BUTTON_NEXT,"Button",BS_OWNERDRAW | BS_FLAT | NOT WS_VISIBLE | WS_TABSTOP,107,158,94,16
|
||||
CONTROL "Having Trouble?",IDC_TROUBLE_LINK,"Button",BS_OWNERDRAW | BS_FLAT | NOT WS_VISIBLE | WS_TABSTOP,126,203,56,11
|
||||
END
|
||||
|
|
|
@ -39,16 +39,27 @@ BOOL CLauncherApp::InitInstance() {
|
|||
}
|
||||
int iNumOfArgs;
|
||||
LPWSTR* pArgs = CommandLineToArgvW(GetCommandLine(), &iNumOfArgs);
|
||||
bool isUninstalling = false;
|
||||
bool isRestarting = false;
|
||||
bool uninstalling = false;
|
||||
bool restarting = false;
|
||||
bool noUpdate = false;
|
||||
LauncherManager::ContinueActionOnStart continueAction = LauncherManager::ContinueActionOnStart::ContinueNone;
|
||||
if (iNumOfArgs > 1) {
|
||||
if (CString(pArgs[1]).Compare(_T("--uninstall")) == 0) {
|
||||
isUninstalling = true;
|
||||
} else if (CString(pArgs[1]).Compare(_T("--restart")) == 0) {
|
||||
isRestarting = true;
|
||||
for (int i = 1; i < iNumOfArgs; i++) {
|
||||
CString curArg = CString(pArgs[i]);
|
||||
if (curArg.Compare(_T("--uninstall")) == 0) {
|
||||
uninstalling = true;
|
||||
} else if (curArg.Compare(_T("--restart")) == 0) {
|
||||
restarting = true;
|
||||
} else if (curArg.Compare(_T("--noUpdate")) == 0) {
|
||||
noUpdate = true;
|
||||
} else if (curArg.Compare(_T("--continueAction")) == 0) {
|
||||
if (i + 1 < iNumOfArgs) {
|
||||
continueAction = LauncherManager::getContinueActionFromParam(pArgs[i + 1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!isRestarting) {
|
||||
if (!restarting) {
|
||||
// don't launch if already running
|
||||
CreateMutex(NULL, TRUE, _T("HQ_Launcher_Mutex"));
|
||||
if (GetLastError() == ERROR_ALREADY_EXISTS) {
|
||||
|
@ -56,10 +67,10 @@ BOOL CLauncherApp::InitInstance() {
|
|||
}
|
||||
}
|
||||
|
||||
if (isUninstalling) {
|
||||
if (uninstalling) {
|
||||
_manager.uninstall();
|
||||
} else {
|
||||
_manager.init();
|
||||
_manager.init(!noUpdate, continueAction);
|
||||
}
|
||||
if (!_manager.hasFailed() && !_manager.installLauncher()) {
|
||||
return FALSE;
|
||||
|
|
|
@ -40,6 +40,8 @@ static CString GRAPHIK_SEMIBOLD = _T("Graphik-Semibold");
|
|||
static CString TROUBLE_URL = _T("https://www.highfidelity.com/hq-support");
|
||||
static CString TERMS_URL = _T("https://www.highfidelity.com/termsofservice");
|
||||
|
||||
static int SPLASH_DURATION = 100;
|
||||
|
||||
|
||||
CLauncherDlg::CLauncherDlg(CWnd* pParent)
|
||||
: CDialog(IDD_LAUNCHER_DIALOG, pParent)
|
||||
|
@ -112,6 +114,11 @@ BOOL CLauncherDlg::OnInitDialog() {
|
|||
m_voxel = (CStatic *)GetDlgItem(IDC_VOXEL);
|
||||
m_progress = (CStatic *)GetDlgItem(IDC_PROGRESS);
|
||||
|
||||
m_version = (CStatic *)GetDlgItem(IDC_VERSION);
|
||||
CString version;
|
||||
version.Format(_T("V.%s"), theApp._manager.getLauncherVersion());
|
||||
m_version->SetWindowTextW(version);
|
||||
|
||||
m_voxel->EnableD2DSupport();
|
||||
m_progress->EnableD2DSupport();
|
||||
|
||||
|
@ -230,7 +237,6 @@ void CLauncherDlg::startProcess() {
|
|||
theApp._manager.setFailed(true);
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
BOOL CLauncherDlg::getHQInfo(const CString& orgname) {
|
||||
|
@ -322,11 +328,12 @@ void CLauncherDlg::drawLogo(CHwndRenderTarget* pRenderTarget) {
|
|||
void CLauncherDlg::drawSmallLogo(CHwndRenderTarget* pRenderTarget) {
|
||||
CD2DBitmap m_pBitmamLogo(pRenderTarget, IDB_PNG5, _T("PNG"));
|
||||
auto size = pRenderTarget->GetSize();
|
||||
int padding = 6;
|
||||
int xPadding = 6;
|
||||
int yPadding = 22;
|
||||
int logoWidth = 100;
|
||||
int logoHeight = 18;
|
||||
float logoPosX = size.width - logoWidth - padding;
|
||||
float logoPosY = size.height - logoHeight - padding;
|
||||
float logoPosX = size.width - logoWidth - xPadding;
|
||||
float logoPosY = size.height - logoHeight - yPadding;
|
||||
CD2DRectF logoRec(logoPosX, logoPosY, logoPosX + logoWidth, logoPosY + logoHeight);
|
||||
pRenderTarget->DrawBitmap(&m_pBitmamLogo, logoRec);
|
||||
}
|
||||
|
@ -350,7 +357,7 @@ void CLauncherDlg::drawVoxel(CHwndRenderTarget* pRenderTarget) {
|
|||
}
|
||||
|
||||
void CLauncherDlg::drawProgress(CHwndRenderTarget* pRenderTarget, float progress, const D2D1::ColorF& color) {
|
||||
auto size = pRenderTarget->GetPixelSize();
|
||||
auto size = pRenderTarget->GetSize();
|
||||
if (progress == 0.0f) {
|
||||
return;
|
||||
} else {
|
||||
|
@ -521,6 +528,7 @@ BOOL CLauncherDlg::getTextFormat(int resID, TextFormat& formatOut) {
|
|||
formatOut.size = FIELDS_FONT_SIZE;
|
||||
formatOut.color = COLOR_GREY;
|
||||
break;
|
||||
case IDC_VERSION:
|
||||
case IDC_TERMS:
|
||||
formatOut.size = TERMS_FONT_SIZE;
|
||||
break;
|
||||
|
@ -663,16 +671,50 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) {
|
|||
// Refresh
|
||||
setDrawDialog(_drawStep, true);
|
||||
}
|
||||
|
||||
if (theApp._manager.needsSelfUpdate()) {
|
||||
if (theApp._manager.needsSelfDownload()) {
|
||||
theApp._manager.downloadNewLauncher();
|
||||
} else {
|
||||
if (_splashStep > SPLASH_DURATION && _splashStep < 2 * SPLASH_DURATION) {
|
||||
float progress = (float)(_splashStep - SPLASH_DURATION) / SPLASH_DURATION;
|
||||
if (theApp._manager.willContinueUpdating()) {
|
||||
progress = CONTINUE_UPDATING_GLOBAL_OFFSET * progress;
|
||||
progress = min(progress, CONTINUE_UPDATING_GLOBAL_OFFSET);
|
||||
}
|
||||
theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, progress);
|
||||
_splashStep++;
|
||||
}
|
||||
if (theApp._manager.needsRestartNewLauncher()) {
|
||||
if (_splashStep >= 2 * SPLASH_DURATION) {
|
||||
theApp._manager.restartNewLauncher();
|
||||
exit(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LauncherManager::ContinueActionOnStart continueAction = theApp._manager.getContinueAction();
|
||||
if (_showSplash) {
|
||||
if (_splashStep == 0) {
|
||||
if (theApp._manager.needsUninstall()) {
|
||||
theApp._manager.addToLog(_T("Waiting to uninstall"));
|
||||
setDrawDialog(DrawStep::DrawProcessUninstall);
|
||||
} else if (continueAction == LauncherManager::ContinueActionOnStart::ContinueUpdate) {
|
||||
setDrawDialog(DrawStep::DrawProcessUpdate);
|
||||
theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, 0.0f);
|
||||
} else if (continueAction == LauncherManager::ContinueActionOnStart::ContinueLogIn) {
|
||||
_splashStep = SPLASH_DURATION;
|
||||
} else if (continueAction == LauncherManager::ContinueActionOnStart::ContinueFinish) {
|
||||
theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, 1.0f);
|
||||
setDrawDialog(DrawStep::DrawProcessFinishUpdate);
|
||||
_splashStep = SPLASH_DURATION;
|
||||
_showSplash = false;
|
||||
} else {
|
||||
theApp._manager.addToLog(_T("Start splash screen"));
|
||||
setDrawDialog(DrawStep::DrawLogo);
|
||||
}
|
||||
} else if (_splashStep > 100 && !theApp._manager.needsToWait()) {
|
||||
} else if (_splashStep > SPLASH_DURATION && !theApp._manager.needsToWait()) {
|
||||
_showSplash = false;
|
||||
if (theApp._manager.shouldShutDown()) {
|
||||
if (_applicationWND != NULL) {
|
||||
|
@ -692,12 +734,14 @@ void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) {
|
|||
theApp._manager.addToLog(_T("HQ failed to uninstall."));
|
||||
theApp._manager.setFailed(true);
|
||||
}
|
||||
} else if (theApp._manager.needsSelfUpdate()) {
|
||||
setDrawDialog(DrawStep::DrawProcessUpdate);
|
||||
} else {
|
||||
theApp._manager.addToLog(_T("Starting login"));
|
||||
setDrawDialog(DrawStep::DrawLoginLogin);
|
||||
}
|
||||
} else if (theApp._manager.needsUninstall()) {
|
||||
theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, (float)_splashStep/100);
|
||||
theApp._manager.updateProgress(LauncherManager::ProcessType::Uninstall, (float)_splashStep / SPLASH_DURATION);
|
||||
}
|
||||
_splashStep++;
|
||||
} else if (theApp._manager.shouldShutDown()) {
|
||||
|
@ -741,12 +785,17 @@ void CLauncherDlg::setDrawDialog(DrawStep step, BOOL isUpdate) {
|
|||
auto m_voxelRenderTarget = m_voxel->GetRenderTarget();
|
||||
auto m_progressRenderTarget = m_progress->GetRenderTarget();
|
||||
switch (_drawStep) {
|
||||
case DrawStep::DrawLogo:
|
||||
case DrawStep::DrawLogo: {
|
||||
m_pRenderTarget->BeginDraw();
|
||||
drawBackground(m_pRenderTarget);
|
||||
drawLogo(m_pRenderTarget);
|
||||
m_pRenderTarget->EndDraw();
|
||||
CRect redrawRec;
|
||||
GetClientRect(redrawRec);
|
||||
redrawRec.top = redrawRec.bottom - 30;
|
||||
RedrawWindow(redrawRec);
|
||||
break;
|
||||
}
|
||||
case DrawStep::DrawLoginLogin:
|
||||
case DrawStep::DrawLoginErrorOrg:
|
||||
case DrawStep::DrawLoginErrorCred:
|
||||
|
|
|
@ -94,6 +94,8 @@ protected:
|
|||
CStatic* m_username_banner;
|
||||
CStatic* m_password_banner;
|
||||
|
||||
CStatic* m_version;
|
||||
|
||||
HWND _applicationWND { 0 };
|
||||
|
||||
void drawBackground(CHwndRenderTarget* pRenderTarget);
|
||||
|
|
|
@ -16,17 +16,55 @@
|
|||
|
||||
|
||||
LauncherManager::LauncherManager() {
|
||||
int tokenPos = 0;
|
||||
_launcherVersion = CString(LAUNCHER_BUILD_VERSION).Tokenize(_T("-"), tokenPos);
|
||||
}
|
||||
|
||||
LauncherManager::~LauncherManager() {
|
||||
}
|
||||
|
||||
void LauncherManager::init() {
|
||||
void LauncherManager::init(BOOL allowUpdate, ContinueActionOnStart continueAction) {
|
||||
initLog();
|
||||
addToLog(_T("Getting most recent build"));
|
||||
getMostRecentBuild(_latestApplicationURL, _latestVersion);
|
||||
_updateLauncherAllowed = allowUpdate;
|
||||
_continueAction = continueAction;
|
||||
CString msg;
|
||||
msg.Format(_T("Start Screen: %s"), getContinueActionParam(continueAction));
|
||||
addToLog(msg);
|
||||
_shouldWait = _continueAction == ContinueActionOnStart::ContinueNone;
|
||||
if (_continueAction == ContinueActionOnStart::ContinueUpdate) {
|
||||
_progressOffset = CONTINUE_UPDATING_GLOBAL_OFFSET;
|
||||
}
|
||||
addToLog(_T("Launcher is running version: " + _launcherVersion));
|
||||
addToLog(_T("Getting most recent builds"));
|
||||
getMostRecentBuilds(_latestLauncherURL, _latestLauncherVersion, _latestApplicationURL, _latestVersion);
|
||||
}
|
||||
|
||||
CString LauncherManager::getContinueActionParam(LauncherManager::ContinueActionOnStart continueAction) {
|
||||
switch (continueAction) {
|
||||
case LauncherManager::ContinueActionOnStart::ContinueNone:
|
||||
return _T("");
|
||||
case LauncherManager::ContinueActionOnStart::ContinueLogIn:
|
||||
return _T("LogIn");
|
||||
case LauncherManager::ContinueActionOnStart::ContinueUpdate:
|
||||
return _T("Update");
|
||||
case LauncherManager::ContinueActionOnStart::ContinueFinish:
|
||||
return _T("Finish");
|
||||
default:
|
||||
return _T("");
|
||||
}
|
||||
}
|
||||
|
||||
LauncherManager::ContinueActionOnStart LauncherManager::getContinueActionFromParam(const CString& param) {
|
||||
if (param.Compare(_T("LogIn")) == 0) {
|
||||
return ContinueActionOnStart::ContinueLogIn;
|
||||
} else if (param.Compare(_T("Update")) == 0) {
|
||||
return ContinueActionOnStart::ContinueUpdate;
|
||||
} else if (param.Compare(_T("Finish")) == 0) {
|
||||
return ContinueActionOnStart::ContinueFinish;
|
||||
} else {
|
||||
return ContinueActionOnStart::ContinueNone;
|
||||
}
|
||||
}
|
||||
BOOL LauncherManager::initLog() {
|
||||
CString logPath;
|
||||
auto result = getAndCreatePaths(PathType::Launcher_Directory, logPath);
|
||||
|
@ -124,6 +162,8 @@ BOOL LauncherManager::restartLauncher() {
|
|||
|
||||
void LauncherManager::updateProgress(ProcessType processType, float progress) {
|
||||
switch (processType) {
|
||||
case ProcessType::DownloadLauncher:
|
||||
break;
|
||||
case ProcessType::Uninstall:
|
||||
_progress = progress;
|
||||
break;
|
||||
|
@ -153,6 +193,7 @@ void LauncherManager::updateProgress(ProcessType processType, float progress) {
|
|||
default:
|
||||
break;
|
||||
}
|
||||
_progress = _progressOffset + (1.0f - _progressOffset) * _progress;
|
||||
TRACE("progress = %f\n", _progress);
|
||||
}
|
||||
|
||||
|
@ -200,11 +241,11 @@ BOOL LauncherManager::isApplicationInstalled(CString& version, CString& domain,
|
|||
CString applicationDir;
|
||||
getAndCreatePaths(PathType::Launcher_Directory, applicationDir);
|
||||
CString applicationPath = applicationDir + "interface\\interface.exe";
|
||||
BOOL isApplicationInstalled = PathFileExistsW(applicationPath);
|
||||
BOOL isInstalled = PathFileExistsW(applicationPath);
|
||||
BOOL configFileExist = PathFileExistsW(applicationDir + _T("interface\\config.json"));
|
||||
if (configFileExist) {
|
||||
LauncherUtils::ResponseError status = readConfigJSON(version, domain, content, loggedIn);
|
||||
return isApplicationInstalled && status == LauncherUtils::ResponseError::NoError;
|
||||
return isInstalled && status == LauncherUtils::ResponseError::NoError;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -359,14 +400,23 @@ LauncherUtils::ResponseError LauncherManager::readOrganizationJSON(const CString
|
|||
return LauncherUtils::ResponseError::ParsingJSON;
|
||||
}
|
||||
|
||||
void LauncherManager::getMostRecentBuild(CString& urlOut, CString& versionOut) {
|
||||
void LauncherManager::getMostRecentBuilds(CString& launcherUrlOut, CString& launcherVersionOut,
|
||||
CString& interfaceUrlOut, CString& interfaceVersionOut) {
|
||||
CString contentTypeJson = L"content-type:application/json";
|
||||
std::function<void(CString, int)> httpCallback = [&](CString response, int err) {
|
||||
LauncherUtils::ResponseError error = LauncherUtils::ResponseError(err);
|
||||
if (error == LauncherUtils::ResponseError::NoError) {
|
||||
Json::Value json;
|
||||
error = LauncherUtils::ResponseError::ParsingJSON;
|
||||
if (LauncherUtils::parseJSON(response, json)) {
|
||||
if (json["launcher"].isObject()) {
|
||||
if (json["launcher"]["windows"].isObject() && json["launcher"]["windows"]["url"].isString()) {
|
||||
launcherUrlOut = json["launcher"]["windows"]["url"].asCString();
|
||||
}
|
||||
if (json["launcher"]["version"].isInt()) {
|
||||
std::string version = std::to_string(json["launcher"]["version"].asInt());
|
||||
launcherVersionOut = CString(version.c_str());
|
||||
}
|
||||
}
|
||||
int count = json["count"].isInt() ? json["count"].asInt() : 0;
|
||||
if (count > 0 && json["results"].isArray()) {
|
||||
for (int i = 0; i < count; i++) {
|
||||
|
@ -374,20 +424,22 @@ void LauncherManager::getMostRecentBuild(CString& urlOut, CString& versionOut) {
|
|||
Json::Value result = json["results"][i];
|
||||
if (result["latest_version"].isInt()) {
|
||||
std::string version = std::to_string(result["latest_version"].asInt());
|
||||
versionOut = CString(version.c_str());
|
||||
interfaceVersionOut = CString(version.c_str());
|
||||
}
|
||||
if (result["installers"].isObject() &&
|
||||
result["installers"]["windows"].isObject() &&
|
||||
result["installers"]["windows"]["zip_url"].isString()) {
|
||||
urlOut = result["installers"]["windows"]["zip_url"].asCString();
|
||||
error = LauncherUtils::ResponseError::NoError;
|
||||
interfaceUrlOut = result["installers"]["windows"]["zip_url"].asCString();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (launcherUrlOut.IsEmpty() || launcherVersionOut.IsEmpty() || interfaceUrlOut.IsEmpty() || interfaceVersionOut.IsEmpty()) {
|
||||
error = LauncherUtils::ResponseError::ParsingJSON;
|
||||
}
|
||||
}
|
||||
onMostRecentBuildReceived(response, error);
|
||||
}
|
||||
onMostRecentBuildsReceived(response, error);
|
||||
};
|
||||
LauncherUtils::httpCallOnThread(L"HQ Launcher",
|
||||
L"thunder.highfidelity.com",
|
||||
|
@ -395,31 +447,50 @@ void LauncherManager::getMostRecentBuild(CString& urlOut, CString& versionOut) {
|
|||
contentTypeJson, CStringA(), false, httpCallback);
|
||||
}
|
||||
|
||||
void LauncherManager::onMostRecentBuildReceived(const CString& response, LauncherUtils::ResponseError error) {
|
||||
void LauncherManager::onMostRecentBuildsReceived(const CString& response, LauncherUtils::ResponseError error) {
|
||||
if (error == LauncherUtils::ResponseError::NoError) {
|
||||
addToLog(_T("Latest version: ") + _latestVersion);
|
||||
addToLog(_T("Latest launcher version: ") + _latestLauncherVersion);
|
||||
CString currentVersion;
|
||||
if (isApplicationInstalled(currentVersion, _domainURL, _contentURL, _loggedIn) && _loggedIn) {
|
||||
addToLog(_T("Installed version: ") + currentVersion);
|
||||
if (_latestVersion.Compare(currentVersion) == 0) {
|
||||
addToLog(_T("Already running most recent build. Launching interface.exe"));
|
||||
_shouldLaunch = TRUE;
|
||||
_shouldShutdown = TRUE;
|
||||
} else {
|
||||
addToLog(_T("New build found. Updating"));
|
||||
_shouldUpdate = TRUE;
|
||||
}
|
||||
} else if (_loggedIn) {
|
||||
addToLog(_T("Interface not found but logged in. Reinstalling"));
|
||||
_shouldUpdate = TRUE;
|
||||
BOOL isInstalled = (isApplicationInstalled(currentVersion, _domainURL, _contentURL, _loggedIn) && _loggedIn);
|
||||
bool newInterfaceVersion = _latestVersion.Compare(currentVersion) != 0;
|
||||
bool newLauncherVersion = _latestLauncherVersion.Compare(_launcherVersion) != 0 && _updateLauncherAllowed;
|
||||
if (newLauncherVersion) {
|
||||
CString updatingMsg;
|
||||
updatingMsg.Format(_T("Updating Launcher from version: %s to version: %s"), _launcherVersion, _latestLauncherVersion);
|
||||
addToLog(updatingMsg);
|
||||
_shouldUpdateLauncher = TRUE;
|
||||
_shouldDownloadLauncher = TRUE;
|
||||
_keepLoggingIn = !isInstalled;
|
||||
_keepUpdating = isInstalled && newInterfaceVersion;
|
||||
} else {
|
||||
_shouldInstall = TRUE;
|
||||
if (_updateLauncherAllowed) {
|
||||
addToLog(_T("Already running most recent build. Launching interface.exe"));
|
||||
} else {
|
||||
addToLog(_T("Updating the launcher was not allowed --noUpdate"));
|
||||
}
|
||||
if (isInstalled) {
|
||||
addToLog(_T("Installed version: ") + currentVersion);
|
||||
if (!newInterfaceVersion) {
|
||||
addToLog(_T("Already running most recent build. Launching interface.exe"));
|
||||
_shouldLaunch = TRUE;
|
||||
_shouldShutdown = TRUE;
|
||||
} else {
|
||||
addToLog(_T("New build found. Updating"));
|
||||
_shouldUpdate = TRUE;
|
||||
}
|
||||
} else if (_loggedIn) {
|
||||
addToLog(_T("Interface not found but logged in. Reinstalling"));
|
||||
_shouldUpdate = TRUE;
|
||||
} else {
|
||||
_shouldInstall = TRUE;
|
||||
}
|
||||
}
|
||||
_shouldWait = FALSE;
|
||||
|
||||
} else {
|
||||
_hasFailed = true;
|
||||
CString msg;
|
||||
msg.Format(_T("Getting most recent build has failed with error: %d"), error);
|
||||
msg.Format(_T("Getting most recent builds has failed with error: %d"), error);
|
||||
addToLog(msg);
|
||||
msg.Format(_T("Response: %s"), response);
|
||||
addToLog(msg);
|
||||
|
@ -521,7 +592,7 @@ BOOL LauncherManager::extractApplication() {
|
|||
}
|
||||
};
|
||||
std::function<void(float)> onProgress = [&](float progress) {
|
||||
updateProgress(ProcessType::UnzipApplication, progress);
|
||||
updateProgress(ProcessType::UnzipApplication, max(progress, 0.0f));
|
||||
};
|
||||
_currentProcess = ProcessType::UnzipApplication;
|
||||
BOOL success = LauncherUtils::unzipFileOnThread(ProcessType::UnzipApplication,
|
||||
|
@ -563,9 +634,25 @@ void LauncherManager::onFileDownloaded(ProcessType type) {
|
|||
setFailed(true);
|
||||
}
|
||||
});
|
||||
} else if (type == ProcessType::DownloadLauncher) {
|
||||
_shouldRestartNewLauncher = true;
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherManager::restartNewLauncher() {
|
||||
closeLog();
|
||||
ContinueActionOnStart continueAction = ContinueActionOnStart::ContinueFinish;
|
||||
if (_keepUpdating) {
|
||||
continueAction = ContinueActionOnStart::ContinueUpdate;
|
||||
} else if (_keepLoggingIn) {
|
||||
continueAction = ContinueActionOnStart::ContinueLogIn;
|
||||
}
|
||||
CStringW params;
|
||||
params.Format(_T(" --restart --noUpdate --continueAction %s"), getContinueActionParam(continueAction));
|
||||
LauncherUtils::launchApplication(_tempLauncherPath, params.GetBuffer());
|
||||
Sleep(500);
|
||||
}
|
||||
|
||||
|
||||
BOOL LauncherManager::installContent() {
|
||||
std::string contentZipFile = LauncherUtils::cStringToStd(_contentZipPath);
|
||||
|
@ -582,7 +669,7 @@ BOOL LauncherManager::installContent() {
|
|||
}
|
||||
};
|
||||
std::function<void(float)> onProgress = [&](float progress) {
|
||||
updateProgress(ProcessType::UnzipContent, progress);
|
||||
updateProgress(ProcessType::UnzipContent, max(progress, 0.0f));
|
||||
};
|
||||
_currentProcess = ProcessType::UnzipContent;
|
||||
BOOL success = LauncherUtils::unzipFileOnThread(ProcessType::UnzipContent, contentZipFile,
|
||||
|
@ -597,10 +684,13 @@ BOOL LauncherManager::installContent() {
|
|||
|
||||
|
||||
BOOL LauncherManager::downloadFile(ProcessType type, const CString& url, CString& outPath) {
|
||||
CString fileName = url.Mid(url.ReverseFind('/') + 1);
|
||||
CString downloadDirectory;
|
||||
BOOL success = getAndCreatePaths(LauncherManager::PathType::Download_Directory, downloadDirectory);
|
||||
outPath = downloadDirectory + fileName;
|
||||
BOOL success = TRUE;
|
||||
if (outPath.IsEmpty()) {
|
||||
CString fileName = url.Mid(url.ReverseFind('/') + 1);
|
||||
CString downloadDirectory;
|
||||
BOOL success = getAndCreatePaths(LauncherManager::PathType::Download_Directory, downloadDirectory);
|
||||
outPath = downloadDirectory + fileName;
|
||||
}
|
||||
_currentProcess = type;
|
||||
if (success) {
|
||||
addToLog(_T("Downloading: ") + url);
|
||||
|
@ -610,18 +700,18 @@ BOOL LauncherManager::downloadFile(ProcessType type, const CString& url, CString
|
|||
} else {
|
||||
if (type == ProcessType::DownloadApplication) {
|
||||
addToLog(_T("Error downloading content."));
|
||||
} else if (type == ProcessType::DownloadLauncher) {
|
||||
addToLog(_T("Error downloading launcher."));
|
||||
} else {
|
||||
addToLog(_T("Error downloading application."));
|
||||
}
|
||||
_hasFailed = true;
|
||||
}
|
||||
};
|
||||
std::function<void(float)> onProgress = [&](float progress) {
|
||||
updateProgress(_currentProcess, progress);
|
||||
std::function<void(float)> onProgress = [&, type](float progress) {
|
||||
updateProgress(_currentProcess, max(progress, 0.0f));
|
||||
};
|
||||
if (!LauncherUtils::downloadFileOnThread(type, url, outPath, onDownloadFinished, onProgress)) {
|
||||
success = FALSE;
|
||||
}
|
||||
success = LauncherUtils::downloadFileOnThread(type, url, outPath, onDownloadFinished, onProgress);
|
||||
}
|
||||
return success;
|
||||
}
|
||||
|
@ -637,6 +727,14 @@ BOOL LauncherManager::downloadApplication() {
|
|||
return downloadFile(ProcessType::DownloadApplication, applicationURL, _applicationZipPath);
|
||||
}
|
||||
|
||||
BOOL LauncherManager::downloadNewLauncher() {
|
||||
_shouldDownloadLauncher = FALSE;
|
||||
getAndCreatePaths(PathType::Temp_Directory, _tempLauncherPath);
|
||||
CString tempName = _T("HQLauncher") + _launcherVersion + _T(".exe");
|
||||
_tempLauncherPath += tempName;
|
||||
return downloadFile(ProcessType::DownloadLauncher, _latestLauncherURL, _tempLauncherPath);
|
||||
}
|
||||
|
||||
void LauncherManager::onCancel() {
|
||||
if (_currentProcess == ProcessType::UnzipApplication) {
|
||||
_latestVersion = _T("");
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#pragma once
|
||||
|
||||
#include "LauncherUtils.h"
|
||||
#include "LauncherDlg.h"
|
||||
|
||||
const CString DIRECTORY_NAME_APP = _T("HQ");
|
||||
const CString DIRECTORY_NAME_DOWNLOADS = _T("downloads");
|
||||
|
@ -25,9 +26,9 @@ const float DOWNLOAD_APPLICATION_INSTALL_WEIGHT = 0.5f;
|
|||
const float EXTRACT_APPLICATION_INSTALL_WEIGHT = 0.2f;
|
||||
const float DOWNLOAD_APPLICATION_UPDATE_WEIGHT = 0.75f;
|
||||
const float EXTRACT_APPLICATION_UPDATE_WEIGHT = 0.25f;
|
||||
const float CONTINUE_UPDATING_GLOBAL_OFFSET = 0.2f;
|
||||
|
||||
class LauncherManager
|
||||
{
|
||||
class LauncherManager {
|
||||
public:
|
||||
enum PathType {
|
||||
Running_Path = 0,
|
||||
|
@ -49,27 +50,40 @@ public:
|
|||
ErrorIOFiles
|
||||
};
|
||||
enum ProcessType {
|
||||
DownloadLauncher = 0,
|
||||
DownloadContent,
|
||||
DownloadApplication,
|
||||
UnzipContent,
|
||||
UnzipApplication,
|
||||
Uninstall
|
||||
};
|
||||
enum ContinueActionOnStart {
|
||||
ContinueNone = 0,
|
||||
ContinueLogIn,
|
||||
ContinueUpdate,
|
||||
ContinueFinish
|
||||
};
|
||||
|
||||
LauncherManager();
|
||||
~LauncherManager();
|
||||
void init();
|
||||
void init(BOOL allowUpdate, ContinueActionOnStart continueAction);
|
||||
static CString getContinueActionParam(ContinueActionOnStart continueAction);
|
||||
static ContinueActionOnStart getContinueActionFromParam(const CString& param);
|
||||
BOOL initLog();
|
||||
BOOL addToLog(const CString& line);
|
||||
void closeLog();
|
||||
void saveErrorLog();
|
||||
BOOL getAndCreatePaths(PathType type, CString& outPath);
|
||||
BOOL getInstalledVersion(const CString& path, CString& version);
|
||||
BOOL isApplicationInstalled(CString& version, CString& domain,
|
||||
BOOL isApplicationInstalled(CString& version, CString& domain,
|
||||
CString& content, bool& loggedIn);
|
||||
LauncherUtils::ResponseError getAccessTokenForCredentials(const CString& username, const CString& password);
|
||||
void getMostRecentBuild(CString& urlOut, CString& versionOut);
|
||||
void getMostRecentBuilds(CString& launcherUrlOut,
|
||||
CString& launcherVersionOut,
|
||||
CString& interfaceUrlOut,
|
||||
CString& interfaceVersionOut);
|
||||
LauncherUtils::ResponseError readOrganizationJSON(const CString& hash);
|
||||
LauncherUtils::ResponseError readConfigJSON(CString& version, CString& domain,
|
||||
LauncherUtils::ResponseError readConfigJSON(CString& version, CString& domain,
|
||||
CString& content, bool& loggedIn);
|
||||
BOOL createConfigJSON();
|
||||
BOOL createApplicationRegistryKeys(int size);
|
||||
|
@ -87,33 +101,46 @@ public:
|
|||
const CString& getVersion() const { return _version; }
|
||||
BOOL shouldShutDown() const { return _shouldShutdown; }
|
||||
BOOL shouldLaunch() const { return _shouldLaunch; }
|
||||
BOOL needsUpdate() { return _shouldUpdate; }
|
||||
BOOL needsUninstall() { return _shouldUninstall; }
|
||||
BOOL needsInstall() { return _shouldInstall; }
|
||||
BOOL needsToWait() { return _shouldWait; }
|
||||
BOOL needsUpdate() const { return _shouldUpdate; }
|
||||
BOOL needsSelfUpdate() const { return _shouldUpdateLauncher; }
|
||||
BOOL needsSelfDownload() const { return _shouldDownloadLauncher; }
|
||||
BOOL needsUninstall() const { return _shouldUninstall; }
|
||||
BOOL needsInstall() const { return _shouldInstall; }
|
||||
BOOL needsToWait() const { return _shouldWait; }
|
||||
BOOL needsRestartNewLauncher() const { return _shouldRestartNewLauncher; }
|
||||
BOOL willContinueUpdating() const { return _keepUpdating; }
|
||||
ContinueActionOnStart getContinueAction() { return _continueAction; }
|
||||
void setDisplayName(const CString& displayName) { _displayName = displayName; }
|
||||
bool isLoggedIn() { return _loggedIn; }
|
||||
bool hasFailed() { return _hasFailed; }
|
||||
bool isLoggedIn() const { return _loggedIn; }
|
||||
bool hasFailed() const { return _hasFailed; }
|
||||
void setFailed(bool hasFailed) { _hasFailed = hasFailed; }
|
||||
const CString& getLatestInterfaceURL() const { return _latestApplicationURL; }
|
||||
void uninstall() { _shouldUninstall = true; _shouldWait = false; };
|
||||
void uninstall() {
|
||||
_shouldUninstall = true;
|
||||
_shouldWait = false;
|
||||
};
|
||||
|
||||
BOOL downloadFile(ProcessType type, const CString& url, CString& localPath);
|
||||
BOOL downloadContent();
|
||||
BOOL downloadApplication();
|
||||
BOOL downloadNewLauncher();
|
||||
BOOL installContent();
|
||||
BOOL extractApplication();
|
||||
void restartNewLauncher();
|
||||
void onZipExtracted(ProcessType type, int size);
|
||||
void onFileDownloaded(ProcessType type);
|
||||
float getProgress() { return _progress; }
|
||||
float getProgress() const { return _progress; }
|
||||
void updateProgress(ProcessType processType, float progress);
|
||||
void onCancel();
|
||||
const CString& getLauncherVersion() const { return _launcherVersion; }
|
||||
|
||||
private:
|
||||
ProcessType _currentProcess { ProcessType::DownloadApplication };
|
||||
void onMostRecentBuildReceived(const CString& response, LauncherUtils::ResponseError error);
|
||||
void onMostRecentBuildsReceived(const CString& response, LauncherUtils::ResponseError error);
|
||||
CString _latestApplicationURL;
|
||||
CString _latestVersion;
|
||||
CString _latestLauncherURL;
|
||||
CString _latestLauncherVersion;
|
||||
CString _contentURL;
|
||||
CString _domainURL;
|
||||
CString _version;
|
||||
|
@ -121,6 +148,8 @@ private:
|
|||
CString _tokensJSON;
|
||||
CString _applicationZipPath;
|
||||
CString _contentZipPath;
|
||||
CString _launcherVersion;
|
||||
CString _tempLauncherPath;
|
||||
bool _loggedIn { false };
|
||||
bool _hasFailed { false };
|
||||
BOOL _shouldUpdate { FALSE };
|
||||
|
@ -129,7 +158,14 @@ private:
|
|||
BOOL _shouldShutdown { FALSE };
|
||||
BOOL _shouldLaunch { FALSE };
|
||||
BOOL _shouldWait { TRUE };
|
||||
BOOL _shouldUpdateLauncher { FALSE };
|
||||
BOOL _shouldDownloadLauncher { FALSE };
|
||||
BOOL _updateLauncherAllowed { TRUE };
|
||||
BOOL _shouldRestartNewLauncher { FALSE };
|
||||
BOOL _keepLoggingIn { FALSE };
|
||||
BOOL _keepUpdating { FALSE };
|
||||
ContinueActionOnStart _continueAction;
|
||||
float _progressOffset { 0.0f };
|
||||
float _progress { 0.0f };
|
||||
CStdioFile _logFile;
|
||||
};
|
||||
|
||||
|
|
45
launchers/win32/cmake/macros/SetPackagingParameters.cmake
Normal file
45
launchers/win32/cmake/macros/SetPackagingParameters.cmake
Normal file
|
@ -0,0 +1,45 @@
|
|||
#
|
||||
# SetPackagingParameters.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Leonardo Murillo on 07/14/2015.
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
# This macro checks some Jenkins defined environment variables to determine the origin of this build
|
||||
# and decides how targets should be packaged.
|
||||
|
||||
macro(SET_PACKAGING_PARAMETERS)
|
||||
set(PR_BUILD 0)
|
||||
set(PRODUCTION_BUILD 0)
|
||||
set(DEV_BUILD 0)
|
||||
set(BUILD_NUMBER 0)
|
||||
|
||||
set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV")
|
||||
set_from_env(RELEASE_NUMBER RELEASE_NUMBER "")
|
||||
set_from_env(STABLE_BUILD STABLE_BUILD 0)
|
||||
|
||||
message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}")
|
||||
set(BUILD_NUMBER ${RELEASE_NUMBER})
|
||||
|
||||
if (RELEASE_TYPE STREQUAL "PRODUCTION")
|
||||
set(PRODUCTION_BUILD 1)
|
||||
set(BUILD_VERSION ${RELEASE_NUMBER})
|
||||
|
||||
# add definition for this release type
|
||||
add_definitions(-DPRODUCTION_BUILD)
|
||||
|
||||
elseif (RELEASE_TYPE STREQUAL "PR")
|
||||
set(PR_BUILD 1)
|
||||
set(BUILD_VERSION "PR${RELEASE_NUMBER}")
|
||||
|
||||
# add definition for this release type
|
||||
add_definitions(-DPR_BUILD)
|
||||
else ()
|
||||
set(DEV_BUILD 1)
|
||||
set(BUILD_VERSION "dev")
|
||||
endif ()
|
||||
|
||||
endmacro(SET_PACKAGING_PARAMETERS)
|
|
@ -27,6 +27,7 @@
|
|||
#define IDC_TROUBLE 1023
|
||||
#define IDC_VOXEL 1024
|
||||
#define IDC_PROGRESS 1025
|
||||
#define IDC_VERSION 1026
|
||||
#define IDC_TROUBLE_LINK 1027
|
||||
|
||||
// Next default values for new objects
|
||||
|
|
|
@ -1092,6 +1092,11 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
_desiredStateAge = 0.0f;
|
||||
}
|
||||
_desiredState = RigRole::Takeoff;
|
||||
} else if (ccState == CharacterControllerState::Seated) {
|
||||
if (_desiredState != RigRole::Seated) {
|
||||
_desiredStateAge = 0.0f;
|
||||
}
|
||||
_desiredState = RigRole::Seated;
|
||||
} else {
|
||||
float moveThresh;
|
||||
if (_state != RigRole::Move) {
|
||||
|
@ -1216,6 +1221,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
_animVars.set("isInAirStand", false);
|
||||
_animVars.set("isInAirRun", false);
|
||||
_animVars.set("isNotInAir", true);
|
||||
_animVars.set("isSeated", false);
|
||||
|
||||
} else if (_state == RigRole::Turn) {
|
||||
if (turningSpeed > 0.0f) {
|
||||
|
@ -1244,6 +1250,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
_animVars.set("isInAirStand", false);
|
||||
_animVars.set("isInAirRun", false);
|
||||
_animVars.set("isNotInAir", true);
|
||||
_animVars.set("isSeated", false);
|
||||
|
||||
} else if (_state == RigRole::Idle) {
|
||||
// default anim vars to notMoving and notTurning
|
||||
|
@ -1265,6 +1272,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
_animVars.set("isInAirStand", false);
|
||||
_animVars.set("isInAirRun", false);
|
||||
_animVars.set("isNotInAir", true);
|
||||
_animVars.set("isSeated", false);
|
||||
|
||||
} else if (_state == RigRole::Hover) {
|
||||
// flying.
|
||||
|
@ -1286,6 +1294,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
_animVars.set("isInAirStand", false);
|
||||
_animVars.set("isInAirRun", false);
|
||||
_animVars.set("isNotInAir", true);
|
||||
_animVars.set("isSeated", false);
|
||||
|
||||
} else if (_state == RigRole::Takeoff) {
|
||||
// jumping in-air
|
||||
|
@ -1315,6 +1324,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
_animVars.set("isInAirStand", false);
|
||||
_animVars.set("isInAirRun", false);
|
||||
_animVars.set("isNotInAir", false);
|
||||
_animVars.set("isSeated", false);
|
||||
|
||||
} else if (_state == RigRole::InAir) {
|
||||
// jumping in-air
|
||||
|
@ -1333,6 +1343,7 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
_animVars.set("isTakeoffStand", false);
|
||||
_animVars.set("isTakeoffRun", false);
|
||||
_animVars.set("isNotTakeoff", true);
|
||||
_animVars.set("isSeated", false);
|
||||
|
||||
bool inAirRun = forwardSpeed > 0.1f;
|
||||
if (inAirRun) {
|
||||
|
@ -1354,6 +1365,26 @@ void Rig::computeMotionAnimationState(float deltaTime, const glm::vec3& worldPos
|
|||
float alpha = glm::clamp((-workingVelocity.y * sensorToWorldScale) / jumpSpeed, -1.0f, 1.0f) + 1.0f;
|
||||
|
||||
_animVars.set("inAirAlpha", alpha);
|
||||
} else if (_state == RigRole::Seated) {
|
||||
_animVars.set("isMovingForward", false);
|
||||
_animVars.set("isMovingBackward", false);
|
||||
_animVars.set("isMovingRight", false);
|
||||
_animVars.set("isMovingLeft", false);
|
||||
_animVars.set("isMovingRightHmd", false);
|
||||
_animVars.set("isMovingLeftHmd", false);
|
||||
_animVars.set("isNotMoving", false);
|
||||
_animVars.set("isTurningRight", false);
|
||||
_animVars.set("isTurningLeft", false);
|
||||
_animVars.set("isNotTurning", true);
|
||||
_animVars.set("isFlying", false);
|
||||
_animVars.set("isNotFlying", true);
|
||||
_animVars.set("isTakeoffStand", false);
|
||||
_animVars.set("isTakeoffRun", false);
|
||||
_animVars.set("isNotTakeoff", true);
|
||||
_animVars.set("isInAirStand", false);
|
||||
_animVars.set("isInAirRun", false);
|
||||
_animVars.set("isNotInAir", true);
|
||||
_animVars.set("isSeated", true);
|
||||
}
|
||||
|
||||
t += deltaTime;
|
||||
|
@ -1873,6 +1904,61 @@ void Rig::updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabl
|
|||
}
|
||||
}
|
||||
|
||||
void Rig::updateReactions(const ControllerParameters& params) {
|
||||
|
||||
// enable/disable animVars
|
||||
bool enabled = params.reactionEnabledFlags[AVATAR_REACTION_POSITIVE];
|
||||
_animVars.set("reactionPositiveEnabled", enabled);
|
||||
_animVars.set("reactionPositiveDisabled", !enabled);
|
||||
|
||||
enabled = params.reactionEnabledFlags[AVATAR_REACTION_NEGATIVE];
|
||||
_animVars.set("reactionNegativeEnabled", enabled);
|
||||
_animVars.set("reactionNegativeDisabled", !enabled);
|
||||
|
||||
enabled = params.reactionEnabledFlags[AVATAR_REACTION_RAISE_HAND];
|
||||
_animVars.set("reactionRaiseHandEnabled", enabled);
|
||||
_animVars.set("reactionRaiseHandDisabled", !enabled);
|
||||
|
||||
enabled = params.reactionEnabledFlags[AVATAR_REACTION_APPLAUD];
|
||||
_animVars.set("reactionApplaudEnabled", enabled);
|
||||
_animVars.set("reactionApplaudDisabled", !enabled);
|
||||
|
||||
enabled = params.reactionEnabledFlags[AVATAR_REACTION_POINT];
|
||||
_animVars.set("reactionPointEnabled", enabled);
|
||||
_animVars.set("reactionPointDisabled", !enabled);
|
||||
|
||||
// trigger animVars
|
||||
if (params.reactionTriggers[AVATAR_REACTION_POSITIVE]) {
|
||||
_animVars.set("reactionPositiveTrigger", true);
|
||||
} else {
|
||||
_animVars.set("reactionPositiveTrigger", false);
|
||||
}
|
||||
|
||||
if (params.reactionTriggers[AVATAR_REACTION_NEGATIVE]) {
|
||||
_animVars.set("reactionNegativeTrigger", true);
|
||||
} else {
|
||||
_animVars.set("reactionNegativeTrigger", false);
|
||||
}
|
||||
|
||||
if (params.reactionTriggers[AVATAR_REACTION_RAISE_HAND]) {
|
||||
_animVars.set("reactionRaiseHandTrigger", true);
|
||||
} else {
|
||||
_animVars.set("reactionRaiseHandTrigger", false);
|
||||
}
|
||||
|
||||
if (params.reactionTriggers[AVATAR_REACTION_APPLAUD]) {
|
||||
_animVars.set("reactionApplaudTrigger", true);
|
||||
} else {
|
||||
_animVars.set("reactionApplaudTrigger", false);
|
||||
}
|
||||
|
||||
if (params.reactionTriggers[AVATAR_REACTION_POINT]) {
|
||||
_animVars.set("reactionPointTrigger", true);
|
||||
} else {
|
||||
_animVars.set("reactionPointTrigger", false);
|
||||
}
|
||||
}
|
||||
|
||||
void Rig::updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAtSpot, const glm::vec3& saccade) {
|
||||
|
||||
// TODO: does not properly handle avatar scale.
|
||||
|
@ -2152,6 +2238,8 @@ void Rig::updateFromControllerParameters(const ControllerParameters& params, flo
|
|||
}
|
||||
}
|
||||
|
||||
updateReactions(params);
|
||||
|
||||
_previousControllerParameters = params;
|
||||
}
|
||||
|
||||
|
|
|
@ -88,6 +88,8 @@ public:
|
|||
AnimPose secondaryControllerPoses[NumSecondaryControllerTypes]; // rig space
|
||||
uint8_t secondaryControllerFlags[NumSecondaryControllerTypes];
|
||||
bool isTalking;
|
||||
bool reactionEnabledFlags[NUM_AVATAR_REACTIONS];
|
||||
bool reactionTriggers[NUM_AVATAR_REACTIONS];
|
||||
HFMJointShapeInfo hipsShapeInfo;
|
||||
HFMJointShapeInfo spineShapeInfo;
|
||||
HFMJointShapeInfo spine1ShapeInfo;
|
||||
|
@ -107,7 +109,8 @@ public:
|
|||
Ground = 0,
|
||||
Takeoff,
|
||||
InAir,
|
||||
Hover
|
||||
Hover,
|
||||
Seated
|
||||
};
|
||||
|
||||
Rig();
|
||||
|
@ -268,6 +271,7 @@ protected:
|
|||
void updateFeet(bool leftFootEnabled, bool rightFootEnabled, bool headEnabled,
|
||||
const AnimPose& leftFootPose, const AnimPose& rightFootPose,
|
||||
const glm::mat4& rigToSensorMatrix, const glm::mat4& sensorToRigMatrix);
|
||||
void updateReactions(const ControllerParameters& params);
|
||||
|
||||
void updateEyeJoint(int index, const glm::vec3& modelTranslation, const glm::quat& modelRotation, const glm::vec3& lookAt, const glm::vec3& saccade);
|
||||
void calcAnimAlpha(float speed, const std::vector<float>& referenceSpeeds, float* alphaOut) const;
|
||||
|
@ -336,7 +340,8 @@ protected:
|
|||
Move,
|
||||
Hover,
|
||||
Takeoff,
|
||||
InAir
|
||||
InAir,
|
||||
Seated
|
||||
};
|
||||
RigRole _state { RigRole::Idle };
|
||||
RigRole _desiredState { RigRole::Idle };
|
||||
|
|
|
@ -7,6 +7,11 @@ link_hifi_libraries(audio plugins)
|
|||
include_hifi_library_headers(shared)
|
||||
include_hifi_library_headers(networking)
|
||||
|
||||
if (ANDROID)
|
||||
else ()
|
||||
target_webrtc()
|
||||
endif ()
|
||||
|
||||
# append audio includes to our list of includes to bubble
|
||||
target_include_directories(${TARGET_NAME} PUBLIC "${HIFI_LIBRARY_DIR}/audio/src")
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
#endif
|
||||
|
||||
#ifdef WIN32
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#define WIN32_LEAN_AND_MEAN 1
|
||||
#include <windows.h>
|
||||
#include <Mmsystem.h>
|
||||
#include <mmdeviceapi.h>
|
||||
|
@ -286,6 +286,7 @@ AudioClient::AudioClient() :
|
|||
_shouldEchoLocally(false),
|
||||
_shouldEchoToServer(false),
|
||||
_isNoiseGateEnabled(true),
|
||||
_isAECEnabled(true),
|
||||
_reverb(false),
|
||||
_reverbOptions(&_scriptReverbOptions),
|
||||
_inputToNetworkResampler(NULL),
|
||||
|
@ -302,6 +303,7 @@ AudioClient::AudioClient() :
|
|||
_isHeadsetPluggedIn(false),
|
||||
#endif
|
||||
_orientationGetter(DEFAULT_ORIENTATION_GETTER) {
|
||||
|
||||
// avoid putting a lock in the device callback
|
||||
assert(_localSamplesAvailable.is_lock_free());
|
||||
|
||||
|
@ -353,6 +355,10 @@ AudioClient::AudioClient() :
|
|||
|
||||
configureReverb();
|
||||
|
||||
#if defined(WEBRTC_ENABLED)
|
||||
configureWebrtc();
|
||||
#endif
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
auto& packetReceiver = nodeList->getPacketReceiver();
|
||||
packetReceiver.registerListener(PacketType::AudioStreamStats, &_stats, "processStreamStatsPacket");
|
||||
|
@ -1084,6 +1090,131 @@ void AudioClient::setReverbOptions(const AudioEffectOptions* options) {
|
|||
}
|
||||
}
|
||||
|
||||
#if defined(WEBRTC_ENABLED)
|
||||
|
||||
static void deinterleaveToFloat(const int16_t* src, float* const* dst, int numFrames, int numChannels) {
|
||||
for (int i = 0; i < numFrames; i++) {
|
||||
for (int ch = 0; ch < numChannels; ch++) {
|
||||
float f = *src++;
|
||||
f *= (1/32768.0f); // scale
|
||||
dst[ch][i] = f; // deinterleave
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void interleaveToInt16(const float* const* src, int16_t* dst, int numFrames, int numChannels) {
|
||||
for (int i = 0; i < numFrames; i++) {
|
||||
for (int ch = 0; ch < numChannels; ch++) {
|
||||
float f = src[ch][i];
|
||||
f *= 32768.0f; // scale
|
||||
f += (f < 0.0f) ? -0.5f : 0.5f; // round
|
||||
f = std::max(std::min(f, 32767.0f), -32768.0f); // saturate
|
||||
*dst++ = (int16_t)f; // interleave
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioClient::configureWebrtc() {
|
||||
_apm = webrtc::AudioProcessingBuilder().Create();
|
||||
|
||||
webrtc::AudioProcessing::Config config;
|
||||
|
||||
config.pre_amplifier.enabled = false;
|
||||
config.high_pass_filter.enabled = false;
|
||||
config.echo_canceller.enabled = true;
|
||||
config.echo_canceller.mobile_mode = false;
|
||||
config.echo_canceller.use_legacy_aec = false;
|
||||
config.noise_suppression.enabled = false;
|
||||
config.noise_suppression.level = webrtc::AudioProcessing::Config::NoiseSuppression::kModerate;
|
||||
config.voice_detection.enabled = false;
|
||||
config.gain_controller1.enabled = false;
|
||||
config.gain_controller2.enabled = false;
|
||||
config.gain_controller2.fixed_digital.gain_db = 0.0f;
|
||||
config.gain_controller2.adaptive_digital.enabled = false;
|
||||
config.residual_echo_detector.enabled = true;
|
||||
config.level_estimation.enabled = false;
|
||||
|
||||
_apm->ApplyConfig(config);
|
||||
}
|
||||
|
||||
// rebuffer into 10ms chunks
|
||||
void AudioClient::processWebrtcFarEnd(const int16_t* samples, int numFrames, int numChannels, int sampleRate) {
|
||||
|
||||
const webrtc::StreamConfig streamConfig = webrtc::StreamConfig(sampleRate, numChannels);
|
||||
const int numChunk = (int)streamConfig.num_frames();
|
||||
|
||||
if (sampleRate > WEBRTC_SAMPLE_RATE_MAX) {
|
||||
qCWarning(audioclient) << "WebRTC does not support" << sampleRate << "output sample rate.";
|
||||
return;
|
||||
}
|
||||
if (numChannels > WEBRTC_CHANNELS_MAX) {
|
||||
qCWarning(audioclient) << "WebRTC does not support" << numChannels << "output channels.";
|
||||
return;
|
||||
}
|
||||
|
||||
while (numFrames > 0) {
|
||||
|
||||
// number of frames to fill
|
||||
int numFill = std::min(numFrames, numChunk - _numFifoFarEnd);
|
||||
|
||||
// refill fifo
|
||||
memcpy(&_fifoFarEnd[_numFifoFarEnd], samples, numFill * numChannels * sizeof(int16_t));
|
||||
samples += numFill * numChannels;
|
||||
numFrames -= numFill;
|
||||
_numFifoFarEnd += numFill;
|
||||
|
||||
if (_numFifoFarEnd == numChunk) {
|
||||
|
||||
// convert audio format
|
||||
float buffer[WEBRTC_CHANNELS_MAX][WEBRTC_FRAMES_MAX];
|
||||
float* const buffers[WEBRTC_CHANNELS_MAX] = { buffer[0], buffer[1] };
|
||||
deinterleaveToFloat(_fifoFarEnd, buffers, numChunk, numChannels);
|
||||
|
||||
// process one chunk
|
||||
int error = _apm->ProcessReverseStream(buffers, streamConfig, streamConfig, buffers);
|
||||
if (error != _apm->kNoError) {
|
||||
qCWarning(audioclient) << "WebRTC ProcessReverseStream() returned ERROR:" << error;
|
||||
}
|
||||
_numFifoFarEnd = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AudioClient::processWebrtcNearEnd(int16_t* samples, int numFrames, int numChannels, int sampleRate) {
|
||||
|
||||
const webrtc::StreamConfig streamConfig = webrtc::StreamConfig(sampleRate, numChannels);
|
||||
const int numChunk = (int)streamConfig.num_frames();
|
||||
|
||||
if (sampleRate > WEBRTC_SAMPLE_RATE_MAX) {
|
||||
qCWarning(audioclient) << "WebRTC does not support" << sampleRate << "input sample rate.";
|
||||
return;
|
||||
}
|
||||
if (numChannels > WEBRTC_CHANNELS_MAX) {
|
||||
qCWarning(audioclient) << "WebRTC does not support" << numChannels << "input channels.";
|
||||
return;
|
||||
}
|
||||
if (numFrames != numChunk) {
|
||||
qCWarning(audioclient) << "WebRTC requires exactly 10ms of input.";
|
||||
return;
|
||||
}
|
||||
|
||||
// convert audio format
|
||||
float buffer[WEBRTC_CHANNELS_MAX][WEBRTC_FRAMES_MAX];
|
||||
float* const buffers[WEBRTC_CHANNELS_MAX] = { buffer[0], buffer[1] };
|
||||
deinterleaveToFloat(samples, buffers, numFrames, numChannels);
|
||||
|
||||
// process one chunk
|
||||
int error = _apm->ProcessStream(buffers, streamConfig, streamConfig, buffers);
|
||||
if (error != _apm->kNoError) {
|
||||
qCWarning(audioclient) << "WebRTC ProcessStream() returned ERROR:" << error;
|
||||
} else {
|
||||
// modify samples in-place
|
||||
interleaveToInt16(buffers, samples, numFrames, numChannels);
|
||||
}
|
||||
}
|
||||
|
||||
#endif // WEBRTC_ENABLED
|
||||
|
||||
void AudioClient::handleLocalEchoAndReverb(QByteArray& inputByteArray) {
|
||||
// If there is server echo, reverb will be applied to the recieved audio stream so no need to have it here.
|
||||
bool hasReverb = _reverb || _receivedAudioStream.hasReverb();
|
||||
|
@ -1262,6 +1393,13 @@ void AudioClient::handleMicAudioInput() {
|
|||
|
||||
_inputRingBuffer.readSamples(inputAudioSamples.get(), inputSamplesRequired);
|
||||
|
||||
#if defined(WEBRTC_ENABLED)
|
||||
if (_isAECEnabled) {
|
||||
processWebrtcNearEnd(inputAudioSamples.get(), inputSamplesRequired / _inputFormat.channelCount(),
|
||||
_inputFormat.channelCount(), _inputFormat.sampleRate());
|
||||
}
|
||||
#endif
|
||||
|
||||
// detect loudness and clipping on the raw input
|
||||
bool isClipping = false;
|
||||
float loudness = computeLoudness(inputAudioSamples.get(), inputSamplesRequired, _inputFormat.channelCount(), isClipping);
|
||||
|
@ -1574,6 +1712,15 @@ void AudioClient::setWarnWhenMuted(bool enable, bool emitSignal) {
|
|||
}
|
||||
}
|
||||
|
||||
void AudioClient::setAcousticEchoCancellation(bool enable, bool emitSignal) {
|
||||
if (_isAECEnabled != enable) {
|
||||
_isAECEnabled = enable;
|
||||
if (emitSignal) {
|
||||
emit acousticEchoCancellationChanged(_isAECEnabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioClient::setIsStereoInput(bool isStereoInput) {
|
||||
bool stereoInputChanged = false;
|
||||
if (isStereoInput != _isStereoInput && _inputDeviceInfo.supportedChannelCounts().contains(2)) {
|
||||
|
@ -2107,15 +2254,16 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
|
|||
return maxSize;
|
||||
}
|
||||
|
||||
// samples requested from OUTPUT_CHANNEL_COUNT
|
||||
// max samples requested from OUTPUT_CHANNEL_COUNT
|
||||
int deviceChannelCount = _audio->_outputFormat.channelCount();
|
||||
int samplesRequested = (int)(maxSize / AudioConstants::SAMPLE_SIZE) * OUTPUT_CHANNEL_COUNT / deviceChannelCount;
|
||||
int maxSamplesRequested = (int)(maxSize / AudioConstants::SAMPLE_SIZE) * OUTPUT_CHANNEL_COUNT / deviceChannelCount;
|
||||
// restrict samplesRequested to the size of our mix/scratch buffers
|
||||
samplesRequested = std::min(samplesRequested, _audio->_outputPeriod);
|
||||
maxSamplesRequested = std::min(maxSamplesRequested, _audio->_outputPeriod);
|
||||
|
||||
int16_t* scratchBuffer = _audio->_outputScratchBuffer;
|
||||
float* mixBuffer = _audio->_outputMixBuffer;
|
||||
|
||||
int samplesRequested = maxSamplesRequested;
|
||||
int networkSamplesPopped;
|
||||
if ((networkSamplesPopped = _receivedAudioStream.popSamples(samplesRequested, false)) > 0) {
|
||||
qCDebug(audiostream, "Read %d samples from buffer (%d available, %d requested)", networkSamplesPopped, _receivedAudioStream.getSamplesAvailable(), samplesRequested);
|
||||
|
@ -2160,45 +2308,45 @@ qint64 AudioClient::AudioOutputIODevice::readData(char * data, qint64 maxSize) {
|
|||
});
|
||||
|
||||
int samplesPopped = std::max(networkSamplesPopped, injectorSamplesPopped);
|
||||
int framesPopped = samplesPopped / AudioConstants::STEREO;
|
||||
int bytesWritten;
|
||||
if (samplesPopped > 0) {
|
||||
|
||||
// apply output gain
|
||||
float newGain = _audio->_outputGain.load(std::memory_order_acquire);
|
||||
float oldGain = _audio->_lastOutputGain;
|
||||
_audio->_lastOutputGain = newGain;
|
||||
|
||||
applyGainSmoothing<OUTPUT_CHANNEL_COUNT>(mixBuffer, framesPopped, oldGain, newGain);
|
||||
|
||||
if (deviceChannelCount == OUTPUT_CHANNEL_COUNT) {
|
||||
// limit the audio
|
||||
_audio->_audioLimiter.render(mixBuffer, (int16_t*)data, framesPopped);
|
||||
} else {
|
||||
_audio->_audioLimiter.render(mixBuffer, scratchBuffer, framesPopped);
|
||||
|
||||
// upmix or downmix to deviceChannelCount
|
||||
if (deviceChannelCount > OUTPUT_CHANNEL_COUNT) {
|
||||
int extraChannels = deviceChannelCount - OUTPUT_CHANNEL_COUNT;
|
||||
channelUpmix(scratchBuffer, (int16_t*)data, samplesPopped, extraChannels);
|
||||
} else {
|
||||
channelDownmix(scratchBuffer, (int16_t*)data, samplesPopped);
|
||||
}
|
||||
}
|
||||
|
||||
bytesWritten = framesPopped * AudioConstants::SAMPLE_SIZE * deviceChannelCount;
|
||||
assert(bytesWritten <= maxSize);
|
||||
|
||||
} else {
|
||||
// nothing on network, don't grab anything from injectors, and just return 0s
|
||||
memset(data, 0, maxSize);
|
||||
bytesWritten = maxSize;
|
||||
if (samplesPopped == 0) {
|
||||
// nothing on network, don't grab anything from injectors, and fill with silence
|
||||
samplesPopped = maxSamplesRequested;
|
||||
memset(mixBuffer, 0, samplesPopped * sizeof(float));
|
||||
}
|
||||
int framesPopped = samplesPopped / OUTPUT_CHANNEL_COUNT;
|
||||
|
||||
// apply output gain
|
||||
float newGain = _audio->_outputGain.load(std::memory_order_acquire);
|
||||
float oldGain = _audio->_lastOutputGain;
|
||||
_audio->_lastOutputGain = newGain;
|
||||
|
||||
applyGainSmoothing<OUTPUT_CHANNEL_COUNT>(mixBuffer, framesPopped, oldGain, newGain);
|
||||
|
||||
// limit the audio
|
||||
_audio->_audioLimiter.render(mixBuffer, scratchBuffer, framesPopped);
|
||||
|
||||
#if defined(WEBRTC_ENABLED)
|
||||
if (_audio->_isAECEnabled) {
|
||||
_audio->processWebrtcFarEnd(scratchBuffer, framesPopped, OUTPUT_CHANNEL_COUNT, _audio->_outputFormat.sampleRate());
|
||||
}
|
||||
#endif
|
||||
|
||||
// if required, upmix or downmix to deviceChannelCount
|
||||
if (deviceChannelCount == OUTPUT_CHANNEL_COUNT) {
|
||||
memcpy(data, scratchBuffer, samplesPopped * AudioConstants::SAMPLE_SIZE);
|
||||
} else if (deviceChannelCount > OUTPUT_CHANNEL_COUNT) {
|
||||
int extraChannels = deviceChannelCount - OUTPUT_CHANNEL_COUNT;
|
||||
channelUpmix(scratchBuffer, (int16_t*)data, samplesPopped, extraChannels);
|
||||
} else {
|
||||
channelDownmix(scratchBuffer, (int16_t*)data, samplesPopped);
|
||||
}
|
||||
int bytesWritten = framesPopped * AudioConstants::SAMPLE_SIZE * deviceChannelCount;
|
||||
assert(bytesWritten <= maxSize);
|
||||
|
||||
// send output buffer for recording
|
||||
if (_audio->_isRecording) {
|
||||
Lock lock(_recordMutex);
|
||||
_audio->_audioFileWav.addRawAudioChunk(reinterpret_cast<char*>(scratchBuffer), bytesWritten);
|
||||
_audio->_audioFileWav.addRawAudioChunk(data, bytesWritten);
|
||||
}
|
||||
|
||||
int bytesAudioOutputUnplayed = _audio->_audioOutput->bufferSize() - _audio->_audioOutput->bytesFree();
|
||||
|
|
|
@ -29,6 +29,7 @@
|
|||
#include <AbstractAudioInterface.h>
|
||||
#include <AudioEffectOptions.h>
|
||||
#include <AudioStreamStats.h>
|
||||
#include <shared/WebRTC.h>
|
||||
|
||||
#include <DependencyManager.h>
|
||||
#include <HifiSockAddr.h>
|
||||
|
@ -215,6 +216,9 @@ public slots:
|
|||
void setWarnWhenMuted(bool isNoiseGateEnabled, bool emitSignal = true);
|
||||
bool isWarnWhenMutedEnabled() const { return _warnWhenMuted; }
|
||||
|
||||
void setAcousticEchoCancellation(bool isAECEnabled, bool emitSignal = true);
|
||||
bool isAcousticEchoCancellationEnabled() const { return _isAECEnabled; }
|
||||
|
||||
virtual bool getLocalEcho() override { return _shouldEchoLocally; }
|
||||
virtual void setLocalEcho(bool localEcho) override { _shouldEchoLocally = localEcho; }
|
||||
virtual void toggleLocalEcho() override { _shouldEchoLocally = !_shouldEchoLocally; }
|
||||
|
@ -256,6 +260,7 @@ signals:
|
|||
void muteToggled(bool muted);
|
||||
void noiseReductionChanged(bool noiseReductionEnabled);
|
||||
void warnWhenMutedChanged(bool warnWhenMutedEnabled);
|
||||
void acousticEchoCancellationChanged(bool acousticEchoCancellationEnabled);
|
||||
void mutedByMixer();
|
||||
void inputReceived(const QByteArray& inputSamples);
|
||||
void inputLoudnessChanged(float loudness, bool isClipping);
|
||||
|
@ -377,6 +382,7 @@ private:
|
|||
bool _shouldEchoToServer;
|
||||
bool _isNoiseGateEnabled;
|
||||
bool _warnWhenMuted;
|
||||
bool _isAECEnabled;
|
||||
|
||||
bool _reverb;
|
||||
AudioEffectOptions _scriptReverbOptions;
|
||||
|
@ -414,9 +420,23 @@ private:
|
|||
// Adds Reverb
|
||||
void configureReverb();
|
||||
void updateReverbOptions();
|
||||
|
||||
void handleLocalEchoAndReverb(QByteArray& inputByteArray);
|
||||
|
||||
#if defined(WEBRTC_ENABLED)
|
||||
static const int WEBRTC_SAMPLE_RATE_MAX = 96000;
|
||||
static const int WEBRTC_CHANNELS_MAX = 2;
|
||||
static const int WEBRTC_FRAMES_MAX = webrtc::AudioProcessing::kChunkSizeMs * WEBRTC_SAMPLE_RATE_MAX / 1000;
|
||||
|
||||
webrtc::AudioProcessing* _apm { nullptr };
|
||||
|
||||
int16_t _fifoFarEnd[WEBRTC_CHANNELS_MAX * WEBRTC_FRAMES_MAX] {};
|
||||
int _numFifoFarEnd = 0; // numFrames saved in fifo
|
||||
|
||||
void configureWebrtc();
|
||||
void processWebrtcFarEnd(const int16_t* samples, int numFrames, int numChannels, int sampleRate);
|
||||
void processWebrtcNearEnd(int16_t* samples, int numFrames, int numChannels, int sampleRate);
|
||||
#endif
|
||||
|
||||
bool switchInputToAudioDevice(const QAudioDeviceInfo inputDeviceInfo, bool isShutdownRequest = false);
|
||||
bool switchOutputToAudioDevice(const QAudioDeviceInfo outputDeviceInfo, bool isShutdownRequest = false);
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include "HTTPManager.h"
|
||||
|
||||
const char* HTTPConnection::StatusCode200 = "200 OK";
|
||||
const char* HTTPConnection::StatusCode204 = "204 No Content";
|
||||
const char* HTTPConnection::StatusCode301 = "301 Moved Permanently";
|
||||
const char* HTTPConnection::StatusCode302 = "302 Found";
|
||||
const char* HTTPConnection::StatusCode400 = "400 Bad Request";
|
||||
|
|
|
@ -45,6 +45,7 @@ class HTTPConnection : public QObject {
|
|||
|
||||
public:
|
||||
static const char* StatusCode200;
|
||||
static const char* StatusCode204;
|
||||
static const char* StatusCode301;
|
||||
static const char* StatusCode302;
|
||||
static const char* StatusCode400;
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue