merge with master
1
.gitignore
vendored
|
@ -92,6 +92,7 @@ npm-debug.log
|
||||||
|
|
||||||
# Resource binary file
|
# Resource binary file
|
||||||
interface/compiledResources
|
interface/compiledResources
|
||||||
|
*.rcc
|
||||||
|
|
||||||
# GPUCache
|
# GPUCache
|
||||||
interface/resources/GPUCache/*
|
interface/resources/GPUCache/*
|
||||||
|
|
|
@ -376,7 +376,6 @@ void Agent::executeScript() {
|
||||||
// setup an Avatar for the script to use
|
// setup an Avatar for the script to use
|
||||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||||
scriptedAvatar->setID(getSessionUUID());
|
scriptedAvatar->setID(getSessionUUID());
|
||||||
scriptedAvatar->setForceFaceTrackerConnected(true);
|
|
||||||
|
|
||||||
// call model URL setters with empty URLs so our avatar, if user, will have the default models
|
// call model URL setters with empty URLs so our avatar, if user, will have the default models
|
||||||
scriptedAvatar->setSkeletonModelURL(QUrl());
|
scriptedAvatar->setSkeletonModelURL(QUrl());
|
||||||
|
|
|
@ -32,24 +32,42 @@ MixerAvatar::MixerAvatar() {
|
||||||
|
|
||||||
_challengeTimer.setSingleShot(true);
|
_challengeTimer.setSingleShot(true);
|
||||||
_challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS);
|
_challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS);
|
||||||
|
_challengeTimer.callOnTimeout(this, &MixerAvatar::challengeTimeout);
|
||||||
_challengeTimer.callOnTimeout(this, [this]() {
|
// QTimer::start is a set of overloaded functions.
|
||||||
if (_verifyState == challengeClient) {
|
connect(this, &MixerAvatar::startChallengeTimer, &_challengeTimer, static_cast<void(QTimer::*)()>(&QTimer::start));
|
||||||
_pendingEvent = false;
|
|
||||||
_verifyState = verificationFailed;
|
|
||||||
_needsIdentityUpdate = true;
|
|
||||||
qCDebug(avatars) << "Dynamic verification TIMED-OUT for" << getDisplayName() << getSessionUUID();
|
|
||||||
} else {
|
|
||||||
qCDebug(avatars) << "Ignoring timeout of avatar challenge";
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* MixerAvatar::stateToName(VerifyState state) {
|
const char* MixerAvatar::stateToName(VerifyState state) {
|
||||||
return QMetaEnum::fromType<VerifyState>().valueToKey(state);
|
return QMetaEnum::fromType<VerifyState>().valueToKey(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MixerAvatar::challengeTimeout() {
|
||||||
|
switch (_verifyState) {
|
||||||
|
case challengeClient:
|
||||||
|
_verifyState = staticValidation;
|
||||||
|
_pendingEvent = true;
|
||||||
|
if (++_numberChallenges < NUM_CHALLENGES_BEFORE_FAIL) {
|
||||||
|
qCDebug(avatars) << "Retrying (" << _numberChallenges << ") timed-out challenge for" << getDisplayName()
|
||||||
|
<< getSessionUUID();
|
||||||
|
} else {
|
||||||
|
_certifyFailed = true;
|
||||||
|
_needsIdentityUpdate = true;
|
||||||
|
qCWarning(avatars) << "ALERT: Dynamic verification TIMED-OUT for" << getDisplayName() << getSessionUUID();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case verificationFailed:
|
||||||
|
qCDebug(avatars) << "Retrying failed challenge for" << getDisplayName() << getSessionUUID();
|
||||||
|
_verifyState = staticValidation;
|
||||||
|
_pendingEvent = true;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
qCDebug(avatars) << "Ignoring timeout of avatar challenge";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void MixerAvatar::fetchAvatarFST() {
|
void MixerAvatar::fetchAvatarFST() {
|
||||||
if (_verifyState >= requestingFST && _verifyState <= challengeClient) {
|
if (_verifyState >= requestingFST && _verifyState <= challengeClient) {
|
||||||
qCDebug(avatars) << "WARNING: Avatar verification restarted; old state:" << stateToName(_verifyState);
|
qCDebug(avatars) << "WARNING: Avatar verification restarted; old state:" << stateToName(_verifyState);
|
||||||
|
@ -58,7 +76,7 @@ void MixerAvatar::fetchAvatarFST() {
|
||||||
|
|
||||||
_pendingEvent = false;
|
_pendingEvent = false;
|
||||||
|
|
||||||
QUrl avatarURL = getSkeletonModelURL();
|
QUrl avatarURL = _skeletonModelURL;
|
||||||
if (avatarURL.isEmpty() || avatarURL.isLocalFile() || avatarURL.scheme() == "qrc") {
|
if (avatarURL.isEmpty() || avatarURL.isLocalFile() || avatarURL.scheme() == "qrc") {
|
||||||
// Not network FST.
|
// Not network FST.
|
||||||
return;
|
return;
|
||||||
|
@ -210,23 +228,8 @@ void MixerAvatar::ownerRequestComplete() {
|
||||||
networkReply->deleteLater();
|
networkReply->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MixerAvatar::processCertifyEvents() {
|
void MixerAvatar::requestCurrentOwnership() {
|
||||||
if (!_pendingEvent) {
|
// Get registered owner's public key from metaverse.
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
|
||||||
switch (_verifyState) {
|
|
||||||
|
|
||||||
case receivedFST:
|
|
||||||
{
|
|
||||||
generateFSTHash();
|
|
||||||
if (_certificateIdFromFST.length() != 0) {
|
|
||||||
QString& marketplacePublicKey = EntityItem::_marketplacePublicKey;
|
|
||||||
bool staticVerification = validateFSTHash(marketplacePublicKey);
|
|
||||||
_verifyState = staticVerification ? staticValidation : verificationFailed;
|
|
||||||
|
|
||||||
if (_verifyState == staticValidation) {
|
|
||||||
static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" };
|
static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" };
|
||||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||||
QNetworkRequest networkRequest;
|
QNetworkRequest networkRequest;
|
||||||
|
@ -238,9 +241,30 @@ void MixerAvatar::processCertifyEvents() {
|
||||||
|
|
||||||
QJsonObject request;
|
QJsonObject request;
|
||||||
request["certificate_id"] = _certificateIdFromFST;
|
request["certificate_id"] = _certificateIdFromFST;
|
||||||
_verifyState = requestingOwner;
|
|
||||||
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||||
connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete);
|
connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MixerAvatar::processCertifyEvents() {
|
||||||
|
if (!_pendingEvent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMutexLocker certifyLocker(&_avatarCertifyLock);
|
||||||
|
switch (_verifyState) {
|
||||||
|
|
||||||
|
case receivedFST:
|
||||||
|
{
|
||||||
|
generateFSTHash();
|
||||||
|
_numberChallenges = 0;
|
||||||
|
if (_certificateIdFromFST.length() != 0) {
|
||||||
|
QString& marketplacePublicKey = EntityItem::_marketplacePublicKey;
|
||||||
|
bool staticVerification = validateFSTHash(marketplacePublicKey);
|
||||||
|
_verifyState = staticVerification ? staticValidation : verificationFailed;
|
||||||
|
|
||||||
|
if (_verifyState == staticValidation) {
|
||||||
|
requestCurrentOwnership();
|
||||||
|
_verifyState = requestingOwner;
|
||||||
} else {
|
} else {
|
||||||
_needsIdentityUpdate = true;
|
_needsIdentityUpdate = true;
|
||||||
_pendingEvent = false;
|
_pendingEvent = false;
|
||||||
|
@ -248,11 +272,21 @@ void MixerAvatar::processCertifyEvents() {
|
||||||
}
|
}
|
||||||
} else { // FST doesn't have a certificate, so noncertified rather than failed:
|
} else { // FST doesn't have a certificate, so noncertified rather than failed:
|
||||||
_pendingEvent = false;
|
_pendingEvent = false;
|
||||||
|
_certifyFailed = false;
|
||||||
|
_needsIdentityUpdate = true;
|
||||||
_verifyState = nonCertified;
|
_verifyState = nonCertified;
|
||||||
|
qCDebug(avatars) << "Avatar " << getDisplayName() << "(" << getSessionUUID() << ") isn't certified";
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case staticValidation:
|
||||||
|
{
|
||||||
|
requestCurrentOwnership();
|
||||||
|
_verifyState = requestingOwner;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
case ownerResponse:
|
case ownerResponse:
|
||||||
{
|
{
|
||||||
QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8());
|
QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8());
|
||||||
|
@ -310,23 +344,28 @@ void MixerAvatar::processCertifyEvents() {
|
||||||
void MixerAvatar::sendOwnerChallenge() {
|
void MixerAvatar::sendOwnerChallenge() {
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
auto nodeList = DependencyManager::get<NodeList>();
|
||||||
QByteArray avatarID = ("{" + _marketplaceIdFromFST + "}").toUtf8();
|
QByteArray avatarID = ("{" + _marketplaceIdFromFST + "}").toUtf8();
|
||||||
QByteArray nonce = QUuid::createUuid().toByteArray();
|
if (_challengeNonce.isEmpty()) {
|
||||||
|
_challengeNonce = QUuid::createUuid().toByteArray();
|
||||||
|
QCryptographicHash nonceHash(QCryptographicHash::Sha256);
|
||||||
|
nonceHash.addData(_challengeNonce);
|
||||||
|
_challengeNonceHash = nonceHash.result();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
|
auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership,
|
||||||
2 * sizeof(int) + nonce.length() + avatarID.length(), true);
|
2 * sizeof(int) + _challengeNonce.length() + avatarID.length(), true);
|
||||||
challengeOwnershipPacket->writePrimitive(avatarID.length());
|
challengeOwnershipPacket->writePrimitive(avatarID.length());
|
||||||
challengeOwnershipPacket->writePrimitive(nonce.length());
|
challengeOwnershipPacket->writePrimitive(_challengeNonce.length());
|
||||||
challengeOwnershipPacket->write(avatarID);
|
challengeOwnershipPacket->write(avatarID);
|
||||||
challengeOwnershipPacket->write(nonce);
|
challengeOwnershipPacket->write(_challengeNonce);
|
||||||
|
|
||||||
nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(getSessionUUID())) );
|
nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(getSessionUUID())) );
|
||||||
QCryptographicHash nonceHash(QCryptographicHash::Sha256);
|
QCryptographicHash nonceHash(QCryptographicHash::Sha256);
|
||||||
nonceHash.addData(nonce);
|
nonceHash.addData(_challengeNonce);
|
||||||
_challengeNonceHash = nonceHash.result();
|
_challengeNonceHash = nonceHash.result();
|
||||||
_pendingEvent = false;
|
_pendingEvent = false;
|
||||||
|
|
||||||
// QTimer::start is a set of overloaded functions.
|
emit startChallengeTimer();
|
||||||
QMetaObject::invokeMethod(&_challengeTimer, static_cast<void(QTimer::*)()>(&QTimer::start));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
||||||
|
@ -337,7 +376,7 @@ void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
||||||
QByteArray responseData = response.readAll();
|
QByteArray responseData = response.readAll();
|
||||||
if (responseData.length() < 8) {
|
if (responseData.length() < 8) {
|
||||||
_verifyState = error;
|
_verifyState = error;
|
||||||
qCDebug(avatars) << "Avatar challenge response packet too small, length:" << responseData.length();
|
qCWarning(avatars) << "ALERT: Avatar challenge response packet too small, length:" << responseData.length();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,11 +393,14 @@ void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
||||||
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash,
|
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash,
|
||||||
QByteArray::fromBase64(signedNonce));
|
QByteArray::fromBase64(signedNonce));
|
||||||
_verifyState = challengeResult ? verificationSucceeded : verificationFailed;
|
_verifyState = challengeResult ? verificationSucceeded : verificationFailed;
|
||||||
|
_certifyFailed = !challengeResult;
|
||||||
_needsIdentityUpdate = true;
|
_needsIdentityUpdate = true;
|
||||||
if (_verifyState == verificationFailed) {
|
if (_certifyFailed) {
|
||||||
qCDebug(avatars) << "Dynamic verification FAILED for" << getDisplayName() << getSessionUUID();
|
qCDebug(avatars) << "Dynamic verification FAILED for" << getDisplayName() << getSessionUUID();
|
||||||
|
emit startChallengeTimer();
|
||||||
} else {
|
} else {
|
||||||
qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID();
|
qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID();
|
||||||
|
_challengeNonce.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -27,7 +27,7 @@ public:
|
||||||
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
|
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
|
||||||
|
|
||||||
void fetchAvatarFST();
|
void fetchAvatarFST();
|
||||||
virtual bool isCertifyFailed() const override { return _verifyState == verificationFailed; }
|
virtual bool isCertifyFailed() const override { return _certifyFailed; }
|
||||||
bool needsIdentityUpdate() const { return _needsIdentityUpdate; }
|
bool needsIdentityUpdate() const { return _needsIdentityUpdate; }
|
||||||
void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; }
|
void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; }
|
||||||
|
|
||||||
|
@ -58,13 +58,18 @@ private:
|
||||||
QString _certificateIdFromFST;
|
QString _certificateIdFromFST;
|
||||||
QString _dynamicMarketResponse;
|
QString _dynamicMarketResponse;
|
||||||
QString _ownerPublicKey;
|
QString _ownerPublicKey;
|
||||||
|
QByteArray _challengeNonce;
|
||||||
QByteArray _challengeNonceHash;
|
QByteArray _challengeNonceHash;
|
||||||
QTimer _challengeTimer;
|
QTimer _challengeTimer;
|
||||||
|
static constexpr int NUM_CHALLENGES_BEFORE_FAIL = 1;
|
||||||
|
int _numberChallenges { 0 };
|
||||||
|
bool _certifyFailed { false };
|
||||||
bool _needsIdentityUpdate { false };
|
bool _needsIdentityUpdate { false };
|
||||||
|
|
||||||
bool generateFSTHash();
|
bool generateFSTHash();
|
||||||
bool validateFSTHash(const QString& publicKey) const;
|
bool validateFSTHash(const QString& publicKey) const;
|
||||||
QByteArray canonicalJson(const QString fstFile);
|
QByteArray canonicalJson(const QString fstFile);
|
||||||
|
void requestCurrentOwnership();
|
||||||
void sendOwnerChallenge();
|
void sendOwnerChallenge();
|
||||||
|
|
||||||
static const QString VERIFY_FAIL_MODEL;
|
static const QString VERIFY_FAIL_MODEL;
|
||||||
|
@ -72,6 +77,10 @@ private:
|
||||||
private slots:
|
private slots:
|
||||||
void fstRequestComplete();
|
void fstRequestComplete();
|
||||||
void ownerRequestComplete();
|
void ownerRequestComplete();
|
||||||
|
void challengeTimeout();
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void startChallengeTimer();
|
||||||
};
|
};
|
||||||
|
|
||||||
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;
|
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;
|
||||||
|
|
|
@ -279,18 +279,6 @@ void ScriptableAvatar::setJointMappingsFromNetworkReply() {
|
||||||
networkReply->deleteLater();
|
networkReply->deleteLater();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ScriptableAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) {
|
|
||||||
_headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptableAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) {
|
|
||||||
_headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ScriptableAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) {
|
|
||||||
_headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement);
|
|
||||||
}
|
|
||||||
|
|
||||||
AvatarEntityMap ScriptableAvatar::getAvatarEntityData() const {
|
AvatarEntityMap ScriptableAvatar::getAvatarEntityData() const {
|
||||||
// DANGER: Now that we store the AvatarEntityData in packed format this call is potentially Very Expensive!
|
// DANGER: Now that we store the AvatarEntityData in packed format this call is potentially Very Expensive!
|
||||||
// Avoid calling this method if possible.
|
// Avoid calling this method if possible.
|
||||||
|
|
|
@ -153,13 +153,6 @@ public:
|
||||||
|
|
||||||
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override;
|
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override;
|
||||||
|
|
||||||
void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement);
|
|
||||||
bool getHasProceduralBlinkFaceMovement() const override { return _headData->getHasProceduralBlinkFaceMovement(); }
|
|
||||||
void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement);
|
|
||||||
bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); }
|
|
||||||
void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement);
|
|
||||||
bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); }
|
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Gets details of all avatar entities.
|
* Gets details of all avatar entities.
|
||||||
* <p><strong>Warning:</strong> Potentially an expensive call. Do not use if possible.</p>
|
* <p><strong>Warning:</strong> Potentially an expensive call. Do not use if possible.</p>
|
||||||
|
|
|
@ -273,8 +273,10 @@ endif()
|
||||||
url = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-18.04.tar.gz'
|
url = 'https://hifi-public.s3.amazonaws.com/dependencies/vcpkg/qt5-install-5.12.3-ubuntu-18.04.tar.gz'
|
||||||
else:
|
else:
|
||||||
print('UNKNOWN LINUX VERSION!!!')
|
print('UNKNOWN LINUX VERSION!!!')
|
||||||
|
return;
|
||||||
else:
|
else:
|
||||||
print('UNKNOWN OPERATING SYSTEM!!!')
|
print('UNKNOWN OPERATING SYSTEM!!!')
|
||||||
|
return;
|
||||||
|
|
||||||
print('Extracting ' + url + ' to ' + dest)
|
print('Extracting ' + url + ' to ' + dest)
|
||||||
hifi_utils.downloadAndExtract(url, dest)
|
hifi_utils.downloadAndExtract(url, dest)
|
||||||
|
|
|
@ -211,10 +211,10 @@ endif()
|
||||||
link_hifi_libraries(
|
link_hifi_libraries(
|
||||||
shared workload task octree ktx gpu gl procedural graphics graphics-scripting render
|
shared workload task octree ktx gpu gl procedural graphics graphics-scripting render
|
||||||
pointers recording hfm fbx networking material-networking
|
pointers recording hfm fbx networking material-networking
|
||||||
model-networking model-baker entities avatars trackers
|
model-networking model-baker entities avatars
|
||||||
audio audio-client animation script-engine physics
|
audio audio-client animation script-engine physics
|
||||||
render-utils entities-renderer avatars-renderer ui qml auto-updater midi
|
render-utils entities-renderer avatars-renderer ui qml auto-updater midi
|
||||||
controllers plugins image trackers platform
|
controllers plugins image platform
|
||||||
ui-plugins display-plugins input-plugins
|
ui-plugins display-plugins input-plugins
|
||||||
# Platform specific GL libraries
|
# Platform specific GL libraries
|
||||||
${PLATFORM_GL_BACKEND}
|
${PLATFORM_GL_BACKEND}
|
||||||
|
|
|
@ -591,6 +591,8 @@
|
||||||
{
|
{
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
"children": [
|
"children": [
|
||||||
|
@ -2369,9 +2371,185 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"id": "seated",
|
"id": "seatedSM",
|
||||||
"type": "stateMachine"
|
"type": "stateMachine"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"baseFrame": 1,
|
||||||
|
"baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx",
|
||||||
|
"blendType": "addAbsolute",
|
||||||
|
"endFrame": 11,
|
||||||
|
"loopFlag": true,
|
||||||
|
"startFrame": 11,
|
||||||
|
"timeScale": 1,
|
||||||
|
"url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx"
|
||||||
|
},
|
||||||
|
"id": "seatedLookLeft",
|
||||||
|
"type": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"baseFrame": 1,
|
||||||
|
"baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx",
|
||||||
|
"blendType": "addAbsolute",
|
||||||
|
"endFrame": 30,
|
||||||
|
"loopFlag": true,
|
||||||
|
"startFrame": 30,
|
||||||
|
"timeScale": 1,
|
||||||
|
"url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx"
|
||||||
|
},
|
||||||
|
"id": "seatedLookRight",
|
||||||
|
"type": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"baseFrame": 1,
|
||||||
|
"baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx",
|
||||||
|
"blendType": "addAbsolute",
|
||||||
|
"endFrame": 50,
|
||||||
|
"loopFlag": true,
|
||||||
|
"startFrame": 50,
|
||||||
|
"timeScale": 1,
|
||||||
|
"url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx"
|
||||||
|
},
|
||||||
|
"id": "seatedLookUp",
|
||||||
|
"type": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"baseFrame": 1,
|
||||||
|
"baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx",
|
||||||
|
"blendType": "addAbsolute",
|
||||||
|
"endFrame": 70,
|
||||||
|
"loopFlag": true,
|
||||||
|
"startFrame": 70,
|
||||||
|
"timeScale": 1,
|
||||||
|
"url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx"
|
||||||
|
},
|
||||||
|
"id": "seatedLookDown",
|
||||||
|
"type": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"baseFrame": 1,
|
||||||
|
"baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx",
|
||||||
|
"blendType": "addAbsolute",
|
||||||
|
"endFrame": 97,
|
||||||
|
"loopFlag": true,
|
||||||
|
"startFrame": 97,
|
||||||
|
"timeScale": 1,
|
||||||
|
"url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx"
|
||||||
|
},
|
||||||
|
"id": "seatedLookUpLeft",
|
||||||
|
"type": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"baseFrame": 1,
|
||||||
|
"baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx",
|
||||||
|
"blendType": "addAbsolute",
|
||||||
|
"endFrame": 110,
|
||||||
|
"loopFlag": true,
|
||||||
|
"startFrame": 110,
|
||||||
|
"timeScale": 1,
|
||||||
|
"url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx"
|
||||||
|
},
|
||||||
|
"id": "seatedLookUpRight",
|
||||||
|
"type": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"baseFrame": 1,
|
||||||
|
"baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx",
|
||||||
|
"blendType": "addAbsolute",
|
||||||
|
"endFrame": 130,
|
||||||
|
"loopFlag": true,
|
||||||
|
"startFrame": 130,
|
||||||
|
"timeScale": 1,
|
||||||
|
"url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx"
|
||||||
|
},
|
||||||
|
"id": "seatedLookDownLeft",
|
||||||
|
"type": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"baseFrame": 1,
|
||||||
|
"baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx",
|
||||||
|
"blendType": "addAbsolute",
|
||||||
|
"endFrame": 150,
|
||||||
|
"loopFlag": true,
|
||||||
|
"startFrame": 150,
|
||||||
|
"timeScale": 1,
|
||||||
|
"url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx"
|
||||||
|
},
|
||||||
|
"id": "seatedLookDownRight",
|
||||||
|
"type": "clip"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"children": [
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"baseFrame": 1,
|
||||||
|
"baseURL": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx",
|
||||||
|
"blendType": "addAbsolute",
|
||||||
|
"endFrame": 3,
|
||||||
|
"loopFlag": true,
|
||||||
|
"startFrame": 3,
|
||||||
|
"timeScale": 1,
|
||||||
|
"url": "qrc:///avatar/animations/sitting_idle_aimoffsets.fbx"
|
||||||
|
},
|
||||||
|
"id": "seatedLookCenter",
|
||||||
|
"type": "clip"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"alpha": [
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0
|
||||||
|
],
|
||||||
|
"alphaVar": "lookAroundAlpha",
|
||||||
|
"centerId": "seatedLookCenter",
|
||||||
|
"downId": "seatedLookDown",
|
||||||
|
"downLeftId": "seatedLookDownLeft",
|
||||||
|
"downRightId": "seatedLookDownRight",
|
||||||
|
"leftId": "seatedLookLeft",
|
||||||
|
"rightId": "seatedLookRight",
|
||||||
|
"upId": "seatedLookUp",
|
||||||
|
"upLeftId": "seatedLookUpLeft",
|
||||||
|
"upRightId": "seatedLookUpRight"
|
||||||
|
},
|
||||||
|
"id": "seatedLookAroundBlend",
|
||||||
|
"type": "blendDirectional"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"data": {
|
||||||
|
"alpha": 0,
|
||||||
|
"alphaVar": "seatedLookBlendAlpha",
|
||||||
|
"blendType": "addAbsolute"
|
||||||
|
},
|
||||||
|
"id": "seated",
|
||||||
|
"type": "blendLinear"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"children": [
|
"children": [
|
||||||
{
|
{
|
||||||
|
|
|
@ -166,9 +166,70 @@
|
||||||
{ "from": "Standard.LeftEye", "to": "Actions.LeftEye" },
|
{ "from": "Standard.LeftEye", "to": "Actions.LeftEye" },
|
||||||
{ "from": "Standard.RightEye", "to": "Actions.RightEye" },
|
{ "from": "Standard.RightEye", "to": "Actions.RightEye" },
|
||||||
|
|
||||||
{ "from": "Standard.LeftEyeBlink", "to": "Actions.LeftEyeBlink" },
|
{ "from": "Standard.EyeBlink_L", "to": "Actions.EyeBlink_L" },
|
||||||
{ "from": "Standard.RightEyeBlink", "to": "Actions.RightEyeBlink" },
|
{ "from": "Standard.EyeBlink_R", "to": "Actions.EyeBlink_R" },
|
||||||
|
{ "from": "Standard.EyeSquint_L", "to": "Actions.EyeSquint_L" },
|
||||||
|
{ "from": "Standard.EyeSquint_R", "to": "Actions.EyeSquint_R" },
|
||||||
|
{ "from": "Standard.EyeDown_L", "to": "Actions.EyeDown_L" },
|
||||||
|
{ "from": "Standard.EyeDown_R", "to": "Actions.EyeDown_R" },
|
||||||
|
{ "from": "Standard.EyeIn_L", "to": "Actions.EyeIn_L" },
|
||||||
|
{ "from": "Standard.EyeIn_R", "to": "Actions.EyeIn_R" },
|
||||||
|
{ "from": "Standard.EyeOpen_L", "to": "Actions.EyeOpen_L" },
|
||||||
|
{ "from": "Standard.EyeOpen_R", "to": "Actions.EyeOpen_R" },
|
||||||
|
{ "from": "Standard.EyeOut_L", "to": "Actions.EyeOut_L" },
|
||||||
|
{ "from": "Standard.EyeOut_R", "to": "Actions.EyeOut_R" },
|
||||||
|
{ "from": "Standard.EyeUp_L", "to": "Actions.EyeUp_L" },
|
||||||
|
{ "from": "Standard.EyeUp_R", "to": "Actions.EyeUp_R" },
|
||||||
|
{ "from": "Standard.BrowsD_L", "to": "Actions.BrowsD_L" },
|
||||||
|
{ "from": "Standard.BrowsD_R", "to": "Actions.BrowsD_R" },
|
||||||
|
{ "from": "Standard.BrowsU_C", "to": "Actions.BrowsU_C" },
|
||||||
|
{ "from": "Standard.BrowsU_L", "to": "Actions.BrowsU_L" },
|
||||||
|
{ "from": "Standard.BrowsU_R", "to": "Actions.BrowsU_R" },
|
||||||
|
{ "from": "Standard.JawFwd", "to": "Actions.JawFwd" },
|
||||||
|
{ "from": "Standard.JawLeft", "to": "Actions.JawLeft" },
|
||||||
|
{ "from": "Standard.JawOpen", "to": "Actions.JawOpen" },
|
||||||
|
{ "from": "Standard.JawRight", "to": "Actions.JawRight" },
|
||||||
|
{ "from": "Standard.MouthLeft", "to": "Actions.MouthLeft" },
|
||||||
|
{ "from": "Standard.MouthRight", "to": "Actions.MouthRight" },
|
||||||
|
{ "from": "Standard.MouthFrown_L", "to": "Actions.MouthFrown_L" },
|
||||||
|
{ "from": "Standard.MouthFrown_R", "to": "Actions.MouthFrown_R" },
|
||||||
|
{ "from": "Standard.MouthSmile_L", "to": "Actions.MouthSmile_L" },
|
||||||
|
{ "from": "Standard.MouthSmile_R", "to": "Actions.MouthSmile_R" },
|
||||||
|
{ "from": "Standard.MouthDimple_L", "to": "Actions.MouthDimple_L" },
|
||||||
|
{ "from": "Standard.MouthDimple_R", "to": "Actions.MouthDimple_R" },
|
||||||
|
{ "from": "Standard.LipsStretch_L", "to": "Actions.LipsStretch_L" },
|
||||||
|
{ "from": "Standard.LipsStretch_R", "to": "Actions.LipsStretch_R" },
|
||||||
|
{ "from": "Standard.LipsUpperClose", "to": "Actions.LipsUpperClose" },
|
||||||
|
{ "from": "Standard.LipsLowerClose", "to": "Actions.LipsLowerClose" },
|
||||||
|
{ "from": "Standard.LipsUpperOpen", "to": "Actions.LipsUpperOpen" },
|
||||||
|
{ "from": "Standard.LipsLowerOpen", "to": "Actions.LipsLowerOpen" },
|
||||||
|
{ "from": "Standard.LipsFunnel", "to": "Actions.LipsFunnel" },
|
||||||
|
{ "from": "Standard.LipsPucker", "to": "Actions.LipsPucker" },
|
||||||
|
{ "from": "Standard.Puff", "to": "Actions.Puff" },
|
||||||
|
{ "from": "Standard.CheekSquint_L", "to": "Actions.CheekSquint_L" },
|
||||||
|
{ "from": "Standard.CheekSquint_R", "to": "Actions.CheekSquint_R" },
|
||||||
|
{ "from": "Standard.MouthClose", "to": "Actions.MouthClose" },
|
||||||
|
{ "from": "Standard.MouthUpperUp_L", "to": "Actions.MouthUpperUp_L" },
|
||||||
|
{ "from": "Standard.MouthUpperUp_R", "to": "Actions.MouthUpperUp_R" },
|
||||||
|
{ "from": "Standard.MouthLowerDown_L", "to": "Actions.MouthLowerDown_L" },
|
||||||
|
{ "from": "Standard.MouthLowerDown_R", "to": "Actions.MouthLowerDown_R" },
|
||||||
|
{ "from": "Standard.MouthPress_L", "to": "Actions.MouthPress_L" },
|
||||||
|
{ "from": "Standard.MouthPress_R", "to": "Actions.MouthPress_R" },
|
||||||
|
{ "from": "Standard.MouthShrugLower", "to": "Actions.MouthShrugLower" },
|
||||||
|
{ "from": "Standard.MouthShrugUpper", "to": "Actions.MouthShrugUpper" },
|
||||||
|
{ "from": "Standard.NoseSneer_L", "to": "Actions.NoseSneer_L" },
|
||||||
|
{ "from": "Standard.NoseSneer_R", "to": "Actions.NoseSneer_R" },
|
||||||
|
{ "from": "Standard.TongueOut", "to": "Actions.TongueOut" },
|
||||||
|
{ "from": "Standard.UserBlendshape0", "to": "Actions.UserBlendshape0" },
|
||||||
|
{ "from": "Standard.UserBlendshape1", "to": "Actions.UserBlendshape1" },
|
||||||
|
{ "from": "Standard.UserBlendshape2", "to": "Actions.UserBlendshape2" },
|
||||||
|
{ "from": "Standard.UserBlendshape3", "to": "Actions.UserBlendshape3" },
|
||||||
|
{ "from": "Standard.UserBlendshape4", "to": "Actions.UserBlendshape4" },
|
||||||
|
{ "from": "Standard.UserBlendshape5", "to": "Actions.UserBlendshape5" },
|
||||||
|
{ "from": "Standard.UserBlendshape6", "to": "Actions.UserBlendshape6" },
|
||||||
|
{ "from": "Standard.UserBlendshape7", "to": "Actions.UserBlendshape7" },
|
||||||
|
{ "from": "Standard.UserBlendshape8", "to": "Actions.UserBlendshape8" },
|
||||||
|
{ "from": "Standard.UserBlendshape9", "to": "Actions.UserBlendshape9" },
|
||||||
|
|
||||||
{ "from": "Standard.TrackedObject00", "to" : "Actions.TrackedObject00" },
|
{ "from": "Standard.TrackedObject00", "to" : "Actions.TrackedObject00" },
|
||||||
{ "from": "Standard.TrackedObject01", "to" : "Actions.TrackedObject01" },
|
{ "from": "Standard.TrackedObject01", "to" : "Actions.TrackedObject01" },
|
||||||
|
|
|
@ -61,8 +61,70 @@
|
||||||
{ "from": "Standard.LeftEye", "to": "Actions.LeftEye" },
|
{ "from": "Standard.LeftEye", "to": "Actions.LeftEye" },
|
||||||
{ "from": "Standard.RightEye", "to": "Actions.RightEye" },
|
{ "from": "Standard.RightEye", "to": "Actions.RightEye" },
|
||||||
|
|
||||||
{ "from": "Standard.LeftEyeBlink", "to": "Actions.LeftEyeBlink" },
|
{ "from": "Standard.EyeBlink_L", "to": "Actions.EyeBlink_L" },
|
||||||
{ "from": "Standard.RightEyeBlink", "to": "Actions.RightEyeBlink" },
|
{ "from": "Standard.EyeBlink_R", "to": "Actions.EyeBlink_R" },
|
||||||
|
{ "from": "Standard.EyeSquint_L", "to": "Actions.EyeSquint_L" },
|
||||||
|
{ "from": "Standard.EyeSquint_R", "to": "Actions.EyeSquint_R" },
|
||||||
|
{ "from": "Standard.EyeDown_L", "to": "Actions.EyeDown_L" },
|
||||||
|
{ "from": "Standard.EyeDown_R", "to": "Actions.EyeDown_R" },
|
||||||
|
{ "from": "Standard.EyeIn_L", "to": "Actions.EyeIn_L" },
|
||||||
|
{ "from": "Standard.EyeIn_R", "to": "Actions.EyeIn_R" },
|
||||||
|
{ "from": "Standard.EyeOpen_L", "to": "Actions.EyeOpen_L" },
|
||||||
|
{ "from": "Standard.EyeOpen_R", "to": "Actions.EyeOpen_R" },
|
||||||
|
{ "from": "Standard.EyeOut_L", "to": "Actions.EyeOut_L" },
|
||||||
|
{ "from": "Standard.EyeOut_R", "to": "Actions.EyeOut_R" },
|
||||||
|
{ "from": "Standard.EyeUp_L", "to": "Actions.EyeUp_L" },
|
||||||
|
{ "from": "Standard.EyeUp_R", "to": "Actions.EyeUp_R" },
|
||||||
|
{ "from": "Standard.BrowsD_L", "to": "Actions.BrowsD_L" },
|
||||||
|
{ "from": "Standard.BrowsD_R", "to": "Actions.BrowsD_R" },
|
||||||
|
{ "from": "Standard.BrowsU_C", "to": "Actions.BrowsU_C" },
|
||||||
|
{ "from": "Standard.BrowsU_L", "to": "Actions.BrowsU_L" },
|
||||||
|
{ "from": "Standard.BrowsU_R", "to": "Actions.BrowsU_R" },
|
||||||
|
{ "from": "Standard.JawFwd", "to": "Actions.JawFwd" },
|
||||||
|
{ "from": "Standard.JawLeft", "to": "Actions.JawLeft" },
|
||||||
|
{ "from": "Standard.JawOpen", "to": "Actions.JawOpen" },
|
||||||
|
{ "from": "Standard.JawRight", "to": "Actions.JawRight" },
|
||||||
|
{ "from": "Standard.MouthLeft", "to": "Actions.MouthLeft" },
|
||||||
|
{ "from": "Standard.MouthRight", "to": "Actions.MouthRight" },
|
||||||
|
{ "from": "Standard.MouthFrown_L", "to": "Actions.MouthFrown_L" },
|
||||||
|
{ "from": "Standard.MouthFrown_R", "to": "Actions.MouthFrown_R" },
|
||||||
|
{ "from": "Standard.MouthSmile_L", "to": "Actions.MouthSmile_L" },
|
||||||
|
{ "from": "Standard.MouthSmile_R", "to": "Actions.MouthSmile_R" },
|
||||||
|
{ "from": "Standard.MouthDimple_L", "to": "Actions.MouthDimple_L" },
|
||||||
|
{ "from": "Standard.MouthDimple_R", "to": "Actions.MouthDimple_R" },
|
||||||
|
{ "from": "Standard.LipsStretch_L", "to": "Actions.LipsStretch_L" },
|
||||||
|
{ "from": "Standard.LipsStretch_R", "to": "Actions.LipsStretch_R" },
|
||||||
|
{ "from": "Standard.LipsUpperClose", "to": "Actions.LipsUpperClose" },
|
||||||
|
{ "from": "Standard.LipsLowerClose", "to": "Actions.LipsLowerClose" },
|
||||||
|
{ "from": "Standard.LipsUpperOpen", "to": "Actions.LipsUpperOpen" },
|
||||||
|
{ "from": "Standard.LipsLowerOpen", "to": "Actions.LipsLowerOpen" },
|
||||||
|
{ "from": "Standard.LipsFunnel", "to": "Actions.LipsFunnel" },
|
||||||
|
{ "from": "Standard.LipsPucker", "to": "Actions.LipsPucker" },
|
||||||
|
{ "from": "Standard.Puff", "to": "Actions.Puff" },
|
||||||
|
{ "from": "Standard.CheekSquint_L", "to": "Actions.CheekSquint_L" },
|
||||||
|
{ "from": "Standard.CheekSquint_R", "to": "Actions.CheekSquint_R" },
|
||||||
|
{ "from": "Standard.MouthClose", "to": "Actions.MouthClose" },
|
||||||
|
{ "from": "Standard.MouthUpperUp_L", "to": "Actions.MouthUpperUp_L" },
|
||||||
|
{ "from": "Standard.MouthUpperUp_R", "to": "Actions.MouthUpperUp_R" },
|
||||||
|
{ "from": "Standard.MouthLowerDown_L", "to": "Actions.MouthLowerDown_L" },
|
||||||
|
{ "from": "Standard.MouthLowerDown_R", "to": "Actions.MouthLowerDown_R" },
|
||||||
|
{ "from": "Standard.MouthPress_L", "to": "Actions.MouthPress_L" },
|
||||||
|
{ "from": "Standard.MouthPress_R", "to": "Actions.MouthPress_R" },
|
||||||
|
{ "from": "Standard.MouthShrugLower", "to": "Actions.MouthShrugLower" },
|
||||||
|
{ "from": "Standard.MouthShrugUpper", "to": "Actions.MouthShrugUpper" },
|
||||||
|
{ "from": "Standard.NoseSneer_L", "to": "Actions.NoseSneer_L" },
|
||||||
|
{ "from": "Standard.NoseSneer_R", "to": "Actions.NoseSneer_R" },
|
||||||
|
{ "from": "Standard.TongueOut", "to": "Actions.TongueOut" },
|
||||||
|
{ "from": "Standard.UserBlendshape0", "to": "Actions.UserBlendshape0" },
|
||||||
|
{ "from": "Standard.UserBlendshape1", "to": "Actions.UserBlendshape1" },
|
||||||
|
{ "from": "Standard.UserBlendshape2", "to": "Actions.UserBlendshape2" },
|
||||||
|
{ "from": "Standard.UserBlendshape3", "to": "Actions.UserBlendshape3" },
|
||||||
|
{ "from": "Standard.UserBlendshape4", "to": "Actions.UserBlendshape4" },
|
||||||
|
{ "from": "Standard.UserBlendshape5", "to": "Actions.UserBlendshape5" },
|
||||||
|
{ "from": "Standard.UserBlendshape6", "to": "Actions.UserBlendshape6" },
|
||||||
|
{ "from": "Standard.UserBlendshape7", "to": "Actions.UserBlendshape7" },
|
||||||
|
{ "from": "Standard.UserBlendshape8", "to": "Actions.UserBlendshape8" },
|
||||||
|
{ "from": "Standard.UserBlendshape9", "to": "Actions.UserBlendshape9" },
|
||||||
|
|
||||||
{ "from": "Standard.TrackedObject00", "to" : "Actions.TrackedObject00" },
|
{ "from": "Standard.TrackedObject00", "to" : "Actions.TrackedObject00" },
|
||||||
{ "from": "Standard.TrackedObject01", "to" : "Actions.TrackedObject01" },
|
{ "from": "Standard.TrackedObject01", "to" : "Actions.TrackedObject01" },
|
||||||
|
|
|
@ -98,8 +98,9 @@
|
||||||
{ "from": "Vive.Head", "to" : "Standard.Head" },
|
{ "from": "Vive.Head", "to" : "Standard.Head" },
|
||||||
{ "from": "Vive.LeftEye", "to" : "Standard.LeftEye" },
|
{ "from": "Vive.LeftEye", "to" : "Standard.LeftEye" },
|
||||||
{ "from": "Vive.RightEye", "to" : "Standard.RightEye" },
|
{ "from": "Vive.RightEye", "to" : "Standard.RightEye" },
|
||||||
{ "from": "Vive.LeftEyeBlink", "to" : "Standard.LeftEyeBlink" },
|
|
||||||
{ "from": "Vive.RightEyeBlink", "to" : "Standard.RightEyeBlink" },
|
{ "from": "Vive.EyeBlink_L", "to" : "Standard.EyeBlink_L" },
|
||||||
|
{ "from": "Vive.EyeBlink_R", "to" : "Standard.EyeBlink_R" },
|
||||||
|
|
||||||
{
|
{
|
||||||
"from": "Vive.LeftFoot", "to" : "Standard.LeftFoot",
|
"from": "Vive.LeftFoot", "to" : "Standard.LeftFoot",
|
||||||
|
|
|
@ -66,6 +66,15 @@ Windows.Window {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: timer
|
||||||
|
interval: 500;
|
||||||
|
repeat: false;
|
||||||
|
onTriggered: {
|
||||||
|
updateContentParent();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function updateInteractiveWindowPositionForMode() {
|
function updateInteractiveWindowPositionForMode() {
|
||||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||||
x = interactiveWindowPosition.x;
|
x = interactiveWindowPosition.x;
|
||||||
|
@ -107,13 +116,11 @@ Windows.Window {
|
||||||
if (nativeWindow) {
|
if (nativeWindow) {
|
||||||
nativeWindow.setVisible(false);
|
nativeWindow.setVisible(false);
|
||||||
}
|
}
|
||||||
updateContentParent();
|
|
||||||
updateInteractiveWindowPositionForMode();
|
updateInteractiveWindowPositionForMode();
|
||||||
shown = interactiveWindowVisible;
|
shown = interactiveWindowVisible;
|
||||||
} else if (presentationMode === Desktop.PresentationMode.NATIVE) {
|
} else if (presentationMode === Desktop.PresentationMode.NATIVE) {
|
||||||
shown = false;
|
shown = false;
|
||||||
if (nativeWindow) {
|
if (nativeWindow) {
|
||||||
updateContentParent();
|
|
||||||
updateInteractiveWindowPositionForMode();
|
updateInteractiveWindowPositionForMode();
|
||||||
nativeWindow.setVisible(interactiveWindowVisible);
|
nativeWindow.setVisible(interactiveWindowVisible);
|
||||||
}
|
}
|
||||||
|
@ -123,9 +130,6 @@ Windows.Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
// Fix for parent loss on OSX:
|
|
||||||
parent.heightChanged.connect(updateContentParent);
|
|
||||||
parent.widthChanged.connect(updateContentParent);
|
|
||||||
|
|
||||||
x = interactiveWindowPosition.x;
|
x = interactiveWindowPosition.x;
|
||||||
y = interactiveWindowPosition.y;
|
y = interactiveWindowPosition.y;
|
||||||
|
@ -140,6 +144,11 @@ Windows.Window {
|
||||||
id: root;
|
id: root;
|
||||||
width: interactiveWindowSize.width
|
width: interactiveWindowSize.width
|
||||||
height: interactiveWindowSize.height
|
height: interactiveWindowSize.height
|
||||||
|
// fix for missing content on OSX initial startup with a non-maximized interface window. It seems that in this case, we cannot update
|
||||||
|
// the content parent during creation of the Window root. This added delay will update the parent after the root has finished loading.
|
||||||
|
Component.onCompleted: {
|
||||||
|
timer.start();
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
color: hifi.colors.baseGray
|
color: hifi.colors.baseGray
|
||||||
|
@ -170,6 +179,7 @@ Windows.Window {
|
||||||
interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y);
|
interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
nativeWindow.yChanged.connect(function() {
|
nativeWindow.yChanged.connect(function() {
|
||||||
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
|
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
|
||||||
interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y);
|
interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y);
|
||||||
|
@ -181,6 +191,7 @@ Windows.Window {
|
||||||
interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height);
|
interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
nativeWindow.heightChanged.connect(function() {
|
nativeWindow.heightChanged.connect(function() {
|
||||||
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
|
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
|
||||||
interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height);
|
interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height);
|
||||||
|
@ -194,15 +205,11 @@ Windows.Window {
|
||||||
|
|
||||||
// finally set the initial window mode:
|
// finally set the initial window mode:
|
||||||
setupPresentationMode();
|
setupPresentationMode();
|
||||||
|
updateContentParent();
|
||||||
|
|
||||||
initialized = true;
|
initialized = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
|
||||||
parent.heightChanged.disconnect(updateContentParent);
|
|
||||||
parent.widthChanged.disconnect(updateContentParent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle message traffic from the script that launched us to the loaded QML
|
// Handle message traffic from the script that launched us to the loaded QML
|
||||||
function fromScript(message) {
|
function fromScript(message) {
|
||||||
if (root.dynamicContent && root.dynamicContent.fromScript) {
|
if (root.dynamicContent && root.dynamicContent.fromScript) {
|
||||||
|
@ -232,8 +239,8 @@ Windows.Window {
|
||||||
interactiveWindowSize.width = newWidth;
|
interactiveWindowSize.width = newWidth;
|
||||||
updateInteractiveWindowSizeForMode();
|
updateInteractiveWindowSizeForMode();
|
||||||
}
|
}
|
||||||
function onRequestNewHeight(newWidth) {
|
function onRequestNewHeight(newHeight) {
|
||||||
interactiveWindowSize.width = newWidth;
|
interactiveWindowSize.height = newHeight;
|
||||||
updateInteractiveWindowSizeForMode();
|
updateInteractiveWindowSizeForMode();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -308,6 +315,7 @@ Windows.Window {
|
||||||
onPresentationModeChanged: {
|
onPresentationModeChanged: {
|
||||||
if (initialized) {
|
if (initialized) {
|
||||||
setupPresentationMode();
|
setupPresentationMode();
|
||||||
|
updateContentParent();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -375,14 +375,14 @@ Rectangle {
|
||||||
x: margins.paddings
|
x: margins.paddings
|
||||||
interactive: false;
|
interactive: false;
|
||||||
height: contentHeight;
|
height: contentHeight;
|
||||||
spacing: 4;
|
|
||||||
clip: true;
|
clip: true;
|
||||||
model: AudioScriptingInterface.devices.input;
|
model: AudioScriptingInterface.devices.input;
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: rightMostInputLevelPos - margins.paddings*2
|
width: rightMostInputLevelPos - margins.paddings*2
|
||||||
height: margins.sizeCheckBox > checkBoxInput.implicitHeight ?
|
height: ((type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)) ?
|
||||||
margins.sizeCheckBox : checkBoxInput.implicitHeight
|
(margins.sizeCheckBox > checkBoxInput.implicitHeight ? margins.sizeCheckBox + 4 : checkBoxInput.implicitHeight + 4) : 0
|
||||||
|
visible: (type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)
|
||||||
AudioControls.CheckBox {
|
AudioControls.CheckBox {
|
||||||
id: checkBoxInput
|
id: checkBoxInput
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
@ -470,13 +470,13 @@ Rectangle {
|
||||||
height: contentHeight;
|
height: contentHeight;
|
||||||
anchors.top: outputDeviceHeader.bottom;
|
anchors.top: outputDeviceHeader.bottom;
|
||||||
anchors.topMargin: 10;
|
anchors.topMargin: 10;
|
||||||
spacing: 4;
|
|
||||||
clip: true;
|
clip: true;
|
||||||
model: AudioScriptingInterface.devices.output;
|
model: AudioScriptingInterface.devices.output;
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: rightMostInputLevelPos
|
width: rightMostInputLevelPos
|
||||||
height: margins.sizeCheckBox > checkBoxOutput.implicitHeight ?
|
height: ((type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)) ?
|
||||||
margins.sizeCheckBox : checkBoxOutput.implicitHeight
|
(margins.sizeCheckBox > checkBoxOutput.implicitHeight ? margins.sizeCheckBox + 4 : checkBoxOutput.implicitHeight + 4) : 0
|
||||||
|
visible: (type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)
|
||||||
|
|
||||||
AudioControls.CheckBox {
|
AudioControls.CheckBox {
|
||||||
id: checkBoxOutput
|
id: checkBoxOutput
|
||||||
|
|
|
@ -133,15 +133,12 @@ Item {
|
||||||
|
|
||||||
ListElement {
|
ListElement {
|
||||||
text: "Low World Detail"
|
text: "Low World Detail"
|
||||||
worldDetailQualityValue: 0.25
|
|
||||||
}
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
text: "Medium World Detail"
|
text: "Medium World Detail"
|
||||||
worldDetailQualityValue: 0.5
|
|
||||||
}
|
}
|
||||||
ListElement {
|
ListElement {
|
||||||
text: "Full World Detail"
|
text: "Full World Detail"
|
||||||
worldDetailQualityValue: 0.75
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,14 +155,7 @@ Item {
|
||||||
currentIndex: -1
|
currentIndex: -1
|
||||||
|
|
||||||
function refreshWorldDetailDropdown() {
|
function refreshWorldDetailDropdown() {
|
||||||
var currentWorldDetailQuality = LODManager.worldDetailQuality;
|
worldDetailDropdown.currentIndex = LODManager.worldDetailQuality;
|
||||||
if (currentWorldDetailQuality <= 0.25) {
|
|
||||||
worldDetailDropdown.currentIndex = 0;
|
|
||||||
} else if (currentWorldDetailQuality <= 0.5) {
|
|
||||||
worldDetailDropdown.currentIndex = 1;
|
|
||||||
} else {
|
|
||||||
worldDetailDropdown.currentIndex = 2;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onCompleted: {
|
Component.onCompleted: {
|
||||||
|
@ -173,7 +163,7 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
onCurrentIndexChanged: {
|
onCurrentIndexChanged: {
|
||||||
LODManager.worldDetailQuality = model.get(currentIndex).worldDetailQualityValue;
|
LODManager.worldDetailQuality = currentIndex;
|
||||||
worldDetailDropdown.displayText = model.get(currentIndex).text;
|
worldDetailDropdown.displayText = model.get(currentIndex).text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -258,13 +258,12 @@ Flickable {
|
||||||
Layout.preferredHeight: contentItem.height
|
Layout.preferredHeight: contentItem.height
|
||||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||||
interactive: false
|
interactive: false
|
||||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
|
||||||
clip: true
|
clip: true
|
||||||
model: AudioScriptingInterface.devices.input
|
model: AudioScriptingInterface.devices.input
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: inputDeviceCheckbox.height
|
height: model.type != "hmd" ? inputDeviceCheckbox.height + simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0
|
||||||
|
visible: model.type != "hmd"
|
||||||
SimplifiedControls.RadioButton {
|
SimplifiedControls.RadioButton {
|
||||||
id: inputDeviceCheckbox
|
id: inputDeviceCheckbox
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
@ -354,13 +353,12 @@ Flickable {
|
||||||
Layout.preferredHeight: contentItem.height
|
Layout.preferredHeight: contentItem.height
|
||||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||||
interactive: false
|
interactive: false
|
||||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
|
||||||
clip: true
|
clip: true
|
||||||
model: AudioScriptingInterface.devices.output
|
model: AudioScriptingInterface.devices.output
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: outputDeviceCheckbox.height
|
height: model.type != "hmd" ? outputDeviceCheckbox.height +simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0
|
||||||
|
visible: model.type != "hmd"
|
||||||
SimplifiedControls.RadioButton {
|
SimplifiedControls.RadioButton {
|
||||||
id: outputDeviceCheckbox
|
id: outputDeviceCheckbox
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
|
@ -259,12 +259,12 @@ Flickable {
|
||||||
Layout.preferredHeight: contentItem.height
|
Layout.preferredHeight: contentItem.height
|
||||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||||
interactive: false
|
interactive: false
|
||||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
|
||||||
clip: true
|
clip: true
|
||||||
model: AudioScriptingInterface.devices.input
|
model: AudioScriptingInterface.devices.input
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: inputDeviceCheckbox.height
|
height: model.type != "desktop" ? inputDeviceCheckbox.height + simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0
|
||||||
|
visible: model.type != "desktop"
|
||||||
|
|
||||||
SimplifiedControls.RadioButton {
|
SimplifiedControls.RadioButton {
|
||||||
id: inputDeviceCheckbox
|
id: inputDeviceCheckbox
|
||||||
|
@ -355,13 +355,12 @@ Flickable {
|
||||||
Layout.preferredHeight: contentItem.height
|
Layout.preferredHeight: contentItem.height
|
||||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||||
interactive: false
|
interactive: false
|
||||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
|
||||||
clip: true
|
clip: true
|
||||||
model: AudioScriptingInterface.devices.output
|
model: AudioScriptingInterface.devices.output
|
||||||
delegate: Item {
|
delegate: Item {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: outputDeviceCheckbox.height
|
height: model.type != "desktop" ? outputDeviceCheckbox.height + simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0
|
||||||
|
visible: model.type != "desktop"
|
||||||
SimplifiedControls.RadioButton {
|
SimplifiedControls.RadioButton {
|
||||||
id: outputDeviceCheckbox
|
id: outputDeviceCheckbox
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
|
|
|
@ -171,7 +171,6 @@
|
||||||
#include "avatar/MyCharacterController.h"
|
#include "avatar/MyCharacterController.h"
|
||||||
#include "CrashRecoveryHandler.h"
|
#include "CrashRecoveryHandler.h"
|
||||||
#include "CrashHandler.h"
|
#include "CrashHandler.h"
|
||||||
#include "devices/DdeFaceTracker.h"
|
|
||||||
#include "DiscoverabilityManager.h"
|
#include "DiscoverabilityManager.h"
|
||||||
#include "GLCanvas.h"
|
#include "GLCanvas.h"
|
||||||
#include "InterfaceDynamicFactory.h"
|
#include "InterfaceDynamicFactory.h"
|
||||||
|
@ -889,11 +888,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
||||||
DependencyManager::set<ScriptCache>();
|
DependencyManager::set<ScriptCache>();
|
||||||
DependencyManager::set<SoundCache>();
|
DependencyManager::set<SoundCache>();
|
||||||
DependencyManager::set<SoundCacheScriptingInterface>();
|
DependencyManager::set<SoundCacheScriptingInterface>();
|
||||||
|
|
||||||
#ifdef HAVE_DDE
|
|
||||||
DependencyManager::set<DdeFaceTracker>();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
DependencyManager::set<AudioClient>();
|
DependencyManager::set<AudioClient>();
|
||||||
DependencyManager::set<AudioScope>();
|
DependencyManager::set<AudioScope>();
|
||||||
DependencyManager::set<DeferredLightingEffect>();
|
DependencyManager::set<DeferredLightingEffect>();
|
||||||
|
@ -1070,7 +1064,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
_lastSendDownstreamAudioStats(usecTimestampNow()),
|
_lastSendDownstreamAudioStats(usecTimestampNow()),
|
||||||
_notifiedPacketVersionMismatchThisDomain(false),
|
_notifiedPacketVersionMismatchThisDomain(false),
|
||||||
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
|
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
|
||||||
_lastFaceTrackerUpdate(0),
|
|
||||||
_snapshotSound(nullptr),
|
_snapshotSound(nullptr),
|
||||||
_sampleSound(nullptr)
|
_sampleSound(nullptr)
|
||||||
{
|
{
|
||||||
|
@ -2020,13 +2013,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
||||||
this->installEventFilter(this);
|
this->installEventFilter(this);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
#ifdef HAVE_DDE
|
|
||||||
auto ddeTracker = DependencyManager::get<DdeFaceTracker>();
|
|
||||||
ddeTracker->init();
|
|
||||||
connect(ddeTracker.data(), &FaceTracker::muteToggled, this, &Application::faceTrackerMuteToggled);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// If launched from Steam, let it handle updates
|
// If launched from Steam, let it handle updates
|
||||||
const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater";
|
const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater";
|
||||||
bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1;
|
bool noUpdater = arguments().indexOf(HIFI_NO_UPDATER_COMMAND_LINE_KEY) != -1;
|
||||||
|
@ -2770,9 +2756,6 @@ void Application::cleanupBeforeQuit() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Stop third party processes so that they're not left running in the event of a subsequent shutdown crash.
|
// Stop third party processes so that they're not left running in the event of a subsequent shutdown crash.
|
||||||
#ifdef HAVE_DDE
|
|
||||||
DependencyManager::get<DdeFaceTracker>()->setEnabled(false);
|
|
||||||
#endif
|
|
||||||
AnimDebugDraw::getInstance().shutdown();
|
AnimDebugDraw::getInstance().shutdown();
|
||||||
|
|
||||||
// FIXME: once we move to shared pointer for the INputDevice we shoud remove this naked delete:
|
// FIXME: once we move to shared pointer for the INputDevice we shoud remove this naked delete:
|
||||||
|
@ -2843,10 +2826,6 @@ void Application::cleanupBeforeQuit() {
|
||||||
_window->saveGeometry();
|
_window->saveGeometry();
|
||||||
|
|
||||||
// Destroy third party processes after scripts have finished using them.
|
// Destroy third party processes after scripts have finished using them.
|
||||||
#ifdef HAVE_DDE
|
|
||||||
DependencyManager::destroy<DdeFaceTracker>();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
DependencyManager::destroy<ContextOverlayInterface>(); // Must be destroyed before TabletScriptingInterface
|
DependencyManager::destroy<ContextOverlayInterface>(); // Must be destroyed before TabletScriptingInterface
|
||||||
|
|
||||||
// stop QML
|
// stop QML
|
||||||
|
@ -3481,9 +3460,6 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
|
||||||
surfaceContext->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
|
surfaceContext->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
|
||||||
|
|
||||||
surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface);
|
surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface);
|
||||||
#ifdef HAVE_DDE
|
|
||||||
surfaceContext->setContextProperty("FaceTracker", DependencyManager::get<DdeFaceTracker>().data());
|
|
||||||
#endif
|
|
||||||
surfaceContext->setContextProperty("AvatarManager", DependencyManager::get<AvatarManager>().data());
|
surfaceContext->setContextProperty("AvatarManager", DependencyManager::get<AvatarManager>().data());
|
||||||
surfaceContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
|
surfaceContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
|
||||||
surfaceContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
surfaceContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
||||||
|
@ -3750,16 +3726,6 @@ void Application::runTests() {
|
||||||
runUnitTests();
|
runUnitTests();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::faceTrackerMuteToggled() {
|
|
||||||
|
|
||||||
QAction* muteAction = Menu::getInstance()->getActionForOption(MenuOption::MuteFaceTracking);
|
|
||||||
Q_CHECK_PTR(muteAction);
|
|
||||||
bool isMuted = getSelectedFaceTracker()->isMuted();
|
|
||||||
muteAction->setChecked(isMuted);
|
|
||||||
getSelectedFaceTracker()->setEnabled(!isMuted);
|
|
||||||
Menu::getInstance()->getActionForOption(MenuOption::CalibrateCamera)->setEnabled(!isMuted);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::setFieldOfView(float fov) {
|
void Application::setFieldOfView(float fov) {
|
||||||
if (fov != _fieldOfView.get()) {
|
if (fov != _fieldOfView.get()) {
|
||||||
_fieldOfView.set(fov);
|
_fieldOfView.set(fov);
|
||||||
|
@ -5334,43 +5300,6 @@ ivec2 Application::getMouse() const {
|
||||||
return getApplicationCompositor().getReticlePosition();
|
return getApplicationCompositor().getReticlePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
FaceTracker* Application::getActiveFaceTracker() {
|
|
||||||
#ifdef HAVE_DDE
|
|
||||||
auto dde = DependencyManager::get<DdeFaceTracker>();
|
|
||||||
|
|
||||||
if (dde && dde->isActive()) {
|
|
||||||
return static_cast<FaceTracker*>(dde.data());
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
FaceTracker* Application::getSelectedFaceTracker() {
|
|
||||||
FaceTracker* faceTracker = nullptr;
|
|
||||||
#ifdef HAVE_DDE
|
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::UseCamera)) {
|
|
||||||
faceTracker = DependencyManager::get<DdeFaceTracker>().data();
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
return faceTracker;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::setActiveFaceTracker() const {
|
|
||||||
#ifdef HAVE_DDE
|
|
||||||
bool isMuted = Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking);
|
|
||||||
bool isUsingDDE = Menu::getInstance()->isOptionChecked(MenuOption::UseCamera);
|
|
||||||
Menu::getInstance()->getActionForOption(MenuOption::BinaryEyelidControl)->setVisible(isUsingDDE);
|
|
||||||
Menu::getInstance()->getActionForOption(MenuOption::CoupleEyelids)->setVisible(isUsingDDE);
|
|
||||||
Menu::getInstance()->getActionForOption(MenuOption::UseAudioForMouth)->setVisible(isUsingDDE);
|
|
||||||
Menu::getInstance()->getActionForOption(MenuOption::VelocityFilter)->setVisible(isUsingDDE);
|
|
||||||
Menu::getInstance()->getActionForOption(MenuOption::CalibrateCamera)->setVisible(isUsingDDE);
|
|
||||||
auto ddeTracker = DependencyManager::get<DdeFaceTracker>();
|
|
||||||
ddeTracker->setIsMuted(isMuted);
|
|
||||||
ddeTracker->setEnabled(isUsingDDE && !isMuted);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Application::exportEntities(const QString& filename,
|
bool Application::exportEntities(const QString& filename,
|
||||||
const QVector<QUuid>& entityIDs,
|
const QVector<QUuid>& entityIDs,
|
||||||
const glm::vec3* givenOffset) {
|
const glm::vec3* givenOffset) {
|
||||||
|
@ -5848,14 +5777,13 @@ void Application::pushPostUpdateLambda(void* key, const std::function<void()>& f
|
||||||
// to everyone.
|
// to everyone.
|
||||||
// The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition().
|
// The principal result is to call updateLookAtTargetAvatar() and then setLookAtPosition().
|
||||||
// Note that it is called BEFORE we update position or joints based on sensors, etc.
|
// Note that it is called BEFORE we update position or joints based on sensors, etc.
|
||||||
void Application::updateMyAvatarLookAtPosition() {
|
void Application::updateMyAvatarLookAtPosition(float deltaTime) {
|
||||||
PerformanceTimer perfTimer("lookAt");
|
PerformanceTimer perfTimer("lookAt");
|
||||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||||
PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()");
|
PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()");
|
||||||
|
|
||||||
auto myAvatar = getMyAvatar();
|
auto myAvatar = getMyAvatar();
|
||||||
FaceTracker* faceTracker = getActiveFaceTracker();
|
myAvatar->updateEyesLookAtPosition(deltaTime);
|
||||||
myAvatar->updateLookAtPosition(faceTracker, _myCamera);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::updateThreads(float deltaTime) {
|
void Application::updateThreads(float deltaTime) {
|
||||||
|
@ -6281,37 +6209,6 @@ void Application::update(float deltaTime) {
|
||||||
auto myAvatar = getMyAvatar();
|
auto myAvatar = getMyAvatar();
|
||||||
{
|
{
|
||||||
PerformanceTimer perfTimer("devices");
|
PerformanceTimer perfTimer("devices");
|
||||||
|
|
||||||
FaceTracker* tracker = getSelectedFaceTracker();
|
|
||||||
if (tracker && Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking) != tracker->isMuted()) {
|
|
||||||
tracker->toggleMute();
|
|
||||||
}
|
|
||||||
|
|
||||||
tracker = getActiveFaceTracker();
|
|
||||||
if (tracker && !tracker->isMuted()) {
|
|
||||||
tracker->update(deltaTime);
|
|
||||||
|
|
||||||
// Auto-mute microphone after losing face tracking?
|
|
||||||
if (tracker->isTracking()) {
|
|
||||||
_lastFaceTrackerUpdate = usecTimestampNow();
|
|
||||||
} else {
|
|
||||||
const quint64 MUTE_MICROPHONE_AFTER_USECS = 5000000; //5 secs
|
|
||||||
Menu* menu = Menu::getInstance();
|
|
||||||
auto audioClient = DependencyManager::get<AudioClient>();
|
|
||||||
if (menu->isOptionChecked(MenuOption::AutoMuteAudio) && !audioClient->isMuted()) {
|
|
||||||
if (_lastFaceTrackerUpdate > 0
|
|
||||||
&& ((usecTimestampNow() - _lastFaceTrackerUpdate) > MUTE_MICROPHONE_AFTER_USECS)) {
|
|
||||||
audioClient->setMuted(true);
|
|
||||||
_lastFaceTrackerUpdate = 0;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_lastFaceTrackerUpdate = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
_lastFaceTrackerUpdate = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||||
|
|
||||||
controller::HmdAvatarAlignmentType hmdAvatarAlignmentType;
|
controller::HmdAvatarAlignmentType hmdAvatarAlignmentType;
|
||||||
|
@ -6639,7 +6536,7 @@ void Application::update(float deltaTime) {
|
||||||
{
|
{
|
||||||
PROFILE_RANGE(simulation, "MyAvatar");
|
PROFILE_RANGE(simulation, "MyAvatar");
|
||||||
PerformanceTimer perfTimer("MyAvatar");
|
PerformanceTimer perfTimer("MyAvatar");
|
||||||
qApp->updateMyAvatarLookAtPosition();
|
qApp->updateMyAvatarLookAtPosition(deltaTime);
|
||||||
avatarManager->updateMyAvatar(deltaTime);
|
avatarManager->updateMyAvatar(deltaTime);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7107,10 +7004,6 @@ void Application::copyDisplayViewFrustum(ViewFrustum& viewOut) const {
|
||||||
// feature. However, we still use this to reset face trackers, eye trackers, audio and to optionally re-load the avatar
|
// feature. However, we still use this to reset face trackers, eye trackers, audio and to optionally re-load the avatar
|
||||||
// rig and animations from scratch.
|
// rig and animations from scratch.
|
||||||
void Application::resetSensors(bool andReload) {
|
void Application::resetSensors(bool andReload) {
|
||||||
#ifdef HAVE_DDE
|
|
||||||
DependencyManager::get<DdeFaceTracker>()->reset();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
_overlayConductor.centerUI();
|
_overlayConductor.centerUI();
|
||||||
getActiveDisplayPlugin()->resetSensors();
|
getActiveDisplayPlugin()->resetSensors();
|
||||||
getMyAvatar()->reset(true, andReload);
|
getMyAvatar()->reset(true, andReload);
|
||||||
|
@ -7508,13 +7401,10 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine
|
||||||
scriptEngine->registerGlobalObject("AccountServices", AccountServicesScriptingInterface::getInstance());
|
scriptEngine->registerGlobalObject("AccountServices", AccountServicesScriptingInterface::getInstance());
|
||||||
qScriptRegisterMetaType(scriptEngine.data(), DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue);
|
qScriptRegisterMetaType(scriptEngine.data(), DownloadInfoResultToScriptValue, DownloadInfoResultFromScriptValue);
|
||||||
|
|
||||||
#ifdef HAVE_DDE
|
|
||||||
scriptEngine->registerGlobalObject("FaceTracker", DependencyManager::get<DdeFaceTracker>().data());
|
|
||||||
#endif
|
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get<AvatarManager>().data());
|
scriptEngine->registerGlobalObject("AvatarManager", DependencyManager::get<AvatarManager>().data());
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("LODManager", DependencyManager::get<LODManager>().data());
|
scriptEngine->registerGlobalObject("LODManager", DependencyManager::get<LODManager>().data());
|
||||||
|
qScriptRegisterMetaType(scriptEngine.data(), worldDetailQualityToScriptValue, worldDetailQualityFromScriptValue);
|
||||||
|
|
||||||
scriptEngine->registerGlobalObject("Keyboard", DependencyManager::get<KeyboardScriptingInterface>().data());
|
scriptEngine->registerGlobalObject("Keyboard", DependencyManager::get<KeyboardScriptingInterface>().data());
|
||||||
scriptEngine->registerGlobalObject("Performance", new PerformanceScriptingInterface());
|
scriptEngine->registerGlobalObject("Performance", new PerformanceScriptingInterface());
|
||||||
|
|
|
@ -81,7 +81,6 @@
|
||||||
#include "VisionSqueeze.h"
|
#include "VisionSqueeze.h"
|
||||||
|
|
||||||
class GLCanvas;
|
class GLCanvas;
|
||||||
class FaceTracker;
|
|
||||||
class MainWindow;
|
class MainWindow;
|
||||||
class AssetUpload;
|
class AssetUpload;
|
||||||
class CompositorHelper;
|
class CompositorHelper;
|
||||||
|
@ -191,9 +190,6 @@ public:
|
||||||
|
|
||||||
ivec2 getMouse() const;
|
ivec2 getMouse() const;
|
||||||
|
|
||||||
FaceTracker* getActiveFaceTracker();
|
|
||||||
FaceTracker* getSelectedFaceTracker();
|
|
||||||
|
|
||||||
ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; }
|
ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; }
|
||||||
const ApplicationOverlay& getApplicationOverlay() const { return _applicationOverlay; }
|
const ApplicationOverlay& getApplicationOverlay() const { return _applicationOverlay; }
|
||||||
CompositorHelper& getApplicationCompositor() const;
|
CompositorHelper& getApplicationCompositor() const;
|
||||||
|
@ -292,7 +288,7 @@ public:
|
||||||
|
|
||||||
virtual void pushPostUpdateLambda(void* key, const std::function<void()>& func) override;
|
virtual void pushPostUpdateLambda(void* key, const std::function<void()>& func) override;
|
||||||
|
|
||||||
void updateMyAvatarLookAtPosition();
|
void updateMyAvatarLookAtPosition(float deltaTime);
|
||||||
|
|
||||||
float getGameLoopRate() const { return _gameLoopCounter.rate(); }
|
float getGameLoopRate() const { return _gameLoopCounter.rate(); }
|
||||||
|
|
||||||
|
@ -423,7 +419,6 @@ public slots:
|
||||||
static void packageModel();
|
static void packageModel();
|
||||||
|
|
||||||
void resetSensors(bool andReload = false);
|
void resetSensors(bool andReload = false);
|
||||||
void setActiveFaceTracker() const;
|
|
||||||
|
|
||||||
void hmdVisibleChanged(bool visible);
|
void hmdVisibleChanged(bool visible);
|
||||||
|
|
||||||
|
@ -497,8 +492,6 @@ private slots:
|
||||||
|
|
||||||
void resettingDomain();
|
void resettingDomain();
|
||||||
|
|
||||||
void faceTrackerMuteToggled();
|
|
||||||
|
|
||||||
void activeChanged(Qt::ApplicationState state);
|
void activeChanged(Qt::ApplicationState state);
|
||||||
void windowMinimizedChanged(bool minimized);
|
void windowMinimizedChanged(bool minimized);
|
||||||
|
|
||||||
|
@ -736,8 +729,6 @@ private:
|
||||||
PerformanceManager _performanceManager;
|
PerformanceManager _performanceManager;
|
||||||
RefreshRateManager _refreshRateManager;
|
RefreshRateManager _refreshRateManager;
|
||||||
|
|
||||||
quint64 _lastFaceTrackerUpdate;
|
|
||||||
|
|
||||||
GameWorkload _gameWorkload;
|
GameWorkload _gameWorkload;
|
||||||
|
|
||||||
GraphicsEngine _graphicsEngine;
|
GraphicsEngine _graphicsEngine;
|
||||||
|
|
|
@ -212,22 +212,36 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
|
||||||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||||
|
|
||||||
|
// Once the skeleton URL has been loaded, add the Avatar Entities.
|
||||||
|
// We have to wait, because otherwise the avatar entities will try to get attached to the joints
|
||||||
|
// of the *current* avatar at first. But the current avatar might have a different joints scheme
|
||||||
|
// from the new avatar, and that would cause the entities to be attached to the wrong joints.
|
||||||
|
|
||||||
|
std::shared_ptr<QMetaObject::Connection> connection1 = std::make_shared<QMetaObject::Connection>();
|
||||||
|
*connection1 = connect(myAvatar.get(), &MyAvatar::onLoadComplete, [this, bookmark, bookmarkName, myAvatar, connection1]() {
|
||||||
|
qCDebug(interfaceapp) << "Finish loading avatar bookmark" << bookmarkName;
|
||||||
|
QObject::disconnect(*connection1);
|
||||||
myAvatar->clearWornAvatarEntities();
|
myAvatar->clearWornAvatarEntities();
|
||||||
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
|
||||||
myAvatar->useFullAvatarURL(avatarUrl);
|
|
||||||
qCDebug(interfaceapp) << "Avatar On";
|
|
||||||
const QList<QVariant>& attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList<QVariant>()).toList();
|
|
||||||
|
|
||||||
qCDebug(interfaceapp) << "Attach " << attachments;
|
|
||||||
myAvatar->setAttachmentsVariant(attachments);
|
|
||||||
|
|
||||||
const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
|
const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
|
||||||
myAvatar->setAvatarScale(qScale);
|
myAvatar->setAvatarScale(qScale);
|
||||||
|
QList<QVariant> attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList<QVariant>()).toList();
|
||||||
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
|
myAvatar->setAttachmentsVariant(attachments);
|
||||||
|
QVariantList avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
|
||||||
addAvatarEntities(avatarEntities);
|
addAvatarEntities(avatarEntities);
|
||||||
|
|
||||||
emit bookmarkLoaded(bookmarkName);
|
emit bookmarkLoaded(bookmarkName);
|
||||||
|
});
|
||||||
|
|
||||||
|
std::shared_ptr<QMetaObject::Connection> connection2 = std::make_shared<QMetaObject::Connection>();
|
||||||
|
*connection2 = connect(myAvatar.get(), &MyAvatar::onLoadFailed, [this, bookmarkName, connection2]() {
|
||||||
|
qCDebug(interfaceapp) << "Failed to load avatar bookmark" << bookmarkName;
|
||||||
|
QObject::disconnect(*connection2);
|
||||||
|
});
|
||||||
|
|
||||||
|
qCDebug(interfaceapp) << "Start loading avatar bookmark" << bookmarkName;
|
||||||
|
|
||||||
|
const QString& avatarUrl = bookmark.value(ENTRY_AVATAR_URL, "").toString();
|
||||||
|
myAvatar->useFullAvatarURL(avatarUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,11 +19,8 @@
|
||||||
#include "ui/DialogsManager.h"
|
#include "ui/DialogsManager.h"
|
||||||
#include "InterfaceLogging.h"
|
#include "InterfaceLogging.h"
|
||||||
|
|
||||||
const float LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS;
|
Setting::Handle<int> desktopWorldDetailQuality("desktopWorldDetailQuality", (int)DEFAULT_WORLD_DETAIL_QUALITY);
|
||||||
const float LODManager::DEFAULT_HMD_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS;
|
Setting::Handle<int> hmdWorldDetailQuality("hmdWorldDetailQuality", (int)DEFAULT_WORLD_DETAIL_QUALITY);
|
||||||
|
|
||||||
Setting::Handle<float> desktopLODDecreaseFPS("desktopLODDecreaseFPS", LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS);
|
|
||||||
Setting::Handle<float> hmdLODDecreaseFPS("hmdLODDecreaseFPS", LODManager::DEFAULT_HMD_LOD_DOWN_FPS);
|
|
||||||
|
|
||||||
LODManager::LODManager() {
|
LODManager::LODManager() {
|
||||||
}
|
}
|
||||||
|
@ -326,19 +323,21 @@ QString LODManager::getLODFeedbackText() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void LODManager::loadSettings() {
|
void LODManager::loadSettings() {
|
||||||
setDesktopLODTargetFPS(desktopLODDecreaseFPS.get());
|
auto desktopQuality = static_cast<WorldDetailQuality>(desktopWorldDetailQuality.get());
|
||||||
Setting::Handle<bool> firstRun { Settings::firstRun, true };
|
auto hmdQuality = static_cast<WorldDetailQuality>(hmdWorldDetailQuality.get());
|
||||||
|
|
||||||
|
Setting::Handle<bool> firstRun{ Settings::firstRun, true };
|
||||||
if (qApp->property(hifi::properties::OCULUS_STORE).toBool() && firstRun.get()) {
|
if (qApp->property(hifi::properties::OCULUS_STORE).toBool() && firstRun.get()) {
|
||||||
const float LOD_HIGH_QUALITY_LEVEL = 0.75f;
|
hmdQuality = WORLD_DETAIL_HIGH;
|
||||||
setHMDLODTargetFPS(LOD_HIGH_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS);
|
|
||||||
} else {
|
|
||||||
setHMDLODTargetFPS(hmdLODDecreaseFPS.get());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setWorldDetailQuality(desktopQuality, false);
|
||||||
|
setWorldDetailQuality(hmdQuality, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LODManager::saveSettings() {
|
void LODManager::saveSettings() {
|
||||||
desktopLODDecreaseFPS.set(getDesktopLODTargetFPS());
|
desktopWorldDetailQuality.set((int)_desktopWorldDetailQuality);
|
||||||
hmdLODDecreaseFPS.set(getHMDLODTargetFPS());
|
hmdWorldDetailQuality.set((int)_hmdWorldDetailQuality);
|
||||||
}
|
}
|
||||||
|
|
||||||
const float MIN_DECREASE_FPS = 0.5f;
|
const float MIN_DECREASE_FPS = 0.5f;
|
||||||
|
@ -393,54 +392,33 @@ float LODManager::getLODTargetFPS() const {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void LODManager::setWorldDetailQuality(float quality) {
|
void LODManager::setWorldDetailQuality(WorldDetailQuality quality, bool isHMDMode) {
|
||||||
static const float MIN_FPS = 10;
|
float desiredFPS = isHMDMode ? QUALITY_TO_FPS_HMD[quality] : QUALITY_TO_FPS_DESKTOP[quality];
|
||||||
static const float LOW = 0.25f;
|
|
||||||
|
|
||||||
bool isLowestValue = quality == LOW;
|
|
||||||
bool isHMDMode = qApp->isHMDMode();
|
|
||||||
|
|
||||||
float maxFPS = isHMDMode ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS;
|
|
||||||
float desiredFPS = maxFPS;
|
|
||||||
|
|
||||||
if (!isLowestValue) {
|
|
||||||
float calculatedFPS = (maxFPS - (maxFPS * quality));
|
|
||||||
desiredFPS = calculatedFPS < MIN_FPS ? MIN_FPS : calculatedFPS;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isHMDMode) {
|
if (isHMDMode) {
|
||||||
|
_hmdWorldDetailQuality = quality;
|
||||||
setHMDLODTargetFPS(desiredFPS);
|
setHMDLODTargetFPS(desiredFPS);
|
||||||
} else {
|
} else {
|
||||||
|
_desktopWorldDetailQuality = quality;
|
||||||
setDesktopLODTargetFPS(desiredFPS);
|
setDesktopLODTargetFPS(desiredFPS);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LODManager::setWorldDetailQuality(WorldDetailQuality quality) {
|
||||||
|
setWorldDetailQuality(quality, qApp->isHMDMode());
|
||||||
emit worldDetailQualityChanged();
|
emit worldDetailQualityChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
float LODManager::getWorldDetailQuality() const {
|
WorldDetailQuality LODManager::getWorldDetailQuality() const {
|
||||||
|
return qApp->isHMDMode() ? _hmdWorldDetailQuality : _desktopWorldDetailQuality;
|
||||||
|
}
|
||||||
|
|
||||||
static const float LOW = 0.25f;
|
QScriptValue worldDetailQualityToScriptValue(QScriptEngine* engine, const WorldDetailQuality& worldDetailQuality) {
|
||||||
static const float MEDIUM = 0.5f;
|
return worldDetailQuality;
|
||||||
static const float HIGH = 0.75f;
|
}
|
||||||
|
|
||||||
bool inHMD = qApp->isHMDMode();
|
void worldDetailQualityFromScriptValue(const QScriptValue& object, WorldDetailQuality& worldDetailQuality) {
|
||||||
|
worldDetailQuality =
|
||||||
float targetFPS = 0.0f;
|
static_cast<WorldDetailQuality>(std::min(std::max(object.toInt32(), (int)WORLD_DETAIL_LOW), (int)WORLD_DETAIL_HIGH));
|
||||||
if (inHMD) {
|
|
||||||
targetFPS = getHMDLODTargetFPS();
|
|
||||||
} else {
|
|
||||||
targetFPS = getDesktopLODTargetFPS();
|
|
||||||
}
|
|
||||||
float maxFPS = inHMD ? LOD_MAX_LIKELY_HMD_FPS : LOD_MAX_LIKELY_DESKTOP_FPS;
|
|
||||||
float percentage = 1.0f - targetFPS / maxFPS;
|
|
||||||
|
|
||||||
if (percentage <= LOW) {
|
|
||||||
return LOW;
|
|
||||||
} else if (percentage <= MEDIUM) {
|
|
||||||
return MEDIUM;
|
|
||||||
}
|
|
||||||
|
|
||||||
return HIGH;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void LODManager::setLODQualityLevel(float quality) {
|
void LODManager::setLODQualityLevel(float quality) {
|
||||||
|
|
|
@ -23,22 +23,48 @@
|
||||||
#include <render/Args.h>
|
#include <render/Args.h>
|
||||||
|
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* <p>The world detail quality rendered.</p>
|
||||||
|
* <table>
|
||||||
|
* <thead>
|
||||||
|
* <tr><th>Value</th><th>Description</th></tr>
|
||||||
|
* </thead>
|
||||||
|
* <tbody>
|
||||||
|
* <tr><td><code>0</code></td><td>Low world detail quality.</td></tr>
|
||||||
|
* <tr><td><code>1</code></td><td>Medium world detail quality.</td></tr>
|
||||||
|
* <tr><td><code>2</code></td><td>High world detail quality.</td></tr>
|
||||||
|
* </tbody>
|
||||||
|
* </table>
|
||||||
|
* @typedef {number} LODManager.WorldDetailQuality
|
||||||
|
*/
|
||||||
|
enum WorldDetailQuality {
|
||||||
|
WORLD_DETAIL_LOW = 0,
|
||||||
|
WORLD_DETAIL_MEDIUM,
|
||||||
|
WORLD_DETAIL_HIGH
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(WorldDetailQuality);
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
const float LOD_DEFAULT_QUALITY_LEVEL = 0.2f; // default quality level setting is High (lower framerate)
|
const float LOD_DEFAULT_QUALITY_LEVEL = 0.2f; // default quality level setting is High (lower framerate)
|
||||||
#else
|
#else
|
||||||
const float LOD_DEFAULT_QUALITY_LEVEL = 0.5f; // default quality level setting is Mid
|
const float LOD_DEFAULT_QUALITY_LEVEL = 0.5f; // default quality level setting is Mid
|
||||||
#endif
|
#endif
|
||||||
const float LOD_MAX_LIKELY_DESKTOP_FPS = 60.0f; // this is essentially, V-synch fps
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
const float LOD_MAX_LIKELY_HMD_FPS = 36.0f; // this is essentially, V-synch fps
|
const WorldDetailQuality DEFAULT_WORLD_DETAIL_QUALITY = WORLD_DETAIL_LOW;
|
||||||
|
const std::vector<float> QUALITY_TO_FPS_DESKTOP = { 60.0f, 30.0f, 15.0f };
|
||||||
|
const std::vector<float> QUALITY_TO_FPS_HMD = { 25.0f, 16.0f, 10.0f };
|
||||||
#else
|
#else
|
||||||
const float LOD_MAX_LIKELY_HMD_FPS = 90.0f; // this is essentially, V-synch fps
|
const WorldDetailQuality DEFAULT_WORLD_DETAIL_QUALITY = WORLD_DETAIL_MEDIUM;
|
||||||
|
const std::vector<float> QUALITY_TO_FPS_DESKTOP = { 60.0f, 30.0f, 15.0f };
|
||||||
|
const std::vector<float> QUALITY_TO_FPS_HMD = { 90.0f, 45.0f, 22.5f };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
const float LOD_OFFSET_FPS = 5.0f; // offset of FPS to add for computing the target framerate
|
const float LOD_OFFSET_FPS = 5.0f; // offset of FPS to add for computing the target framerate
|
||||||
|
|
||||||
class AABox;
|
class AABox;
|
||||||
|
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* The LOD class manages your Level of Detail functions within Interface.
|
* The LOD class manages your Level of Detail functions within Interface.
|
||||||
* @namespace LODManager
|
* @namespace LODManager
|
||||||
|
@ -51,12 +77,12 @@ class AABox;
|
||||||
* @property {number} engineRunTime <em>Read-only.</em>
|
* @property {number} engineRunTime <em>Read-only.</em>
|
||||||
* @property {number} gpuTime <em>Read-only.</em>
|
* @property {number} gpuTime <em>Read-only.</em>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
class LODManager : public QObject, public Dependency {
|
class LODManager : public QObject, public Dependency {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
|
|
||||||
Q_PROPERTY(float worldDetailQuality READ getWorldDetailQuality WRITE setWorldDetailQuality NOTIFY worldDetailQualityChanged)
|
Q_PROPERTY(WorldDetailQuality worldDetailQuality READ getWorldDetailQuality WRITE setWorldDetailQuality
|
||||||
|
NOTIFY worldDetailQualityChanged)
|
||||||
|
|
||||||
Q_PROPERTY(float lodQualityLevel READ getLODQualityLevel WRITE setLODQualityLevel NOTIFY lodQualityLevelChanged)
|
Q_PROPERTY(float lodQualityLevel READ getLODQualityLevel WRITE setLODQualityLevel NOTIFY lodQualityLevelChanged)
|
||||||
|
|
||||||
|
@ -193,8 +219,8 @@ public:
|
||||||
float getSmoothRenderTime() const { return _smoothRenderTime; };
|
float getSmoothRenderTime() const { return _smoothRenderTime; };
|
||||||
float getSmoothRenderFPS() const { return (_smoothRenderTime > 0.0f ? (float)MSECS_PER_SECOND / _smoothRenderTime : 0.0f); };
|
float getSmoothRenderFPS() const { return (_smoothRenderTime > 0.0f ? (float)MSECS_PER_SECOND / _smoothRenderTime : 0.0f); };
|
||||||
|
|
||||||
void setWorldDetailQuality(float quality);
|
void setWorldDetailQuality(WorldDetailQuality quality);
|
||||||
float getWorldDetailQuality() const;
|
WorldDetailQuality getWorldDetailQuality() const;
|
||||||
|
|
||||||
void setLODQualityLevel(float quality);
|
void setLODQualityLevel(float quality);
|
||||||
float getLODQualityLevel() const;
|
float getLODQualityLevel() const;
|
||||||
|
@ -220,9 +246,6 @@ public:
|
||||||
float getPidOd() const;
|
float getPidOd() const;
|
||||||
float getPidO() const;
|
float getPidO() const;
|
||||||
|
|
||||||
static const float DEFAULT_DESKTOP_LOD_DOWN_FPS;
|
|
||||||
static const float DEFAULT_HMD_LOD_DOWN_FPS;
|
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
@ -244,6 +267,8 @@ signals:
|
||||||
private:
|
private:
|
||||||
LODManager();
|
LODManager();
|
||||||
|
|
||||||
|
void setWorldDetailQuality(WorldDetailQuality quality, bool isHMDMode);
|
||||||
|
|
||||||
std::mutex _automaticLODLock;
|
std::mutex _automaticLODLock;
|
||||||
bool _automaticLODAdjust = true;
|
bool _automaticLODAdjust = true;
|
||||||
|
|
||||||
|
@ -258,8 +283,11 @@ private:
|
||||||
|
|
||||||
float _lodQualityLevel{ LOD_DEFAULT_QUALITY_LEVEL };
|
float _lodQualityLevel{ LOD_DEFAULT_QUALITY_LEVEL };
|
||||||
|
|
||||||
float _desktopTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS };
|
WorldDetailQuality _desktopWorldDetailQuality { DEFAULT_WORLD_DETAIL_QUALITY };
|
||||||
float _hmdTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS };
|
WorldDetailQuality _hmdWorldDetailQuality { DEFAULT_WORLD_DETAIL_QUALITY };
|
||||||
|
|
||||||
|
float _desktopTargetFPS { QUALITY_TO_FPS_DESKTOP[_desktopWorldDetailQuality] };
|
||||||
|
float _hmdTargetFPS { QUALITY_TO_FPS_HMD[_hmdWorldDetailQuality] };
|
||||||
|
|
||||||
float _lodHalfAngle = getHalfAngleFromVisibilityDistance(DEFAULT_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT);
|
float _lodHalfAngle = getHalfAngleFromVisibilityDistance(DEFAULT_VISIBILITY_DISTANCE_FOR_UNIT_ELEMENT);
|
||||||
int _boundaryLevelAdjust = 0;
|
int _boundaryLevelAdjust = 0;
|
||||||
|
@ -269,4 +297,7 @@ private:
|
||||||
glm::vec4 _pidOutputs{ 0.0f };
|
glm::vec4 _pidOutputs{ 0.0f };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QScriptValue worldDetailQualityToScriptValue(QScriptEngine* engine, const WorldDetailQuality& worldDetailQuality);
|
||||||
|
void worldDetailQualityFromScriptValue(const QScriptValue& object, WorldDetailQuality& worldDetailQuality);
|
||||||
|
|
||||||
#endif // hifi_LODManager_h
|
#endif // hifi_LODManager_h
|
||||||
|
|
|
@ -37,7 +37,6 @@
|
||||||
#include "avatar/AvatarManager.h"
|
#include "avatar/AvatarManager.h"
|
||||||
#include "avatar/AvatarPackager.h"
|
#include "avatar/AvatarPackager.h"
|
||||||
#include "AvatarBookmarks.h"
|
#include "AvatarBookmarks.h"
|
||||||
#include "devices/DdeFaceTracker.h"
|
|
||||||
#include "MainWindow.h"
|
#include "MainWindow.h"
|
||||||
#include "render/DrawStatus.h"
|
#include "render/DrawStatus.h"
|
||||||
#include "scripting/MenuScriptingInterface.h"
|
#include "scripting/MenuScriptingInterface.h"
|
||||||
|
@ -499,47 +498,6 @@ Menu::Menu() {
|
||||||
// Developer > Avatar >>>
|
// Developer > Avatar >>>
|
||||||
MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar");
|
MenuWrapper* avatarDebugMenu = developerMenu->addMenu("Avatar");
|
||||||
|
|
||||||
// Developer > Avatar > Face Tracking
|
|
||||||
MenuWrapper* faceTrackingMenu = avatarDebugMenu->addMenu("Face Tracking");
|
|
||||||
{
|
|
||||||
QActionGroup* faceTrackerGroup = new QActionGroup(avatarDebugMenu);
|
|
||||||
|
|
||||||
bool defaultNoFaceTracking = true;
|
|
||||||
#ifdef HAVE_DDE
|
|
||||||
defaultNoFaceTracking = false;
|
|
||||||
#endif
|
|
||||||
QAction* noFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::NoFaceTracking,
|
|
||||||
0, defaultNoFaceTracking,
|
|
||||||
qApp, SLOT(setActiveFaceTracker()));
|
|
||||||
faceTrackerGroup->addAction(noFaceTracker);
|
|
||||||
|
|
||||||
#ifdef HAVE_DDE
|
|
||||||
QAction* ddeFaceTracker = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseCamera,
|
|
||||||
0, true,
|
|
||||||
qApp, SLOT(setActiveFaceTracker()));
|
|
||||||
faceTrackerGroup->addAction(ddeFaceTracker);
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
#ifdef HAVE_DDE
|
|
||||||
faceTrackingMenu->addSeparator();
|
|
||||||
QAction* binaryEyelidControl = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::BinaryEyelidControl, 0, true);
|
|
||||||
binaryEyelidControl->setVisible(true); // DDE face tracking is on by default
|
|
||||||
QAction* coupleEyelids = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::CoupleEyelids, 0, true);
|
|
||||||
coupleEyelids->setVisible(true); // DDE face tracking is on by default
|
|
||||||
QAction* useAudioForMouth = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::UseAudioForMouth, 0, true);
|
|
||||||
useAudioForMouth->setVisible(true); // DDE face tracking is on by default
|
|
||||||
QAction* ddeFiltering = addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::VelocityFilter, 0, true);
|
|
||||||
ddeFiltering->setVisible(true); // DDE face tracking is on by default
|
|
||||||
QAction* ddeCalibrate = addActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::CalibrateCamera, 0,
|
|
||||||
DependencyManager::get<DdeFaceTracker>().data(), SLOT(calibrate()));
|
|
||||||
ddeCalibrate->setVisible(true); // DDE face tracking is on by default
|
|
||||||
faceTrackingMenu->addSeparator();
|
|
||||||
addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::MuteFaceTracking,
|
|
||||||
[](bool mute) { FaceTracker::setIsMuted(mute); },
|
|
||||||
Qt::CTRL | Qt::SHIFT | Qt::Key_F, FaceTracker::isMuted());
|
|
||||||
addCheckableActionToQMenuAndActionHash(faceTrackingMenu, MenuOption::AutoMuteAudio, 0, false);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false);
|
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::AvatarReceiveStats, 0, false);
|
||||||
connect(action, &QAction::triggered, [this]{ Avatar::setShowReceiveStats(isOptionChecked(MenuOption::AvatarReceiveStats)); });
|
connect(action, &QAction::triggered, [this]{ Avatar::setShowReceiveStats(isOptionChecked(MenuOption::AvatarReceiveStats)); });
|
||||||
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowBoundingCollisionShapes, 0, false);
|
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowBoundingCollisionShapes, 0, false);
|
||||||
|
|
|
@ -92,7 +92,7 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP
|
||||||
RenderScriptingInterface::getInstance()->setShadowsEnabled(true);
|
RenderScriptingInterface::getInstance()->setShadowsEnabled(true);
|
||||||
qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::REALTIME);
|
qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::REALTIME);
|
||||||
|
|
||||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(0.75f);
|
DependencyManager::get<LODManager>()->setWorldDetailQuality(WORLD_DETAIL_HIGH);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PerformancePreset::MID:
|
case PerformancePreset::MID:
|
||||||
|
@ -104,7 +104,7 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP
|
||||||
|
|
||||||
RenderScriptingInterface::getInstance()->setShadowsEnabled(false);
|
RenderScriptingInterface::getInstance()->setShadowsEnabled(false);
|
||||||
qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::INTERACTIVE);
|
qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::INTERACTIVE);
|
||||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(0.5f);
|
DependencyManager::get<LODManager>()->setWorldDetailQuality(WORLD_DETAIL_MEDIUM);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PerformancePreset::LOW:
|
case PerformancePreset::LOW:
|
||||||
|
@ -114,7 +114,7 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP
|
||||||
|
|
||||||
RenderScriptingInterface::getInstance()->setViewportResolutionScale(recommandedPpiScale);
|
RenderScriptingInterface::getInstance()->setViewportResolutionScale(recommandedPpiScale);
|
||||||
|
|
||||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(0.25f);
|
DependencyManager::get<LODManager>()->setWorldDetailQuality(WORLD_DETAIL_LOW);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case PerformancePreset::UNKNOWN:
|
case PerformancePreset::UNKNOWN:
|
||||||
|
|
|
@ -26,19 +26,22 @@ class AudioScope : public QObject, public Dependency {
|
||||||
SINGLETON_DEPENDENCY
|
SINGLETON_DEPENDENCY
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* The AudioScope API helps control the Audio Scope features in Interface
|
* The <code>AudioScope</code> API provides facilities for an audio scope.
|
||||||
|
*
|
||||||
* @namespace AudioScope
|
* @namespace AudioScope
|
||||||
*
|
*
|
||||||
|
* @deprecated This API doesn't work properly. It is deprecated and will be removed.
|
||||||
|
*
|
||||||
* @hifi-interface
|
* @hifi-interface
|
||||||
* @hifi-client-entity
|
* @hifi-client-entity
|
||||||
* @hifi-avatar
|
* @hifi-avatar
|
||||||
*
|
*
|
||||||
* @property {number} scopeInput <em>Read-only.</em>
|
* @property {number[]} scopeInput - Scope input. <em>Read-only.</em>
|
||||||
* @property {number} scopeOutputLeft <em>Read-only.</em>
|
* @property {number[]} scopeOutputLeft - Scope left output. <em>Read-only.</em>
|
||||||
* @property {number} scopeOutputRight <em>Read-only.</em>
|
* @property {number[]} scopeOutputRight - Scope right output. <em>Read-only.</em>
|
||||||
* @property {number} triggerInput <em>Read-only.</em>
|
* @property {number[]} triggerInput - Trigger input. <em>Read-only.</em>
|
||||||
* @property {number} triggerOutputLeft <em>Read-only.</em>
|
* @property {number[]} triggerOutputLeft - Trigger left output. <em>Read-only.</em>
|
||||||
* @property {number} triggerOutputRight <em>Read-only.</em>
|
* @property {number[]} triggerOutputRight - Trigger right output. <em>Read-only.</em>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Q_PROPERTY(QVector<int> scopeInput READ getScopeInput)
|
Q_PROPERTY(QVector<int> scopeInput READ getScopeInput)
|
||||||
|
@ -58,159 +61,186 @@ public:
|
||||||
public slots:
|
public slots:
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Toggle.
|
||||||
* @function AudioScope.toggle
|
* @function AudioScope.toggle
|
||||||
*/
|
*/
|
||||||
void toggle() { setVisible(!_isEnabled); }
|
void toggle() { setVisible(!_isEnabled); }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Set visible.
|
||||||
* @function AudioScope.setVisible
|
* @function AudioScope.setVisible
|
||||||
* @param {boolean} visible
|
* @param {boolean} visible - Visible.
|
||||||
*/
|
*/
|
||||||
void setVisible(bool visible);
|
void setVisible(bool visible);
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get visible.
|
||||||
* @function AudioScope.getVisible
|
* @function AudioScope.getVisible
|
||||||
* @returns {boolean}
|
* @returns {boolean} Visible.
|
||||||
*/
|
*/
|
||||||
bool getVisible() const { return _isEnabled; }
|
bool getVisible() const { return _isEnabled; }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Toggle pause.
|
||||||
* @function AudioScope.togglePause
|
* @function AudioScope.togglePause
|
||||||
*/
|
*/
|
||||||
void togglePause() { setPause(!_isPaused); }
|
void togglePause() { setPause(!_isPaused); }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Set pause.
|
||||||
* @function AudioScope.setPause
|
* @function AudioScope.setPause
|
||||||
* @param {boolean} paused
|
* @param {boolean} pause - Pause.
|
||||||
*/
|
*/
|
||||||
void setPause(bool paused) { _isPaused = paused; emit pauseChanged(); }
|
void setPause(bool paused) { _isPaused = paused; emit pauseChanged(); }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get pause.
|
||||||
* @function AudioScope.getPause
|
* @function AudioScope.getPause
|
||||||
* @returns {boolean}
|
* @returns {boolean} Pause.
|
||||||
*/
|
*/
|
||||||
bool getPause() { return _isPaused; }
|
bool getPause() { return _isPaused; }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Toggle trigger.
|
||||||
* @function AudioScope.toggleTrigger
|
* @function AudioScope.toggleTrigger
|
||||||
*/
|
*/
|
||||||
void toggleTrigger() { _autoTrigger = !_autoTrigger; }
|
void toggleTrigger() { _autoTrigger = !_autoTrigger; }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get auto trigger.
|
||||||
* @function AudioScope.getAutoTrigger
|
* @function AudioScope.getAutoTrigger
|
||||||
* @returns {boolean}
|
* @returns {boolean} Auto trigger.
|
||||||
*/
|
*/
|
||||||
bool getAutoTrigger() { return _autoTrigger; }
|
bool getAutoTrigger() { return _autoTrigger; }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Set auto trigger.
|
||||||
* @function AudioScope.setAutoTrigger
|
* @function AudioScope.setAutoTrigger
|
||||||
* @param {boolean} autoTrigger
|
* @param {boolean} autoTrigger - Auto trigger.
|
||||||
*/
|
*/
|
||||||
void setAutoTrigger(bool autoTrigger) { _isTriggered = false; _autoTrigger = autoTrigger; }
|
void setAutoTrigger(bool autoTrigger) { _isTriggered = false; _autoTrigger = autoTrigger; }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Set trigger values.
|
||||||
* @function AudioScope.setTriggerValues
|
* @function AudioScope.setTriggerValues
|
||||||
* @param {number} x
|
* @param {number} x - X.
|
||||||
* @param {number} y
|
* @param {number} y - Y.
|
||||||
*/
|
*/
|
||||||
void setTriggerValues(int x, int y) { _triggerValues.x = x; _triggerValues.y = y; }
|
void setTriggerValues(int x, int y) { _triggerValues.x = x; _triggerValues.y = y; }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Set triggered.
|
||||||
* @function AudioScope.setTriggered
|
* @function AudioScope.setTriggered
|
||||||
* @param {boolean} triggered
|
* @param {boolean} triggered - Triggered.
|
||||||
*/
|
*/
|
||||||
void setTriggered(bool triggered) { _isTriggered = triggered; }
|
void setTriggered(bool triggered) { _isTriggered = triggered; }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get triggered.
|
||||||
* @function AudioScope.getTriggered
|
* @function AudioScope.getTriggered
|
||||||
* @returns {boolean}
|
* @returns {boolean} Triggered.
|
||||||
*/
|
*/
|
||||||
bool getTriggered() { return _isTriggered; }
|
bool getTriggered() { return _isTriggered; }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get frames per second.
|
||||||
* @function AudioScope.getFramesPerSecond
|
* @function AudioScope.getFramesPerSecond
|
||||||
* @returns {number}
|
* @returns {number} Frames per second.
|
||||||
*/
|
*/
|
||||||
float getFramesPerSecond();
|
float getFramesPerSecond();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get frames per scope.
|
||||||
* @function AudioScope.getFramesPerScope
|
* @function AudioScope.getFramesPerScope
|
||||||
* @returns {number}
|
* @returns {number} Frames per scope.
|
||||||
*/
|
*/
|
||||||
int getFramesPerScope() { return _framesPerScope; }
|
int getFramesPerScope() { return _framesPerScope; }
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Select five frames audio scope.
|
||||||
* @function AudioScope.selectAudioScopeFiveFrames
|
* @function AudioScope.selectAudioScopeFiveFrames
|
||||||
*/
|
*/
|
||||||
void selectAudioScopeFiveFrames();
|
void selectAudioScopeFiveFrames();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Select twenty frames audio scope.
|
||||||
* @function AudioScope.selectAudioScopeTwentyFrames
|
* @function AudioScope.selectAudioScopeTwentyFrames
|
||||||
*/
|
*/
|
||||||
void selectAudioScopeTwentyFrames();
|
void selectAudioScopeTwentyFrames();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Select fifty frames audio scope.
|
||||||
* @function AudioScope.selectAudioScopeFiftyFrames
|
* @function AudioScope.selectAudioScopeFiftyFrames
|
||||||
*/
|
*/
|
||||||
void selectAudioScopeFiftyFrames();
|
void selectAudioScopeFiftyFrames();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get scope input.
|
||||||
* @function AudioScope.getScopeInput
|
* @function AudioScope.getScopeInput
|
||||||
* @returns {number[]}
|
* @returns {number[]} Scope input.
|
||||||
*/
|
*/
|
||||||
QVector<int> getScopeInput() { return _scopeInputData; };
|
QVector<int> getScopeInput() { return _scopeInputData; };
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get scope left output.
|
||||||
* @function AudioScope.getScopeOutputLeft
|
* @function AudioScope.getScopeOutputLeft
|
||||||
* @returns {number[]}
|
* @returns {number[]} Scope left output.
|
||||||
*/
|
*/
|
||||||
QVector<int> getScopeOutputLeft() { return _scopeOutputLeftData; };
|
QVector<int> getScopeOutputLeft() { return _scopeOutputLeftData; };
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get scope right output.
|
||||||
* @function AudioScope.getScopeOutputRight
|
* @function AudioScope.getScopeOutputRight
|
||||||
* @returns {number[]}
|
* @returns {number[]} Scope right output.
|
||||||
*/
|
*/
|
||||||
QVector<int> getScopeOutputRight() { return _scopeOutputRightData; };
|
QVector<int> getScopeOutputRight() { return _scopeOutputRightData; };
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get trigger input.
|
||||||
* @function AudioScope.getTriggerInput
|
* @function AudioScope.getTriggerInput
|
||||||
* @returns {number[]}
|
* @returns {number[]} Trigger input.
|
||||||
*/
|
*/
|
||||||
QVector<int> getTriggerInput() { return _triggerInputData; };
|
QVector<int> getTriggerInput() { return _triggerInputData; };
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get left trigger output.
|
||||||
* @function AudioScope.getTriggerOutputLeft
|
* @function AudioScope.getTriggerOutputLeft
|
||||||
* @returns {number[]}
|
* @returns {number[]} Left trigger output.
|
||||||
*/
|
*/
|
||||||
QVector<int> getTriggerOutputLeft() { return _triggerOutputLeftData; };
|
QVector<int> getTriggerOutputLeft() { return _triggerOutputLeftData; };
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Get right trigger output.
|
||||||
* @function AudioScope.getTriggerOutputRight
|
* @function AudioScope.getTriggerOutputRight
|
||||||
* @returns {number[]}
|
* @returns {number[]} Right trigger output.
|
||||||
*/
|
*/
|
||||||
QVector<int> getTriggerOutputRight() { return _triggerOutputRightData; };
|
QVector<int> getTriggerOutputRight() { return _triggerOutputRightData; };
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Set local echo.
|
||||||
* @function AudioScope.setLocalEcho
|
* @function AudioScope.setLocalEcho
|
||||||
* @parm {boolean} localEcho
|
* @parm {boolean} localEcho - Local echo.
|
||||||
*/
|
*/
|
||||||
void setLocalEcho(bool localEcho);
|
void setLocalEcho(bool localEcho);
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Set server echo.
|
||||||
* @function AudioScope.setServerEcho
|
* @function AudioScope.setServerEcho
|
||||||
* @parm {boolean} serverEcho
|
* @parm {boolean} serverEcho - Server echo.
|
||||||
*/
|
*/
|
||||||
void setServerEcho(bool serverEcho);
|
void setServerEcho(bool serverEcho);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Triggered when pause changes.
|
||||||
* @function AudioScope.pauseChanged
|
* @function AudioScope.pauseChanged
|
||||||
* @returns {Signal}
|
* @returns {Signal}
|
||||||
*/
|
*/
|
||||||
void pauseChanged();
|
void pauseChanged();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
|
* Triggered when scope is triggered.
|
||||||
* @function AudioScope.triggered
|
* @function AudioScope.triggered
|
||||||
* @returns {Signal}
|
* @returns {Signal}
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -48,7 +48,6 @@
|
||||||
#include <recording/Clip.h>
|
#include <recording/Clip.h>
|
||||||
#include <recording/Frame.h>
|
#include <recording/Frame.h>
|
||||||
#include <RecordingScriptingInterface.h>
|
#include <RecordingScriptingInterface.h>
|
||||||
#include <trackers/FaceTracker.h>
|
|
||||||
#include <RenderableModelEntityItem.h>
|
#include <RenderableModelEntityItem.h>
|
||||||
#include <VariantMapToScriptValue.h>
|
#include <VariantMapToScriptValue.h>
|
||||||
|
|
||||||
|
@ -101,7 +100,7 @@ static const QString USER_RECENTER_MODEL_DISABLE_HMD_LEAN = QStringLiteral("Disa
|
||||||
|
|
||||||
const QString HEAD_BLEND_DIRECTIONAL_ALPHA_NAME = "lookAroundAlpha";
|
const QString HEAD_BLEND_DIRECTIONAL_ALPHA_NAME = "lookAroundAlpha";
|
||||||
const QString HEAD_BLEND_LINEAR_ALPHA_NAME = "lookBlendAlpha";
|
const QString HEAD_BLEND_LINEAR_ALPHA_NAME = "lookBlendAlpha";
|
||||||
const float HEAD_ALPHA_BLENDING = 1.0f;
|
const QString SEATED_HEAD_BLEND_LINEAR_ALPHA_NAME = "seatedLookBlendAlpha";
|
||||||
|
|
||||||
const QString POINT_REACTION_NAME = "point";
|
const QString POINT_REACTION_NAME = "point";
|
||||||
const QString POINT_BLEND_DIRECTIONAL_ALPHA_NAME = "pointAroundAlpha";
|
const QString POINT_BLEND_DIRECTIONAL_ALPHA_NAME = "pointAroundAlpha";
|
||||||
|
@ -349,7 +348,8 @@ MyAvatar::MyAvatar(QThread* thread) :
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(&(_skeletonModel->getRig()), SIGNAL(onLoadComplete()), this, SIGNAL(onLoadComplete()));
|
connect(&(_skeletonModel->getRig()), &Rig::onLoadComplete, this, &MyAvatar::onLoadComplete);
|
||||||
|
connect(&(_skeletonModel->getRig()), &Rig::onLoadFailed, this, &MyAvatar::onLoadFailed);
|
||||||
|
|
||||||
_characterController.setDensity(_density);
|
_characterController.setDensity(_density);
|
||||||
}
|
}
|
||||||
|
@ -733,7 +733,7 @@ void MyAvatar::update(float deltaTime) {
|
||||||
_physicsSafetyPending = getCollisionsEnabled();
|
_physicsSafetyPending = getCollisionsEnabled();
|
||||||
_characterController.recomputeFlying(); // In case we've gone to into the sky.
|
_characterController.recomputeFlying(); // In case we've gone to into the sky.
|
||||||
}
|
}
|
||||||
if (_goToFeetAjustment && _skeletonModelLoaded) {
|
if (_goToFeetAjustment && _skeletonModel->isLoaded()) {
|
||||||
auto feetAjustment = getWorldPosition() - getWorldFeetPosition();
|
auto feetAjustment = getWorldPosition() - getWorldFeetPosition();
|
||||||
_goToPosition = getWorldPosition() + feetAjustment;
|
_goToPosition = getWorldPosition() + feetAjustment;
|
||||||
setWorldPosition(_goToPosition);
|
setWorldPosition(_goToPosition);
|
||||||
|
@ -749,7 +749,6 @@ void MyAvatar::update(float deltaTime) {
|
||||||
|
|
||||||
Head* head = getHead();
|
Head* head = getHead();
|
||||||
head->relax(deltaTime);
|
head->relax(deltaTime);
|
||||||
updateFromTrackers(deltaTime);
|
|
||||||
|
|
||||||
if (getIsInWalkingState() && glm::length(getControllerPoseInAvatarFrame(controller::Action::HEAD).getVelocity()) < DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) {
|
if (getIsInWalkingState() && glm::length(getControllerPoseInAvatarFrame(controller::Action::HEAD).getVelocity()) < DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) {
|
||||||
setIsInWalkingState(false);
|
setIsInWalkingState(false);
|
||||||
|
@ -782,18 +781,6 @@ void MyAvatar::update(float deltaTime) {
|
||||||
emit energyChanged(currentEnergy);
|
emit energyChanged(currentEnergy);
|
||||||
|
|
||||||
updateEyeContactTarget(deltaTime);
|
updateEyeContactTarget(deltaTime);
|
||||||
|
|
||||||
// if we're getting eye rotations from a tracker, disable observer-side procedural eye motions
|
|
||||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
|
||||||
bool eyesTracked =
|
|
||||||
userInputMapper->getPoseState(controller::Action::LEFT_EYE).valid &&
|
|
||||||
userInputMapper->getPoseState(controller::Action::RIGHT_EYE).valid;
|
|
||||||
|
|
||||||
int leftEyeJointIndex = getJointIndex("LeftEye");
|
|
||||||
int rightEyeJointIndex = getJointIndex("RightEye");
|
|
||||||
bool eyesAreOverridden = getIsJointOverridden(leftEyeJointIndex) || getIsJointOverridden(rightEyeJointIndex);
|
|
||||||
|
|
||||||
_headData->setHasProceduralEyeMovement(!(eyesTracked || eyesAreOverridden));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::updateEyeContactTarget(float deltaTime) {
|
void MyAvatar::updateEyeContactTarget(float deltaTime) {
|
||||||
|
@ -1148,60 +1135,6 @@ void MyAvatar::updateSensorToWorldMatrix() {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update avatar head rotation with sensor data
|
|
||||||
void MyAvatar::updateFromTrackers(float deltaTime) {
|
|
||||||
glm::vec3 estimatedRotation;
|
|
||||||
|
|
||||||
bool hasHead = getControllerPoseInAvatarFrame(controller::Action::HEAD).isValid();
|
|
||||||
bool playing = DependencyManager::get<recording::Deck>()->isPlaying();
|
|
||||||
if (hasHead && playing) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FaceTracker* tracker = qApp->getActiveFaceTracker();
|
|
||||||
bool inFacetracker = tracker && !FaceTracker::isMuted();
|
|
||||||
|
|
||||||
if (inFacetracker) {
|
|
||||||
estimatedRotation = glm::degrees(safeEulerAngles(tracker->getHeadRotation()));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rotate the body if the head is turned beyond the screen
|
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::TurnWithHead)) {
|
|
||||||
const float TRACKER_YAW_TURN_SENSITIVITY = 0.5f;
|
|
||||||
const float TRACKER_MIN_YAW_TURN = 15.0f;
|
|
||||||
const float TRACKER_MAX_YAW_TURN = 50.0f;
|
|
||||||
if ( (fabs(estimatedRotation.y) > TRACKER_MIN_YAW_TURN) &&
|
|
||||||
(fabs(estimatedRotation.y) < TRACKER_MAX_YAW_TURN) ) {
|
|
||||||
if (estimatedRotation.y > 0.0f) {
|
|
||||||
_bodyYawDelta += (estimatedRotation.y - TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
|
|
||||||
} else {
|
|
||||||
_bodyYawDelta += (estimatedRotation.y + TRACKER_MIN_YAW_TURN) * TRACKER_YAW_TURN_SENSITIVITY;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set the rotation of the avatar's head (as seen by others, not affecting view frustum)
|
|
||||||
// to be scaled such that when the user's physical head is pointing at edge of screen, the
|
|
||||||
// avatar head is at the edge of the in-world view frustum. So while a real person may move
|
|
||||||
// their head only 30 degrees or so, this may correspond to a 90 degree field of view.
|
|
||||||
// Note that roll is magnified by a constant because it is not related to field of view.
|
|
||||||
|
|
||||||
|
|
||||||
Head* head = getHead();
|
|
||||||
if (hasHead || playing) {
|
|
||||||
head->setDeltaPitch(estimatedRotation.x);
|
|
||||||
head->setDeltaYaw(estimatedRotation.y);
|
|
||||||
head->setDeltaRoll(estimatedRotation.z);
|
|
||||||
} else {
|
|
||||||
ViewFrustum viewFrustum;
|
|
||||||
qApp->copyViewFrustum(viewFrustum);
|
|
||||||
float magnifyFieldOfView = viewFrustum.getFieldOfView() / _realWorldFieldOfView.get();
|
|
||||||
head->setDeltaPitch(estimatedRotation.x * magnifyFieldOfView);
|
|
||||||
head->setDeltaYaw(estimatedRotation.y * magnifyFieldOfView);
|
|
||||||
head->setDeltaRoll(estimatedRotation.z);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::vec3 MyAvatar::getLeftHandPosition() const {
|
glm::vec3 MyAvatar::getLeftHandPosition() const {
|
||||||
auto pose = getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND);
|
auto pose = getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND);
|
||||||
return pose.isValid() ? pose.getTranslation() : glm::vec3(0.0f);
|
return pose.isValid() ? pose.getTranslation() : glm::vec3(0.0f);
|
||||||
|
@ -2158,7 +2091,7 @@ static float lookAtCostFunction(const glm::vec3& myForward, const glm::vec3& myP
|
||||||
const float DISTANCE_FACTOR = 3.14f;
|
const float DISTANCE_FACTOR = 3.14f;
|
||||||
const float MY_ANGLE_FACTOR = 1.0f;
|
const float MY_ANGLE_FACTOR = 1.0f;
|
||||||
const float OTHER_ANGLE_FACTOR = 1.0f;
|
const float OTHER_ANGLE_FACTOR = 1.0f;
|
||||||
const float OTHER_IS_TALKING_TERM = otherIsTalking ? 1.0f : 0.0f;
|
const float OTHER_IS_TALKING_TERM = otherIsTalking ? -1.0f : 0.0f;
|
||||||
const float LOOKING_AT_OTHER_ALREADY_TERM = lookingAtOtherAlready ? -0.2f : 0.0f;
|
const float LOOKING_AT_OTHER_ALREADY_TERM = lookingAtOtherAlready ? -0.2f : 0.0f;
|
||||||
|
|
||||||
const float GREATEST_LOOKING_AT_DISTANCE = 10.0f; // meters
|
const float GREATEST_LOOKING_AT_DISTANCE = 10.0f; // meters
|
||||||
|
@ -2184,6 +2117,9 @@ static float lookAtCostFunction(const glm::vec3& myForward, const glm::vec3& myP
|
||||||
|
|
||||||
void MyAvatar::computeMyLookAtTarget(const AvatarHash& hash) {
|
void MyAvatar::computeMyLookAtTarget(const AvatarHash& hash) {
|
||||||
glm::vec3 myForward = _lookAtYaw * IDENTITY_FORWARD;
|
glm::vec3 myForward = _lookAtYaw * IDENTITY_FORWARD;
|
||||||
|
if (_skeletonModel->isLoaded()) {
|
||||||
|
myForward = getHeadJointFrontVector();
|
||||||
|
}
|
||||||
glm::vec3 myPosition = getHead()->getEyePosition();
|
glm::vec3 myPosition = getHead()->getEyePosition();
|
||||||
CameraMode mode = qApp->getCamera().getMode();
|
CameraMode mode = qApp->getCamera().getMode();
|
||||||
if (mode == CAMERA_MODE_FIRST_PERSON_LOOK_AT || mode == CAMERA_MODE_FIRST_PERSON) {
|
if (mode == CAMERA_MODE_FIRST_PERSON_LOOK_AT || mode == CAMERA_MODE_FIRST_PERSON) {
|
||||||
|
@ -2196,7 +2132,7 @@ void MyAvatar::computeMyLookAtTarget(const AvatarHash& hash) {
|
||||||
foreach (const AvatarSharedPointer& avatarData, hash) {
|
foreach (const AvatarSharedPointer& avatarData, hash) {
|
||||||
std::shared_ptr<Avatar> avatar = std::static_pointer_cast<Avatar>(avatarData);
|
std::shared_ptr<Avatar> avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||||
if (!avatar->isMyAvatar() && avatar->isInitialized()) {
|
if (!avatar->isMyAvatar() && avatar->isInitialized()) {
|
||||||
glm::vec3 otherForward = avatar->getHead()->getForwardDirection();
|
glm::vec3 otherForward = avatar->getHeadJointFrontVector();
|
||||||
glm::vec3 otherPosition = avatar->getHead()->getEyePosition();
|
glm::vec3 otherPosition = avatar->getHead()->getEyePosition();
|
||||||
const float TIME_WITHOUT_TALKING_THRESHOLD = 1.0f;
|
const float TIME_WITHOUT_TALKING_THRESHOLD = 1.0f;
|
||||||
bool otherIsTalking = avatar->getHead()->getTimeWithoutTalking() <= TIME_WITHOUT_TALKING_THRESHOLD;
|
bool otherIsTalking = avatar->getHead()->getTimeWithoutTalking() <= TIME_WITHOUT_TALKING_THRESHOLD;
|
||||||
|
@ -2284,7 +2220,9 @@ void MyAvatar::updateLookAtTargetAvatar() {
|
||||||
AvatarHash hash = DependencyManager::get<AvatarManager>()->getHashCopy();
|
AvatarHash hash = DependencyManager::get<AvatarManager>()->getHashCopy();
|
||||||
|
|
||||||
// determine what the best look at target for my avatar should be.
|
// determine what the best look at target for my avatar should be.
|
||||||
|
if (!_scriptControlsEyesLookAt) {
|
||||||
computeMyLookAtTarget(hash);
|
computeMyLookAtTarget(hash);
|
||||||
|
}
|
||||||
|
|
||||||
// snap look at position for avatars that are looking at me.
|
// snap look at position for avatars that are looking at me.
|
||||||
snapOtherAvatarLookAtTargetsToMe(hash);
|
snapOtherAvatarLookAtTargetsToMe(hash);
|
||||||
|
@ -2496,7 +2434,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
|
|
||||||
_headBoneSet.clear();
|
_headBoneSet.clear();
|
||||||
_cauterizationNeedsUpdate = true;
|
_cauterizationNeedsUpdate = true;
|
||||||
_skeletonModelLoaded = false;
|
|
||||||
|
|
||||||
std::shared_ptr<QMetaObject::Connection> skeletonConnection = std::make_shared<QMetaObject::Connection>();
|
std::shared_ptr<QMetaObject::Connection> skeletonConnection = std::make_shared<QMetaObject::Connection>();
|
||||||
*skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() {
|
*skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() {
|
||||||
|
@ -2515,8 +2452,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
||||||
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
|
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
|
||||||
initAnimGraph();
|
initAnimGraph();
|
||||||
initFlowFromFST();
|
initFlowFromFST();
|
||||||
|
|
||||||
_skeletonModelLoaded = true;
|
|
||||||
}
|
}
|
||||||
QObject::disconnect(*skeletonConnection);
|
QObject::disconnect(*skeletonConnection);
|
||||||
});
|
});
|
||||||
|
@ -2633,6 +2568,8 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
|
||||||
if (urlString.isEmpty() || (fullAvatarURL != getSkeletonModelURL())) {
|
if (urlString.isEmpty() || (fullAvatarURL != getSkeletonModelURL())) {
|
||||||
setSkeletonModelURL(fullAvatarURL);
|
setSkeletonModelURL(fullAvatarURL);
|
||||||
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
|
UserActivityLogger::getInstance().changedModel("skeleton", urlString);
|
||||||
|
} else {
|
||||||
|
emit onLoadComplete();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3414,31 +3351,6 @@ bool MyAvatar::shouldRenderHead(const RenderArgs* renderArgs) const {
|
||||||
return !defaultMode || (!firstPerson && !insideHead) || (overrideAnim && !insideHead);
|
return !defaultMode || (!firstPerson && !insideHead) || (overrideAnim && !insideHead);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::setHasScriptedBlendshapes(bool hasScriptedBlendshapes) {
|
|
||||||
if (hasScriptedBlendshapes == _hasScriptedBlendShapes) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!hasScriptedBlendshapes) {
|
|
||||||
// send a forced avatarData update to make sure the script can send neutal blendshapes on unload
|
|
||||||
// without having to wait for the update loop, make sure _hasScriptedBlendShapes is still true
|
|
||||||
// before sending the update, or else it won't send the neutal blendshapes to the receiving clients
|
|
||||||
sendAvatarDataPacket(true);
|
|
||||||
}
|
|
||||||
_hasScriptedBlendShapes = hasScriptedBlendshapes;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyAvatar::setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement) {
|
|
||||||
_headData->setHasProceduralBlinkFaceMovement(hasProceduralBlinkFaceMovement);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyAvatar::setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement) {
|
|
||||||
_headData->setHasProceduralEyeFaceMovement(hasProceduralEyeFaceMovement);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyAvatar::setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement) {
|
|
||||||
_headData->setHasAudioEnabledFaceMovement(hasAudioEnabledFaceMovement);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MyAvatar::setRotationRecenterFilterLength(float length) {
|
void MyAvatar::setRotationRecenterFilterLength(float length) {
|
||||||
const float MINIMUM_ROTATION_RECENTER_FILTER_LENGTH = 0.01f;
|
const float MINIMUM_ROTATION_RECENTER_FILTER_LENGTH = 0.01f;
|
||||||
_rotationRecenterFilterLength = std::max(MINIMUM_ROTATION_RECENTER_FILTER_LENGTH, length);
|
_rotationRecenterFilterLength = std::max(MINIMUM_ROTATION_RECENTER_FILTER_LENGTH, length);
|
||||||
|
@ -5212,10 +5124,9 @@ bool MyAvatar::isReadyForPhysics() const {
|
||||||
|
|
||||||
void MyAvatar::setSprintMode(bool sprint) {
|
void MyAvatar::setSprintMode(bool sprint) {
|
||||||
if (qApp->isHMDMode()) {
|
if (qApp->isHMDMode()) {
|
||||||
_walkSpeedScalar = sprint ? AVATAR_DESKTOP_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
_walkSpeedScalar = sprint ? AVATAR_HMD_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR;
|
_walkSpeedScalar = sprint ? AVATAR_HMD_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR;
|
||||||
|
} else {
|
||||||
|
_walkSpeedScalar = sprint ? AVATAR_DESKTOP_SPRINT_SPEED_SCALAR : AVATAR_WALK_SPEED_SCALAR;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6619,11 +6530,10 @@ bool MyAvatar::getIsJointOverridden(int jointIndex) const {
|
||||||
return _skeletonModel->getIsJointOverridden(jointIndex);
|
return _skeletonModel->getIsJointOverridden(jointIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera) {
|
void MyAvatar::updateEyesLookAtPosition(float deltaTime) {
|
||||||
|
|
||||||
updateLookAtTargetAvatar();
|
updateLookAtTargetAvatar();
|
||||||
|
|
||||||
bool isLookingAtSomeone = false;
|
|
||||||
glm::vec3 lookAtSpot;
|
glm::vec3 lookAtSpot;
|
||||||
|
|
||||||
const MyHead* myHead = getMyHead();
|
const MyHead* myHead = getMyHead();
|
||||||
|
@ -6649,6 +6559,13 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera)
|
||||||
} else {
|
} else {
|
||||||
lookAtSpot = myHead->getEyePosition() + glm::normalize(leftVec) * 1000.0f;
|
lookAtSpot = myHead->getEyePosition() + glm::normalize(leftVec) * 1000.0f;
|
||||||
}
|
}
|
||||||
|
} else if (_scriptControlsEyesLookAt) {
|
||||||
|
if (_scriptEyesControlTimer < MAX_LOOK_AT_TIME_SCRIPT_CONTROL) {
|
||||||
|
_scriptEyesControlTimer += deltaTime;
|
||||||
|
lookAtSpot = _eyesLookAtTarget.get();
|
||||||
|
} else {
|
||||||
|
_scriptControlsEyesLookAt = false;
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
controller::Pose leftEyePose = getControllerPoseInAvatarFrame(controller::Action::LEFT_EYE);
|
controller::Pose leftEyePose = getControllerPoseInAvatarFrame(controller::Action::LEFT_EYE);
|
||||||
controller::Pose rightEyePose = getControllerPoseInAvatarFrame(controller::Action::RIGHT_EYE);
|
controller::Pose rightEyePose = getControllerPoseInAvatarFrame(controller::Action::RIGHT_EYE);
|
||||||
|
@ -6677,7 +6594,6 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera)
|
||||||
avatar && avatar->getLookAtSnappingEnabled() && getLookAtSnappingEnabled();
|
avatar && avatar->getLookAtSnappingEnabled() && getLookAtSnappingEnabled();
|
||||||
if (haveLookAtCandidate && mutualLookAtSnappingEnabled) {
|
if (haveLookAtCandidate && mutualLookAtSnappingEnabled) {
|
||||||
// If I am looking at someone else, look directly at one of their eyes
|
// If I am looking at someone else, look directly at one of their eyes
|
||||||
isLookingAtSomeone = true;
|
|
||||||
auto lookingAtHead = avatar->getHead();
|
auto lookingAtHead = avatar->getHead();
|
||||||
|
|
||||||
const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE;
|
const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE;
|
||||||
|
@ -6711,28 +6627,14 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera)
|
||||||
if (headPose.isValid()) {
|
if (headPose.isValid()) {
|
||||||
lookAtSpot = transformPoint(headPose.getMatrix(), glm::vec3(0.0f, 0.0f, TREE_SCALE));
|
lookAtSpot = transformPoint(headPose.getMatrix(), glm::vec3(0.0f, 0.0f, TREE_SCALE));
|
||||||
} else {
|
} else {
|
||||||
lookAtSpot = myHead->getEyePosition() +
|
lookAtSpot = _shouldTurnToFaceCamera ?
|
||||||
(getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE));
|
myHead->getLookAtPosition() :
|
||||||
}
|
myHead->getEyePosition() + getHeadJointFrontVector() * (float)TREE_SCALE;
|
||||||
}
|
|
||||||
|
|
||||||
// Deflect the eyes a bit to match the detected gaze from the face tracker if active.
|
|
||||||
if (faceTracker && !faceTracker->isMuted()) {
|
|
||||||
float eyePitch = faceTracker->getEstimatedEyePitch();
|
|
||||||
float eyeYaw = faceTracker->getEstimatedEyeYaw();
|
|
||||||
const float GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT = 0.1f;
|
|
||||||
glm::vec3 origin = myHead->getEyePosition();
|
|
||||||
float deflection = faceTracker->getEyeDeflection();
|
|
||||||
if (isLookingAtSomeone) {
|
|
||||||
deflection *= GAZE_DEFLECTION_REDUCTION_DURING_EYE_CONTACT;
|
|
||||||
}
|
|
||||||
lookAtSpot = origin + myCamera.getOrientation() * glm::quat(glm::radians(glm::vec3(
|
|
||||||
eyePitch * deflection, eyeYaw * deflection, 0.0f))) *
|
|
||||||
glm::inverse(myCamera.getOrientation()) * (lookAtSpot - origin);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
_eyesLookAtTarget.set(lookAtSpot);
|
||||||
getHead()->setLookAtPosition(lookAtSpot);
|
getHead()->setLookAtPosition(lookAtSpot);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6769,9 +6671,18 @@ glm::vec3 MyAvatar::aimToBlendValues(const glm::vec3& aimVector, const glm::quat
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::resetHeadLookAt() {
|
void MyAvatar::resetHeadLookAt() {
|
||||||
if (_skeletonModelLoaded) {
|
if (_skeletonModel->isLoaded()) {
|
||||||
|
if (isSeated()) {
|
||||||
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
|
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
|
||||||
HEAD_BLEND_LINEAR_ALPHA_NAME, HEAD_ALPHA_BLENDING);
|
HEAD_BLEND_LINEAR_ALPHA_NAME, 0.0f);
|
||||||
|
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
|
||||||
|
SEATED_HEAD_BLEND_LINEAR_ALPHA_NAME, 1.0f);
|
||||||
|
} else {
|
||||||
|
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
|
||||||
|
HEAD_BLEND_LINEAR_ALPHA_NAME, 1.0f);
|
||||||
|
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
|
||||||
|
SEATED_HEAD_BLEND_LINEAR_ALPHA_NAME, 0.0f);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6785,16 +6696,25 @@ void MyAvatar::resetLookAtRotation(const glm::vec3& avatarPosition, const glm::q
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::updateHeadLookAt(float deltaTime) {
|
void MyAvatar::updateHeadLookAt(float deltaTime) {
|
||||||
if (_skeletonModelLoaded) {
|
if (_skeletonModel->isLoaded()) {
|
||||||
glm::vec3 lookAtTarget = _scriptControlsHeadLookAt ? _lookAtScriptTarget : _lookAtCameraTarget;
|
glm::vec3 lookAtTarget = _scriptControlsHeadLookAt ? _lookAtScriptTarget : _lookAtCameraTarget;
|
||||||
glm::vec3 aimVector = lookAtTarget - getDefaultEyePosition();
|
glm::vec3 aimVector = lookAtTarget - getDefaultEyePosition();
|
||||||
glm::vec3 lookAtBlend = MyAvatar::aimToBlendValues(aimVector, getWorldOrientation());
|
glm::vec3 lookAtBlend = MyAvatar::aimToBlendValues(aimVector, getWorldOrientation());
|
||||||
|
if (isSeated()) {
|
||||||
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend,
|
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend,
|
||||||
HEAD_BLEND_LINEAR_ALPHA_NAME, HEAD_ALPHA_BLENDING);
|
HEAD_BLEND_LINEAR_ALPHA_NAME, 0.0f);
|
||||||
|
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend,
|
||||||
|
SEATED_HEAD_BLEND_LINEAR_ALPHA_NAME, 1.0f);
|
||||||
|
} else {
|
||||||
|
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend,
|
||||||
|
HEAD_BLEND_LINEAR_ALPHA_NAME, 1.0f);
|
||||||
|
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend,
|
||||||
|
SEATED_HEAD_BLEND_LINEAR_ALPHA_NAME, 0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
if (_scriptControlsHeadLookAt) {
|
if (_scriptControlsHeadLookAt) {
|
||||||
_scriptHeadControlTimer += deltaTime;
|
_scriptHeadControlTimer += deltaTime;
|
||||||
if (_scriptHeadControlTimer > MAX_LOOK_AT_TIME_SCRIPT_CONTROL) {
|
if (_scriptHeadControlTimer >= MAX_LOOK_AT_TIME_SCRIPT_CONTROL) {
|
||||||
_scriptHeadControlTimer = 0.0f;
|
_scriptHeadControlTimer = 0.0f;
|
||||||
_scriptControlsHeadLookAt = false;
|
_scriptControlsHeadLookAt = false;
|
||||||
_lookAtCameraTarget = _lookAtScriptTarget;
|
_lookAtCameraTarget = _lookAtScriptTarget;
|
||||||
|
@ -6815,6 +6735,25 @@ void MyAvatar::setHeadLookAt(const glm::vec3& lookAtTarget) {
|
||||||
_lookAtScriptTarget = lookAtTarget;
|
_lookAtScriptTarget = lookAtTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MyAvatar::setEyesLookAt(const glm::vec3& lookAtTarget) {
|
||||||
|
if (QThread::currentThread() != thread()) {
|
||||||
|
BLOCKING_INVOKE_METHOD(this, "setEyesLookAt",
|
||||||
|
Q_ARG(const glm::vec3&, lookAtTarget));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
_eyesLookAtTarget.set(lookAtTarget);
|
||||||
|
_scriptEyesControlTimer = 0.0f;
|
||||||
|
_scriptControlsEyesLookAt = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::releaseHeadLookAtControl() {
|
||||||
|
_scriptHeadControlTimer = MAX_LOOK_AT_TIME_SCRIPT_CONTROL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MyAvatar::releaseEyesLookAtControl() {
|
||||||
|
_scriptEyesControlTimer = MAX_LOOK_AT_TIME_SCRIPT_CONTROL;
|
||||||
|
}
|
||||||
|
|
||||||
glm::vec3 MyAvatar::getLookAtPivotPoint() {
|
glm::vec3 MyAvatar::getLookAtPivotPoint() {
|
||||||
glm::vec3 avatarUp = getWorldOrientation() * Vectors::UP;
|
glm::vec3 avatarUp = getWorldOrientation() * Vectors::UP;
|
||||||
glm::vec3 yAxisEyePosition = getWorldPosition() + avatarUp * glm::dot(avatarUp, _skeletonModel->getDefaultEyeModelPosition());
|
glm::vec3 yAxisEyePosition = getWorldPosition() + avatarUp * glm::dot(avatarUp, _skeletonModel->getDefaultEyeModelPosition());
|
||||||
|
@ -6888,7 +6827,7 @@ bool MyAvatar::setPointAt(const glm::vec3& pointAtTarget) {
|
||||||
Q_ARG(const glm::vec3&, pointAtTarget));
|
Q_ARG(const glm::vec3&, pointAtTarget));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
if (_skeletonModelLoaded && _pointAtActive) {
|
if (_skeletonModel->isLoaded() && _pointAtActive) {
|
||||||
glm::vec3 aimVector = pointAtTarget - getJointPosition(POINT_REF_JOINT_NAME);
|
glm::vec3 aimVector = pointAtTarget - getJointPosition(POINT_REF_JOINT_NAME);
|
||||||
_isPointTargetValid = glm::dot(aimVector, getWorldOrientation() * Vectors::FRONT) > 0.0f;
|
_isPointTargetValid = glm::dot(aimVector, getWorldOrientation() * Vectors::FRONT) > 0.0f;
|
||||||
if (_isPointTargetValid) {
|
if (_isPointTargetValid) {
|
||||||
|
@ -6902,9 +6841,8 @@ bool MyAvatar::setPointAt(const glm::vec3& pointAtTarget) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MyAvatar::resetPointAt() {
|
void MyAvatar::resetPointAt() {
|
||||||
if (_skeletonModelLoaded) {
|
if (_skeletonModel->isLoaded()) {
|
||||||
_skeletonModel->getRig().setDirectionalBlending(POINT_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
|
_skeletonModel->getRig().setDirectionalBlending(POINT_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
|
||||||
POINT_BLEND_LINEAR_ALPHA_NAME, POINT_ALPHA_BLENDING);
|
POINT_BLEND_LINEAR_ALPHA_NAME, POINT_ALPHA_BLENDING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,6 @@
|
||||||
#include "AtRestDetector.h"
|
#include "AtRestDetector.h"
|
||||||
#include "MyCharacterController.h"
|
#include "MyCharacterController.h"
|
||||||
#include "RingBufferHistory.h"
|
#include "RingBufferHistory.h"
|
||||||
#include "devices/DdeFaceTracker.h"
|
|
||||||
|
|
||||||
class AvatarActionHold;
|
class AvatarActionHold;
|
||||||
class ModelItemID;
|
class ModelItemID;
|
||||||
|
@ -184,12 +183,6 @@ class MyAvatar : public Avatar {
|
||||||
* property value is <code>audioListenerModeCustom</code>.
|
* property value is <code>audioListenerModeCustom</code>.
|
||||||
* @property {Quat} customListenOrientation=Quat.IDENTITY - The listening orientation used when the
|
* @property {Quat} customListenOrientation=Quat.IDENTITY - The listening orientation used when the
|
||||||
* <code>audioListenerMode</code> property value is <code>audioListenerModeCustom</code>.
|
* <code>audioListenerMode</code> property value is <code>audioListenerModeCustom</code>.
|
||||||
* @property {boolean} hasScriptedBlendshapes=false - <code>true</code> to transmit blendshapes over the network.
|
|
||||||
* <p><strong>Note:</strong> Currently doesn't work. Use {@link MyAvatar.setForceFaceTrackerConnected} instead.</p>
|
|
||||||
* @property {boolean} hasProceduralBlinkFaceMovement=true - <code>true</code> if procedural blinking is turned on.
|
|
||||||
* @property {boolean} hasProceduralEyeFaceMovement=true - <code>true</code> if procedural eye movement is turned on.
|
|
||||||
* @property {boolean} hasAudioEnabledFaceMovement=true - <code>true</code> to move the mouth blendshapes with voice audio
|
|
||||||
* when <code>MyAvatar.hasScriptedBlendshapes</code> is enabled.
|
|
||||||
* @property {number} rotationRecenterFilterLength - Configures how quickly the avatar root rotates to recenter its facing
|
* @property {number} rotationRecenterFilterLength - Configures how quickly the avatar root rotates to recenter its facing
|
||||||
* direction to match that of the user's torso based on head and hands orientation. A smaller value makes the
|
* direction to match that of the user's torso based on head and hands orientation. A smaller value makes the
|
||||||
* recentering happen more quickly. The minimum value is <code>0.01</code>.
|
* recentering happen more quickly. The minimum value is <code>0.01</code>.
|
||||||
|
@ -275,7 +268,7 @@ class MyAvatar : public Avatar {
|
||||||
* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme.
|
* @property {number} analogPlusSprintSpeed - The sprint (run) speed of your avatar for the "AnalogPlus" control scheme.
|
||||||
* @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior.
|
* @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior.
|
||||||
* @property {number} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting
|
* @property {number} isInSittingState - <code>true</code> if the user wearing the HMD is determined to be sitting
|
||||||
* (avatar leaning is disabled, recenntering is enabled), <code>false</code> if the user wearing the HMD is
|
* (avatar leaning is disabled, recentering is enabled), <code>false</code> if the user wearing the HMD is
|
||||||
* determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far).
|
* determined to be standing (avatar leaning is enabled, and avatar recenters if it leans too far).
|
||||||
* If <code>userRecenterModel == 2</code> (i.e., auto) the property value automatically updates as the user sits
|
* If <code>userRecenterModel == 2</code> (i.e., auto) the property value automatically updates as the user sits
|
||||||
* or stands, unless <code>isSitStandStateLocked == true</code>. Setting the property value overrides the current
|
* or stands, unless <code>isSitStandStateLocked == true</code>. Setting the property value overrides the current
|
||||||
|
@ -312,7 +305,10 @@ class MyAvatar : public Avatar {
|
||||||
* @borrows Avatar.setAttachmentsVariant as setAttachmentsVariant
|
* @borrows Avatar.setAttachmentsVariant as setAttachmentsVariant
|
||||||
* @borrows Avatar.updateAvatarEntity as updateAvatarEntity
|
* @borrows Avatar.updateAvatarEntity as updateAvatarEntity
|
||||||
* @borrows Avatar.clearAvatarEntity as clearAvatarEntity
|
* @borrows Avatar.clearAvatarEntity as clearAvatarEntity
|
||||||
* @borrows Avatar.setForceFaceTrackerConnected as setForceFaceTrackerConnected
|
* @borrows Avatar.hasScriptedBlendshapes as hasScriptedBlendshapes
|
||||||
|
* @borrows Avatar.hasProceduralBlinkFaceMovement as hasProceduralBlinkFaceMovement
|
||||||
|
* @borrows Avatar.hasProceduralEyeFaceMovement as hasProceduralEyeFaceMovement
|
||||||
|
* @borrows Avatar.hasAudioEnabledFaceMovement as hasAudioEnabledFaceMovement
|
||||||
* @borrows Avatar.setSkeletonModelURL as setSkeletonModelURL
|
* @borrows Avatar.setSkeletonModelURL as setSkeletonModelURL
|
||||||
* @borrows Avatar.getAttachmentData as getAttachmentData
|
* @borrows Avatar.getAttachmentData as getAttachmentData
|
||||||
* @borrows Avatar.setAttachmentData as setAttachmentData
|
* @borrows Avatar.setAttachmentData as setAttachmentData
|
||||||
|
@ -359,10 +355,6 @@ class MyAvatar : public Avatar {
|
||||||
Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom)
|
Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom)
|
||||||
Q_PROPERTY(glm::vec3 customListenPosition READ getCustomListenPosition WRITE setCustomListenPosition)
|
Q_PROPERTY(glm::vec3 customListenPosition READ getCustomListenPosition WRITE setCustomListenPosition)
|
||||||
Q_PROPERTY(glm::quat customListenOrientation READ getCustomListenOrientation WRITE setCustomListenOrientation)
|
Q_PROPERTY(glm::quat customListenOrientation READ getCustomListenOrientation WRITE setCustomListenOrientation)
|
||||||
Q_PROPERTY(bool hasScriptedBlendshapes READ getHasScriptedBlendshapes WRITE setHasScriptedBlendshapes)
|
|
||||||
Q_PROPERTY(bool hasProceduralBlinkFaceMovement READ getHasProceduralBlinkFaceMovement WRITE setHasProceduralBlinkFaceMovement)
|
|
||||||
Q_PROPERTY(bool hasProceduralEyeFaceMovement READ getHasProceduralEyeFaceMovement WRITE setHasProceduralEyeFaceMovement)
|
|
||||||
Q_PROPERTY(bool hasAudioEnabledFaceMovement READ getHasAudioEnabledFaceMovement WRITE setHasAudioEnabledFaceMovement)
|
|
||||||
Q_PROPERTY(float rotationRecenterFilterLength READ getRotationRecenterFilterLength WRITE setRotationRecenterFilterLength)
|
Q_PROPERTY(float rotationRecenterFilterLength READ getRotationRecenterFilterLength WRITE setRotationRecenterFilterLength)
|
||||||
Q_PROPERTY(float rotationThreshold READ getRotationThreshold WRITE setRotationThreshold)
|
Q_PROPERTY(float rotationThreshold READ getRotationThreshold WRITE setRotationThreshold)
|
||||||
Q_PROPERTY(bool enableStepResetRotation READ getEnableStepResetRotation WRITE setEnableStepResetRotation)
|
Q_PROPERTY(bool enableStepResetRotation READ getEnableStepResetRotation WRITE setEnableStepResetRotation)
|
||||||
|
@ -1765,10 +1757,38 @@ public:
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Returns the current head look at target point in world coordinates.
|
* Returns the current head look at target point in world coordinates.
|
||||||
* @function MyAvatar.getHeadLookAt
|
* @function MyAvatar.getHeadLookAt
|
||||||
* @returns {Vec3} Default position between your avatar's eyes in world coordinates.
|
* @returns {Vec3} The head's look at target in world coordinates.
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE glm::vec3 getHeadLookAt() { return _lookAtCameraTarget; }
|
Q_INVOKABLE glm::vec3 getHeadLookAt() { return _lookAtCameraTarget; }
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* When this function is called the engine regains control of the head immediately.
|
||||||
|
* @function MyAvatar.releaseHeadLookAtControl
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void releaseHeadLookAtControl();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Force the avatar's eyes to look to the specified location.
|
||||||
|
* Once this method is called, API calls will have full control of the eyes for a limited time.
|
||||||
|
* If this method is not called for two seconds, the engine will regain control of the eyes.
|
||||||
|
* @function MyAvatar.setEyesLookAt
|
||||||
|
* @param {Vec3} lookAtTarget - The target point in world coordinates.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void setEyesLookAt(const glm::vec3& lookAtTarget);
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Returns the current eyes look at target point in world coordinates.
|
||||||
|
* @function MyAvatar.getEyesLookAt
|
||||||
|
* @returns {Vec3} The eyes's look at target in world coordinates.
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE glm::vec3 getEyesLookAt() { return _eyesLookAtTarget.get(); }
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* When this function is called the engine regains control of the eyes immediately.
|
||||||
|
* @function MyAvatar.releaseEyesLookAtControl
|
||||||
|
*/
|
||||||
|
Q_INVOKABLE void releaseEyesLookAtControl();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Aims the pointing directional blending towards the provided target point.
|
* Aims the pointing directional blending towards the provided target point.
|
||||||
* The "point" reaction should be triggered before using this method.
|
* The "point" reaction should be triggered before using this method.
|
||||||
|
@ -1906,7 +1926,7 @@ public:
|
||||||
bool getFlowActive() const;
|
bool getFlowActive() const;
|
||||||
bool getNetworkGraphActive() const;
|
bool getNetworkGraphActive() const;
|
||||||
|
|
||||||
void updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera);
|
void updateEyesLookAtPosition(float deltaTime);
|
||||||
|
|
||||||
// sets the reaction enabled and triggered parameters of the passed in params
|
// sets the reaction enabled and triggered parameters of the passed in params
|
||||||
// also clears internal reaction triggers
|
// also clears internal reaction triggers
|
||||||
|
@ -2439,6 +2459,13 @@ signals:
|
||||||
*/
|
*/
|
||||||
void onLoadComplete();
|
void onLoadComplete();
|
||||||
|
|
||||||
|
/**jsdoc
|
||||||
|
* Triggered when the avatar's model has failed to load.
|
||||||
|
* @function MyAvatar.onLoadFailed
|
||||||
|
* @returns {Signal}
|
||||||
|
*/
|
||||||
|
void onLoadFailed();
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Triggered when your avatar changes from being active to being away.
|
* Triggered when your avatar changes from being active to being away.
|
||||||
* @function MyAvatar.wentAway
|
* @function MyAvatar.wentAway
|
||||||
|
@ -2551,20 +2578,11 @@ private:
|
||||||
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override;
|
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override;
|
||||||
|
|
||||||
void simulate(float deltaTime, bool inView) override;
|
void simulate(float deltaTime, bool inView) override;
|
||||||
void updateFromTrackers(float deltaTime);
|
|
||||||
void saveAvatarUrl();
|
void saveAvatarUrl();
|
||||||
virtual void render(RenderArgs* renderArgs) override;
|
virtual void render(RenderArgs* renderArgs) override;
|
||||||
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override;
|
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override;
|
||||||
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; setEnableMeshVisible(shouldRender); }
|
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; setEnableMeshVisible(shouldRender); }
|
||||||
bool getShouldRenderLocally() const { return _shouldRender; }
|
bool getShouldRenderLocally() const { return _shouldRender; }
|
||||||
void setHasScriptedBlendshapes(bool hasScriptedBlendshapes);
|
|
||||||
bool getHasScriptedBlendshapes() const override { return _hasScriptedBlendShapes; }
|
|
||||||
void setHasProceduralBlinkFaceMovement(bool hasProceduralBlinkFaceMovement);
|
|
||||||
bool getHasProceduralBlinkFaceMovement() const override { return _headData->getHasProceduralBlinkFaceMovement(); }
|
|
||||||
void setHasProceduralEyeFaceMovement(bool hasProceduralEyeFaceMovement);
|
|
||||||
bool getHasProceduralEyeFaceMovement() const override { return _headData->getHasProceduralEyeFaceMovement(); }
|
|
||||||
void setHasAudioEnabledFaceMovement(bool hasAudioEnabledFaceMovement);
|
|
||||||
bool getHasAudioEnabledFaceMovement() const override { return _headData->getHasAudioEnabledFaceMovement(); }
|
|
||||||
void setRotationRecenterFilterLength(float length);
|
void setRotationRecenterFilterLength(float length);
|
||||||
float getRotationRecenterFilterLength() const { return _rotationRecenterFilterLength; }
|
float getRotationRecenterFilterLength() const { return _rotationRecenterFilterLength; }
|
||||||
void setRotationThreshold(float angleRadians);
|
void setRotationThreshold(float angleRadians);
|
||||||
|
@ -2666,6 +2684,9 @@ private:
|
||||||
|
|
||||||
eyeContactTarget _eyeContactTarget;
|
eyeContactTarget _eyeContactTarget;
|
||||||
float _eyeContactTargetTimer { 0.0f };
|
float _eyeContactTargetTimer { 0.0f };
|
||||||
|
ThreadSafeValueCache<glm::vec3> _eyesLookAtTarget { glm::vec3() };
|
||||||
|
bool _scriptControlsEyesLookAt{ false };
|
||||||
|
float _scriptEyesControlTimer{ 0.0f };
|
||||||
|
|
||||||
glm::vec3 _trackedHeadPosition;
|
glm::vec3 _trackedHeadPosition;
|
||||||
|
|
||||||
|
@ -2899,7 +2920,6 @@ private:
|
||||||
|
|
||||||
bool _haveReceivedHeightLimitsFromDomain { false };
|
bool _haveReceivedHeightLimitsFromDomain { false };
|
||||||
int _disableHandTouchCount { 0 };
|
int _disableHandTouchCount { 0 };
|
||||||
bool _skeletonModelLoaded { false };
|
|
||||||
bool _reloadAvatarEntityDataFromSettings { true };
|
bool _reloadAvatarEntityDataFromSettings { true };
|
||||||
|
|
||||||
TimePoint _nextTraitsSendWindow;
|
TimePoint _nextTraitsSendWindow;
|
||||||
|
|
|
@ -14,15 +14,80 @@
|
||||||
#include <NodeList.h>
|
#include <NodeList.h>
|
||||||
#include <recording/Deck.h>
|
#include <recording/Deck.h>
|
||||||
#include <Rig.h>
|
#include <Rig.h>
|
||||||
#include <trackers/FaceTracker.h>
|
#include <BlendshapeConstants.h>
|
||||||
#include <FaceshiftConstants.h>
|
|
||||||
|
|
||||||
#include "devices/DdeFaceTracker.h"
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
#include "MyAvatar.h"
|
#include "MyAvatar.h"
|
||||||
|
|
||||||
using namespace std;
|
using namespace std;
|
||||||
|
|
||||||
|
static controller::Action blendshapeActions[] = {
|
||||||
|
controller::Action::EYEBLINK_L,
|
||||||
|
controller::Action::EYEBLINK_R,
|
||||||
|
controller::Action::EYESQUINT_L,
|
||||||
|
controller::Action::EYESQUINT_R,
|
||||||
|
controller::Action::EYEDOWN_L,
|
||||||
|
controller::Action::EYEDOWN_R,
|
||||||
|
controller::Action::EYEIN_L,
|
||||||
|
controller::Action::EYEIN_R,
|
||||||
|
controller::Action::EYEOPEN_L,
|
||||||
|
controller::Action::EYEOPEN_R,
|
||||||
|
controller::Action::EYEOUT_L,
|
||||||
|
controller::Action::EYEOUT_R,
|
||||||
|
controller::Action::EYEUP_L,
|
||||||
|
controller::Action::EYEUP_R,
|
||||||
|
controller::Action::BROWSD_L,
|
||||||
|
controller::Action::BROWSD_R,
|
||||||
|
controller::Action::BROWSU_C,
|
||||||
|
controller::Action::BROWSU_L,
|
||||||
|
controller::Action::BROWSU_R,
|
||||||
|
controller::Action::JAWFWD,
|
||||||
|
controller::Action::JAWLEFT,
|
||||||
|
controller::Action::JAWOPEN,
|
||||||
|
controller::Action::JAWRIGHT,
|
||||||
|
controller::Action::MOUTHLEFT,
|
||||||
|
controller::Action::MOUTHRIGHT,
|
||||||
|
controller::Action::MOUTHFROWN_L,
|
||||||
|
controller::Action::MOUTHFROWN_R,
|
||||||
|
controller::Action::MOUTHSMILE_L,
|
||||||
|
controller::Action::MOUTHSMILE_R,
|
||||||
|
controller::Action::MOUTHDIMPLE_L,
|
||||||
|
controller::Action::MOUTHDIMPLE_R,
|
||||||
|
controller::Action::LIPSSTRETCH_L,
|
||||||
|
controller::Action::LIPSSTRETCH_R,
|
||||||
|
controller::Action::LIPSUPPERCLOSE,
|
||||||
|
controller::Action::LIPSLOWERCLOSE,
|
||||||
|
controller::Action::LIPSUPPEROPEN,
|
||||||
|
controller::Action::LIPSLOWEROPEN,
|
||||||
|
controller::Action::LIPSFUNNEL,
|
||||||
|
controller::Action::LIPSPUCKER,
|
||||||
|
controller::Action::PUFF,
|
||||||
|
controller::Action::CHEEKSQUINT_L,
|
||||||
|
controller::Action::CHEEKSQUINT_R,
|
||||||
|
controller::Action::MOUTHCLOSE,
|
||||||
|
controller::Action::MOUTHUPPERUP_L,
|
||||||
|
controller::Action::MOUTHUPPERUP_R,
|
||||||
|
controller::Action::MOUTHLOWERDOWN_L,
|
||||||
|
controller::Action::MOUTHLOWERDOWN_R,
|
||||||
|
controller::Action::MOUTHPRESS_L,
|
||||||
|
controller::Action::MOUTHPRESS_R,
|
||||||
|
controller::Action::MOUTHSHRUGLOWER,
|
||||||
|
controller::Action::MOUTHSHRUGUPPER,
|
||||||
|
controller::Action::NOSESNEER_L,
|
||||||
|
controller::Action::NOSESNEER_R,
|
||||||
|
controller::Action::TONGUEOUT,
|
||||||
|
controller::Action::USERBLENDSHAPE0,
|
||||||
|
controller::Action::USERBLENDSHAPE1,
|
||||||
|
controller::Action::USERBLENDSHAPE2,
|
||||||
|
controller::Action::USERBLENDSHAPE3,
|
||||||
|
controller::Action::USERBLENDSHAPE4,
|
||||||
|
controller::Action::USERBLENDSHAPE5,
|
||||||
|
controller::Action::USERBLENDSHAPE6,
|
||||||
|
controller::Action::USERBLENDSHAPE7,
|
||||||
|
controller::Action::USERBLENDSHAPE8,
|
||||||
|
controller::Action::USERBLENDSHAPE9
|
||||||
|
};
|
||||||
|
|
||||||
MyHead::MyHead(MyAvatar* owningAvatar) : Head(owningAvatar) {
|
MyHead::MyHead(MyAvatar* owningAvatar) : Head(owningAvatar) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,36 +111,57 @@ void MyHead::simulate(float deltaTime) {
|
||||||
auto player = DependencyManager::get<recording::Deck>();
|
auto player = DependencyManager::get<recording::Deck>();
|
||||||
// Only use face trackers when not playing back a recording.
|
// Only use face trackers when not playing back a recording.
|
||||||
if (!player->isPlaying()) {
|
if (!player->isPlaying()) {
|
||||||
// TODO -- finish removing face-tracker specific code. To do this, add input channels for
|
|
||||||
// each blendshape-coefficient and update the various json files to relay them in a useful way.
|
|
||||||
// After that, input plugins can be used to drive the avatar's face, and the various "DDE" files
|
|
||||||
// can be ported into the plugin and removed.
|
|
||||||
//
|
|
||||||
// auto faceTracker = qApp->getActiveFaceTracker();
|
|
||||||
// const bool hasActualFaceTrackerConnected = faceTracker && !faceTracker->isMuted();
|
|
||||||
// _isFaceTrackerConnected = hasActualFaceTrackerConnected || _owningAvatar->getHasScriptedBlendshapes();
|
|
||||||
// if (_isFaceTrackerConnected) {
|
|
||||||
// if (hasActualFaceTrackerConnected) {
|
|
||||||
// _blendshapeCoefficients = faceTracker->getBlendshapeCoefficients();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
auto userInputMapper = DependencyManager::get<UserInputMapper>();
|
||||||
|
|
||||||
|
// if input system has control over blink blendshapes
|
||||||
bool eyeLidsTracked =
|
bool eyeLidsTracked =
|
||||||
userInputMapper->getActionStateValid(controller::Action::LEFT_EYE_BLINK) &&
|
userInputMapper->getActionStateValid(controller::Action::EYEBLINK_L) ||
|
||||||
userInputMapper->getActionStateValid(controller::Action::RIGHT_EYE_BLINK);
|
userInputMapper->getActionStateValid(controller::Action::EYEBLINK_R);
|
||||||
setFaceTrackerConnected(eyeLidsTracked);
|
|
||||||
if (eyeLidsTracked) {
|
// if input system has control over the brows.
|
||||||
float leftEyeBlink = userInputMapper->getActionState(controller::Action::LEFT_EYE_BLINK);
|
bool browsTracked =
|
||||||
float rightEyeBlink = userInputMapper->getActionState(controller::Action::RIGHT_EYE_BLINK);
|
userInputMapper->getActionStateValid(controller::Action::BROWSD_L) ||
|
||||||
_blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2));
|
userInputMapper->getActionStateValid(controller::Action::BROWSD_R) ||
|
||||||
_blendshapeCoefficients[EYE_BLINK_INDICES[0]] = leftEyeBlink;
|
userInputMapper->getActionStateValid(controller::Action::BROWSU_L) ||
|
||||||
_blendshapeCoefficients[EYE_BLINK_INDICES[1]] = rightEyeBlink;
|
userInputMapper->getActionStateValid(controller::Action::BROWSU_R) ||
|
||||||
} else {
|
userInputMapper->getActionStateValid(controller::Action::BROWSU_C);
|
||||||
const float FULLY_OPEN = 0.0f;
|
|
||||||
_blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2));
|
// if input system has control of mouth
|
||||||
_blendshapeCoefficients[EYE_BLINK_INDICES[0]] = FULLY_OPEN;
|
bool mouthTracked =
|
||||||
_blendshapeCoefficients[EYE_BLINK_INDICES[1]] = FULLY_OPEN;
|
userInputMapper->getActionStateValid(controller::Action::JAWOPEN) ||
|
||||||
|
userInputMapper->getActionStateValid(controller::Action::LIPSUPPERCLOSE) ||
|
||||||
|
userInputMapper->getActionStateValid(controller::Action::LIPSLOWERCLOSE) ||
|
||||||
|
userInputMapper->getActionStateValid(controller::Action::LIPSFUNNEL) ||
|
||||||
|
userInputMapper->getActionStateValid(controller::Action::MOUTHSMILE_L) ||
|
||||||
|
userInputMapper->getActionStateValid(controller::Action::MOUTHSMILE_R);
|
||||||
|
|
||||||
|
bool eyesTracked =
|
||||||
|
userInputMapper->getPoseState(controller::Action::LEFT_EYE).valid &&
|
||||||
|
userInputMapper->getPoseState(controller::Action::RIGHT_EYE).valid;
|
||||||
|
|
||||||
|
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||||
|
int leftEyeJointIndex = myAvatar->getJointIndex("LeftEye");
|
||||||
|
int rightEyeJointIndex = myAvatar->getJointIndex("RightEye");
|
||||||
|
bool eyeJointsOverridden = myAvatar->getIsJointOverridden(leftEyeJointIndex) || myAvatar->getIsJointOverridden(rightEyeJointIndex);
|
||||||
|
|
||||||
|
bool anyInputTracked = false;
|
||||||
|
for (int i = 0; i < (int)Blendshapes::BlendshapeCount; i++) {
|
||||||
|
anyInputTracked = anyInputTracked || userInputMapper->getActionStateValid(blendshapeActions[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
setHasInputDrivenBlendshapes(anyInputTracked);
|
||||||
|
|
||||||
|
// suppress any procedural blendshape animation if they overlap with driven input.
|
||||||
|
setSuppressProceduralAnimationFlag(HeadData::BlinkProceduralBlendshapeAnimation, eyeLidsTracked);
|
||||||
|
setSuppressProceduralAnimationFlag(HeadData::LidAdjustmentProceduralBlendshapeAnimation, eyeLidsTracked || browsTracked);
|
||||||
|
setSuppressProceduralAnimationFlag(HeadData::AudioProceduralBlendshapeAnimation, mouthTracked);
|
||||||
|
setSuppressProceduralAnimationFlag(HeadData::SaccadeProceduralEyeJointAnimation, eyesTracked || eyeJointsOverridden);
|
||||||
|
|
||||||
|
if (anyInputTracked) {
|
||||||
|
for (int i = 0; i < (int)Blendshapes::BlendshapeCount; i++) {
|
||||||
|
_blendshapeCoefficients[i] = userInputMapper->getActionState(blendshapeActions[i]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Parent::simulate(deltaTime);
|
Parent::simulate(deltaTime);
|
||||||
|
|
|
@ -112,9 +112,13 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
||||||
void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||||
const HFMModel& hfmModel = getHFMModel();
|
const HFMModel& hfmModel = getHFMModel();
|
||||||
|
|
||||||
|
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||||
|
assert(myAvatar);
|
||||||
|
|
||||||
Head* head = _owningAvatar->getHead();
|
Head* head = _owningAvatar->getHead();
|
||||||
|
|
||||||
bool eyePosesValid = !head->getHasProceduralEyeMovement();
|
bool eyePosesValid = (myAvatar->getControllerPoseInSensorFrame(controller::Action::LEFT_EYE).isValid() ||
|
||||||
|
myAvatar->getControllerPoseInSensorFrame(controller::Action::RIGHT_EYE).isValid());
|
||||||
glm::vec3 lookAt;
|
glm::vec3 lookAt;
|
||||||
if (eyePosesValid) {
|
if (eyePosesValid) {
|
||||||
lookAt = head->getLookAtPosition(); // don't apply no-crosseyes code when eyes are being tracked
|
lookAt = head->getLookAtPosition(); // don't apply no-crosseyes code when eyes are being tracked
|
||||||
|
@ -122,9 +126,6 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||||
lookAt = avoidCrossedEyes(head->getLookAtPosition());
|
lookAt = avoidCrossedEyes(head->getLookAtPosition());
|
||||||
}
|
}
|
||||||
|
|
||||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
|
||||||
assert(myAvatar);
|
|
||||||
|
|
||||||
Rig::ControllerParameters params;
|
Rig::ControllerParameters params;
|
||||||
|
|
||||||
AnimPose avatarToRigPose(glm::vec3(1.0f), Quaternions::Y_180, glm::vec3(0.0f));
|
AnimPose avatarToRigPose(glm::vec3(1.0f), Quaternions::Y_180, glm::vec3(0.0f));
|
||||||
|
|
|
@ -267,6 +267,7 @@ void OtherAvatar::simulate(float deltaTime, bool inView) {
|
||||||
_skeletonModel->getRig().computeExternalPoses(rootTransform);
|
_skeletonModel->getRig().computeExternalPoses(rootTransform);
|
||||||
_jointDataSimulationRate.increment();
|
_jointDataSimulationRate.increment();
|
||||||
|
|
||||||
|
head->simulate(deltaTime);
|
||||||
_skeletonModel->simulate(deltaTime, true);
|
_skeletonModel->simulate(deltaTime, true);
|
||||||
|
|
||||||
locationChanged(); // joints changed, so if there are any children, update them.
|
locationChanged(); // joints changed, so if there are any children, update them.
|
||||||
|
@ -277,9 +278,11 @@ void OtherAvatar::simulate(float deltaTime, bool inView) {
|
||||||
headPosition = getWorldPosition();
|
headPosition = getWorldPosition();
|
||||||
}
|
}
|
||||||
head->setPosition(headPosition);
|
head->setPosition(headPosition);
|
||||||
|
} else {
|
||||||
|
head->simulate(deltaTime);
|
||||||
|
_skeletonModel->simulate(deltaTime, false);
|
||||||
}
|
}
|
||||||
head->setScale(getModelScale());
|
head->setScale(getModelScale());
|
||||||
head->simulate(deltaTime);
|
|
||||||
relayJointDataToChildren();
|
relayJointDataToChildren();
|
||||||
} else {
|
} else {
|
||||||
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
|
// a non-full update is still required so that the position, rotation, scale and bounds of the skeletonModel are updated.
|
||||||
|
|
|
@ -1,686 +0,0 @@
|
||||||
//
|
|
||||||
// DdeFaceTracker.cpp
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Clement on 8/2/14.
|
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "DdeFaceTracker.h"
|
|
||||||
|
|
||||||
#include <SharedUtil.h>
|
|
||||||
|
|
||||||
#include <QtCore/QCoreApplication>
|
|
||||||
#include <QtCore/QJsonDocument>
|
|
||||||
#include <QtCore/QJsonArray>
|
|
||||||
#include <QtCore/QJsonObject>
|
|
||||||
#include <QtCore/QTimer>
|
|
||||||
|
|
||||||
#include <GLMHelpers.h>
|
|
||||||
#include <NumericalConstants.h>
|
|
||||||
#include <FaceshiftConstants.h>
|
|
||||||
|
|
||||||
#include "Application.h"
|
|
||||||
#include "InterfaceLogging.h"
|
|
||||||
#include "Menu.h"
|
|
||||||
|
|
||||||
|
|
||||||
static const QHostAddress DDE_SERVER_ADDR("127.0.0.1");
|
|
||||||
static const quint16 DDE_SERVER_PORT = 64204;
|
|
||||||
static const quint16 DDE_CONTROL_PORT = 64205;
|
|
||||||
#if defined(Q_OS_WIN)
|
|
||||||
static const QString DDE_PROGRAM_PATH = "/dde/dde.exe";
|
|
||||||
#elif defined(Q_OS_MAC)
|
|
||||||
static const QString DDE_PROGRAM_PATH = "/dde.app/Contents/MacOS/dde";
|
|
||||||
#endif
|
|
||||||
static const QStringList DDE_ARGUMENTS = QStringList()
|
|
||||||
<< "--udp=" + DDE_SERVER_ADDR.toString() + ":" + QString::number(DDE_SERVER_PORT)
|
|
||||||
<< "--receiver=" + QString::number(DDE_CONTROL_PORT)
|
|
||||||
<< "--facedet_interval=500" // ms
|
|
||||||
<< "--headless";
|
|
||||||
|
|
||||||
static const int NUM_EXPRESSIONS = 46;
|
|
||||||
static const int MIN_PACKET_SIZE = (8 + NUM_EXPRESSIONS) * sizeof(float) + sizeof(int);
|
|
||||||
static const int MAX_NAME_SIZE = 31;
|
|
||||||
|
|
||||||
// There's almost but not quite a 1-1 correspondence between DDE's 46 and Faceshift 1.3's 48 packets.
|
|
||||||
// The best guess at mapping is to:
|
|
||||||
// - Swap L and R values
|
|
||||||
// - Skip two Faceshift values: JawChew (22) and LipsLowerDown (37)
|
|
||||||
static const int DDE_TO_FACESHIFT_MAPPING[] = {
|
|
||||||
1, 0, 3, 2, 5, 4, 7, 6, 9, 8, 11, 10, 13, 12, 15, 14,
|
|
||||||
16,
|
|
||||||
18, 17,
|
|
||||||
19,
|
|
||||||
23,
|
|
||||||
21,
|
|
||||||
// Skip JawChew
|
|
||||||
20,
|
|
||||||
25, 24, 27, 26, 29, 28, 31, 30, 33, 32,
|
|
||||||
34, 35, 36,
|
|
||||||
// Skip LipsLowerDown
|
|
||||||
38, 39, 40, 41, 42, 43, 44, 45,
|
|
||||||
47, 46
|
|
||||||
};
|
|
||||||
|
|
||||||
// The DDE coefficients, overall, range from -0.2 to 1.5 or so. However, individual coefficients typically vary much
|
|
||||||
// less than this.
|
|
||||||
static const float DDE_COEFFICIENT_SCALES[] = {
|
|
||||||
1.0f, // EyeBlink_L
|
|
||||||
1.0f, // EyeBlink_R
|
|
||||||
1.0f, // EyeSquint_L
|
|
||||||
1.0f, // EyeSquint_R
|
|
||||||
1.0f, // EyeDown_L
|
|
||||||
1.0f, // EyeDown_R
|
|
||||||
1.0f, // EyeIn_L
|
|
||||||
1.0f, // EyeIn_R
|
|
||||||
1.0f, // EyeOpen_L
|
|
||||||
1.0f, // EyeOpen_R
|
|
||||||
1.0f, // EyeOut_L
|
|
||||||
1.0f, // EyeOut_R
|
|
||||||
1.0f, // EyeUp_L
|
|
||||||
1.0f, // EyeUp_R
|
|
||||||
3.0f, // BrowsD_L
|
|
||||||
3.0f, // BrowsD_R
|
|
||||||
3.0f, // BrowsU_C
|
|
||||||
3.0f, // BrowsU_L
|
|
||||||
3.0f, // BrowsU_R
|
|
||||||
1.0f, // JawFwd
|
|
||||||
2.0f, // JawLeft
|
|
||||||
1.8f, // JawOpen
|
|
||||||
1.0f, // JawChew
|
|
||||||
2.0f, // JawRight
|
|
||||||
1.5f, // MouthLeft
|
|
||||||
1.5f, // MouthRight
|
|
||||||
1.5f, // MouthFrown_L
|
|
||||||
1.5f, // MouthFrown_R
|
|
||||||
2.5f, // MouthSmile_L
|
|
||||||
2.5f, // MouthSmile_R
|
|
||||||
1.0f, // MouthDimple_L
|
|
||||||
1.0f, // MouthDimple_R
|
|
||||||
1.0f, // LipsStretch_L
|
|
||||||
1.0f, // LipsStretch_R
|
|
||||||
1.0f, // LipsUpperClose
|
|
||||||
1.0f, // LipsLowerClose
|
|
||||||
1.0f, // LipsUpperUp
|
|
||||||
1.0f, // LipsLowerDown
|
|
||||||
1.0f, // LipsUpperOpen
|
|
||||||
1.0f, // LipsLowerOpen
|
|
||||||
1.5f, // LipsFunnel
|
|
||||||
2.5f, // LipsPucker
|
|
||||||
1.5f, // ChinLowerRaise
|
|
||||||
1.5f, // ChinUpperRaise
|
|
||||||
1.0f, // Sneer
|
|
||||||
3.0f, // Puff
|
|
||||||
1.0f, // CheekSquint_L
|
|
||||||
1.0f // CheekSquint_R
|
|
||||||
};
|
|
||||||
|
|
||||||
struct DDEPacket {
|
|
||||||
//roughly in mm
|
|
||||||
float focal_length[1];
|
|
||||||
float translation[3];
|
|
||||||
|
|
||||||
//quaternion
|
|
||||||
float rotation[4];
|
|
||||||
|
|
||||||
// The DDE coefficients, overall, range from -0.2 to 1.5 or so. However, individual coefficients typically vary much
|
|
||||||
// less than this.
|
|
||||||
float expressions[NUM_EXPRESSIONS];
|
|
||||||
|
|
||||||
//avatar id selected on the UI
|
|
||||||
int avatar_id;
|
|
||||||
|
|
||||||
//client name, arbitrary length
|
|
||||||
char name[MAX_NAME_SIZE + 1];
|
|
||||||
};
|
|
||||||
|
|
||||||
static const float STARTING_DDE_MESSAGE_TIME = 0.033f;
|
|
||||||
static const float DEFAULT_DDE_EYE_CLOSING_THRESHOLD = 0.8f;
|
|
||||||
static const int CALIBRATION_SAMPLES = 150;
|
|
||||||
|
|
||||||
DdeFaceTracker::DdeFaceTracker() :
|
|
||||||
DdeFaceTracker(QHostAddress::Any, DDE_SERVER_PORT, DDE_CONTROL_PORT)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
DdeFaceTracker::DdeFaceTracker(const QHostAddress& host, quint16 serverPort, quint16 controlPort) :
|
|
||||||
_ddeProcess(NULL),
|
|
||||||
_ddeStopping(false),
|
|
||||||
_host(host),
|
|
||||||
_serverPort(serverPort),
|
|
||||||
_controlPort(controlPort),
|
|
||||||
_lastReceiveTimestamp(0),
|
|
||||||
_reset(false),
|
|
||||||
_leftBlinkIndex(0), // see http://support.faceshift.com/support/articles/35129-export-of-blendshapes
|
|
||||||
_rightBlinkIndex(1),
|
|
||||||
_leftEyeDownIndex(4),
|
|
||||||
_rightEyeDownIndex(5),
|
|
||||||
_leftEyeInIndex(6),
|
|
||||||
_rightEyeInIndex(7),
|
|
||||||
_leftEyeOpenIndex(8),
|
|
||||||
_rightEyeOpenIndex(9),
|
|
||||||
_browDownLeftIndex(14),
|
|
||||||
_browDownRightIndex(15),
|
|
||||||
_browUpCenterIndex(16),
|
|
||||||
_browUpLeftIndex(17),
|
|
||||||
_browUpRightIndex(18),
|
|
||||||
_mouthSmileLeftIndex(28),
|
|
||||||
_mouthSmileRightIndex(29),
|
|
||||||
_jawOpenIndex(21),
|
|
||||||
_lastMessageReceived(0),
|
|
||||||
_averageMessageTime(STARTING_DDE_MESSAGE_TIME),
|
|
||||||
_lastHeadTranslation(glm::vec3(0.0f)),
|
|
||||||
_filteredHeadTranslation(glm::vec3(0.0f)),
|
|
||||||
_lastBrowUp(0.0f),
|
|
||||||
_filteredBrowUp(0.0f),
|
|
||||||
_eyePitch(0.0f),
|
|
||||||
_eyeYaw(0.0f),
|
|
||||||
_lastEyePitch(0.0f),
|
|
||||||
_lastEyeYaw(0.0f),
|
|
||||||
_filteredEyePitch(0.0f),
|
|
||||||
_filteredEyeYaw(0.0f),
|
|
||||||
_longTermAverageEyePitch(0.0f),
|
|
||||||
_longTermAverageEyeYaw(0.0f),
|
|
||||||
_lastEyeBlinks(),
|
|
||||||
_filteredEyeBlinks(),
|
|
||||||
_lastEyeCoefficients(),
|
|
||||||
_eyeClosingThreshold("ddeEyeClosingThreshold", DEFAULT_DDE_EYE_CLOSING_THRESHOLD),
|
|
||||||
_isCalibrating(false),
|
|
||||||
_calibrationCount(0),
|
|
||||||
_calibrationValues(),
|
|
||||||
_calibrationBillboard(NULL),
|
|
||||||
_calibrationMessage(QString()),
|
|
||||||
_isCalibrated(false)
|
|
||||||
{
|
|
||||||
_coefficients.resize(NUM_FACESHIFT_BLENDSHAPES);
|
|
||||||
_blendshapeCoefficients.resize(NUM_FACESHIFT_BLENDSHAPES);
|
|
||||||
_coefficientAverages.resize(NUM_FACESHIFT_BLENDSHAPES);
|
|
||||||
_calibrationValues.resize(NUM_FACESHIFT_BLENDSHAPES);
|
|
||||||
|
|
||||||
_eyeStates[0] = EYE_UNCONTROLLED;
|
|
||||||
_eyeStates[1] = EYE_UNCONTROLLED;
|
|
||||||
|
|
||||||
connect(&_udpSocket, SIGNAL(readyRead()), SLOT(readPendingDatagrams()));
|
|
||||||
connect(&_udpSocket, SIGNAL(error(QAbstractSocket::SocketError)), SLOT(socketErrorOccurred(QAbstractSocket::SocketError)));
|
|
||||||
connect(&_udpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
|
|
||||||
SLOT(socketStateChanged(QAbstractSocket::SocketState)));
|
|
||||||
}
|
|
||||||
|
|
||||||
DdeFaceTracker::~DdeFaceTracker() {
|
|
||||||
setEnabled(false);
|
|
||||||
|
|
||||||
if (_isCalibrating) {
|
|
||||||
cancelCalibration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::init() {
|
|
||||||
FaceTracker::init();
|
|
||||||
setEnabled(Menu::getInstance()->isOptionChecked(MenuOption::UseCamera) && !_isMuted);
|
|
||||||
Menu::getInstance()->getActionForOption(MenuOption::CalibrateCamera)->setEnabled(!_isMuted);
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::setEnabled(bool enabled) {
|
|
||||||
if (!_isInitialized) {
|
|
||||||
// Don't enable until have explicitly initialized
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#ifdef HAVE_DDE
|
|
||||||
|
|
||||||
if (_isCalibrating) {
|
|
||||||
cancelCalibration();
|
|
||||||
}
|
|
||||||
|
|
||||||
// isOpen() does not work as one might expect on QUdpSocket; don't test isOpen() before closing socket.
|
|
||||||
_udpSocket.close();
|
|
||||||
|
|
||||||
// Terminate any existing DDE process, perhaps left running after an Interface crash.
|
|
||||||
// Do this even if !enabled in case user reset their settings after crash.
|
|
||||||
const char* DDE_EXIT_COMMAND = "exit";
|
|
||||||
_udpSocket.bind(_host, _serverPort);
|
|
||||||
_udpSocket.writeDatagram(DDE_EXIT_COMMAND, DDE_SERVER_ADDR, _controlPort);
|
|
||||||
|
|
||||||
if (enabled && !_ddeProcess) {
|
|
||||||
_ddeStopping = false;
|
|
||||||
qCDebug(interfaceapp) << "DDE Face Tracker: Starting";
|
|
||||||
_ddeProcess = new QProcess(qApp);
|
|
||||||
connect(_ddeProcess, SIGNAL(finished(int, QProcess::ExitStatus)), SLOT(processFinished(int, QProcess::ExitStatus)));
|
|
||||||
_ddeProcess->start(QCoreApplication::applicationDirPath() + DDE_PROGRAM_PATH, DDE_ARGUMENTS);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!enabled && _ddeProcess) {
|
|
||||||
_ddeStopping = true;
|
|
||||||
qCDebug(interfaceapp) << "DDE Face Tracker: Stopping";
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::processFinished(int exitCode, QProcess::ExitStatus exitStatus) {
|
|
||||||
if (_ddeProcess) {
|
|
||||||
if (_ddeStopping) {
|
|
||||||
qCDebug(interfaceapp) << "DDE Face Tracker: Stopped";
|
|
||||||
|
|
||||||
} else {
|
|
||||||
qCWarning(interfaceapp) << "DDE Face Tracker: Stopped unexpectedly";
|
|
||||||
Menu::getInstance()->setIsOptionChecked(MenuOption::NoFaceTracking, true);
|
|
||||||
}
|
|
||||||
_udpSocket.close();
|
|
||||||
delete _ddeProcess;
|
|
||||||
_ddeProcess = NULL;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::reset() {
|
|
||||||
if (_udpSocket.state() == QAbstractSocket::BoundState) {
|
|
||||||
_reset = true;
|
|
||||||
|
|
||||||
qCDebug(interfaceapp) << "DDE Face Tracker: Reset";
|
|
||||||
|
|
||||||
const char* DDE_RESET_COMMAND = "reset";
|
|
||||||
_udpSocket.writeDatagram(DDE_RESET_COMMAND, DDE_SERVER_ADDR, _controlPort);
|
|
||||||
|
|
||||||
FaceTracker::reset();
|
|
||||||
|
|
||||||
_reset = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::update(float deltaTime) {
|
|
||||||
if (!isActive()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
FaceTracker::update(deltaTime);
|
|
||||||
|
|
||||||
glm::vec3 headEulers = glm::degrees(glm::eulerAngles(_headRotation));
|
|
||||||
_estimatedEyePitch = _eyePitch - headEulers.x;
|
|
||||||
_estimatedEyeYaw = _eyeYaw - headEulers.y;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DdeFaceTracker::isActive() const {
|
|
||||||
return (_ddeProcess != NULL);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DdeFaceTracker::isTracking() const {
|
|
||||||
static const quint64 ACTIVE_TIMEOUT_USECS = 3000000; //3 secs
|
|
||||||
return (usecTimestampNow() - _lastReceiveTimestamp < ACTIVE_TIMEOUT_USECS);
|
|
||||||
}
|
|
||||||
|
|
||||||
//private slots and methods
|
|
||||||
void DdeFaceTracker::socketErrorOccurred(QAbstractSocket::SocketError socketError) {
|
|
||||||
qCWarning(interfaceapp) << "DDE Face Tracker: Socket error: " << _udpSocket.errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::socketStateChanged(QAbstractSocket::SocketState socketState) {
|
|
||||||
QString state;
|
|
||||||
switch(socketState) {
|
|
||||||
case QAbstractSocket::BoundState:
|
|
||||||
state = "Bound";
|
|
||||||
break;
|
|
||||||
case QAbstractSocket::ClosingState:
|
|
||||||
state = "Closing";
|
|
||||||
break;
|
|
||||||
case QAbstractSocket::ConnectedState:
|
|
||||||
state = "Connected";
|
|
||||||
break;
|
|
||||||
case QAbstractSocket::ConnectingState:
|
|
||||||
state = "Connecting";
|
|
||||||
break;
|
|
||||||
case QAbstractSocket::HostLookupState:
|
|
||||||
state = "Host Lookup";
|
|
||||||
break;
|
|
||||||
case QAbstractSocket::ListeningState:
|
|
||||||
state = "Listening";
|
|
||||||
break;
|
|
||||||
case QAbstractSocket::UnconnectedState:
|
|
||||||
state = "Unconnected";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
qCDebug(interfaceapp) << "DDE Face Tracker: Socket: " << state;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::readPendingDatagrams() {
|
|
||||||
QByteArray buffer;
|
|
||||||
while (_udpSocket.hasPendingDatagrams()) {
|
|
||||||
buffer.resize(_udpSocket.pendingDatagramSize());
|
|
||||||
_udpSocket.readDatagram(buffer.data(), buffer.size());
|
|
||||||
}
|
|
||||||
decodePacket(buffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
float DdeFaceTracker::getBlendshapeCoefficient(int index) const {
|
|
||||||
return (index >= 0 && index < (int)_blendshapeCoefficients.size()) ? _blendshapeCoefficients[index] : 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::decodePacket(const QByteArray& buffer) {
|
|
||||||
_lastReceiveTimestamp = usecTimestampNow();
|
|
||||||
|
|
||||||
if (buffer.size() > MIN_PACKET_SIZE) {
|
|
||||||
if (!_isCalibrated) {
|
|
||||||
calibrate();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isFiltering = Menu::getInstance()->isOptionChecked(MenuOption::VelocityFilter);
|
|
||||||
|
|
||||||
DDEPacket packet;
|
|
||||||
int bytesToCopy = glm::min((int)sizeof(packet), buffer.size());
|
|
||||||
memset(&packet.name, '\n', MAX_NAME_SIZE + 1);
|
|
||||||
memcpy(&packet, buffer.data(), bytesToCopy);
|
|
||||||
|
|
||||||
glm::vec3 translation;
|
|
||||||
memcpy(&translation, packet.translation, sizeof(packet.translation));
|
|
||||||
glm::quat rotation;
|
|
||||||
memcpy(&rotation, &packet.rotation, sizeof(packet.rotation));
|
|
||||||
if (_reset || (_lastMessageReceived == 0)) {
|
|
||||||
memcpy(&_referenceTranslation, &translation, sizeof(glm::vec3));
|
|
||||||
memcpy(&_referenceRotation, &rotation, sizeof(glm::quat));
|
|
||||||
_reset = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute relative translation
|
|
||||||
float LEAN_DAMPING_FACTOR = 75.0f;
|
|
||||||
translation -= _referenceTranslation;
|
|
||||||
translation /= LEAN_DAMPING_FACTOR;
|
|
||||||
translation.x *= -1;
|
|
||||||
if (isFiltering) {
|
|
||||||
glm::vec3 linearVelocity = (translation - _lastHeadTranslation) / _averageMessageTime;
|
|
||||||
const float LINEAR_VELOCITY_FILTER_STRENGTH = 0.3f;
|
|
||||||
float velocityFilter = glm::clamp(1.0f - glm::length(linearVelocity) *
|
|
||||||
LINEAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f);
|
|
||||||
_filteredHeadTranslation = velocityFilter * _filteredHeadTranslation + (1.0f - velocityFilter) * translation;
|
|
||||||
_lastHeadTranslation = translation;
|
|
||||||
_headTranslation = _filteredHeadTranslation;
|
|
||||||
} else {
|
|
||||||
_headTranslation = translation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Compute relative rotation
|
|
||||||
rotation = glm::inverse(_referenceRotation) * rotation;
|
|
||||||
if (isFiltering) {
|
|
||||||
glm::quat r = glm::normalize(rotation * glm::inverse(_headRotation));
|
|
||||||
float theta = 2 * acos(r.w);
|
|
||||||
glm::vec3 angularVelocity;
|
|
||||||
if (theta > EPSILON) {
|
|
||||||
float rMag = glm::length(glm::vec3(r.x, r.y, r.z));
|
|
||||||
angularVelocity = theta / _averageMessageTime * glm::vec3(r.x, r.y, r.z) / rMag;
|
|
||||||
} else {
|
|
||||||
angularVelocity = glm::vec3(0, 0, 0);
|
|
||||||
}
|
|
||||||
const float ANGULAR_VELOCITY_FILTER_STRENGTH = 0.3f;
|
|
||||||
_headRotation = safeMix(_headRotation, rotation, glm::clamp(glm::length(angularVelocity) *
|
|
||||||
ANGULAR_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f));
|
|
||||||
} else {
|
|
||||||
_headRotation = rotation;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Translate DDE coefficients to Faceshift compatible coefficients
|
|
||||||
for (int i = 0; i < NUM_EXPRESSIONS; i++) {
|
|
||||||
_coefficients[DDE_TO_FACESHIFT_MAPPING[i]] = packet.expressions[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calibration
|
|
||||||
if (_isCalibrating) {
|
|
||||||
addCalibrationDatum();
|
|
||||||
}
|
|
||||||
for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) {
|
|
||||||
_coefficients[i] -= _coefficientAverages[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use BrowsU_C to control both brows' up and down
|
|
||||||
float browUp = _coefficients[_browUpCenterIndex];
|
|
||||||
if (isFiltering) {
|
|
||||||
const float BROW_VELOCITY_FILTER_STRENGTH = 0.5f;
|
|
||||||
float velocity = fabsf(browUp - _lastBrowUp) / _averageMessageTime;
|
|
||||||
float velocityFilter = glm::clamp(velocity * BROW_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f);
|
|
||||||
_filteredBrowUp = velocityFilter * browUp + (1.0f - velocityFilter) * _filteredBrowUp;
|
|
||||||
_lastBrowUp = browUp;
|
|
||||||
browUp = _filteredBrowUp;
|
|
||||||
_coefficients[_browUpCenterIndex] = browUp;
|
|
||||||
}
|
|
||||||
_coefficients[_browUpLeftIndex] = browUp;
|
|
||||||
_coefficients[_browUpRightIndex] = browUp;
|
|
||||||
_coefficients[_browDownLeftIndex] = -browUp;
|
|
||||||
_coefficients[_browDownRightIndex] = -browUp;
|
|
||||||
|
|
||||||
// Offset jaw open coefficient
|
|
||||||
static const float JAW_OPEN_THRESHOLD = 0.1f;
|
|
||||||
_coefficients[_jawOpenIndex] = _coefficients[_jawOpenIndex] - JAW_OPEN_THRESHOLD;
|
|
||||||
|
|
||||||
// Offset smile coefficients
|
|
||||||
static const float SMILE_THRESHOLD = 0.5f;
|
|
||||||
_coefficients[_mouthSmileLeftIndex] = _coefficients[_mouthSmileLeftIndex] - SMILE_THRESHOLD;
|
|
||||||
_coefficients[_mouthSmileRightIndex] = _coefficients[_mouthSmileRightIndex] - SMILE_THRESHOLD;
|
|
||||||
|
|
||||||
// Eye pitch and yaw
|
|
||||||
// EyeDown coefficients work better over both +ve and -ve values than EyeUp values.
|
|
||||||
// EyeIn coefficients work better over both +ve and -ve values than EyeOut values.
|
|
||||||
// Pitch and yaw values are relative to the screen.
|
|
||||||
const float EYE_PITCH_SCALE = -1500.0f; // Sign, scale, and average to be similar to Faceshift values.
|
|
||||||
_eyePitch = EYE_PITCH_SCALE * (_coefficients[_leftEyeDownIndex] + _coefficients[_rightEyeDownIndex]);
|
|
||||||
const float EYE_YAW_SCALE = 2000.0f; // Scale and average to be similar to Faceshift values.
|
|
||||||
_eyeYaw = EYE_YAW_SCALE * (_coefficients[_leftEyeInIndex] + _coefficients[_rightEyeInIndex]);
|
|
||||||
if (isFiltering) {
|
|
||||||
const float EYE_VELOCITY_FILTER_STRENGTH = 0.005f;
|
|
||||||
float pitchVelocity = fabsf(_eyePitch - _lastEyePitch) / _averageMessageTime;
|
|
||||||
float pitchVelocityFilter = glm::clamp(pitchVelocity * EYE_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f);
|
|
||||||
_filteredEyePitch = pitchVelocityFilter * _eyePitch + (1.0f - pitchVelocityFilter) * _filteredEyePitch;
|
|
||||||
_lastEyePitch = _eyePitch;
|
|
||||||
_eyePitch = _filteredEyePitch;
|
|
||||||
float yawVelocity = fabsf(_eyeYaw - _lastEyeYaw) / _averageMessageTime;
|
|
||||||
float yawVelocityFilter = glm::clamp(yawVelocity * EYE_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f);
|
|
||||||
_filteredEyeYaw = yawVelocityFilter * _eyeYaw + (1.0f - yawVelocityFilter) * _filteredEyeYaw;
|
|
||||||
_lastEyeYaw = _eyeYaw;
|
|
||||||
_eyeYaw = _filteredEyeYaw;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Velocity filter EyeBlink values
|
|
||||||
const float DDE_EYEBLINK_SCALE = 3.0f;
|
|
||||||
float eyeBlinks[] = { DDE_EYEBLINK_SCALE * _coefficients[_leftBlinkIndex],
|
|
||||||
DDE_EYEBLINK_SCALE * _coefficients[_rightBlinkIndex] };
|
|
||||||
if (isFiltering) {
|
|
||||||
const float BLINK_VELOCITY_FILTER_STRENGTH = 0.3f;
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
float velocity = fabsf(eyeBlinks[i] - _lastEyeBlinks[i]) / _averageMessageTime;
|
|
||||||
float velocityFilter = glm::clamp(velocity * BLINK_VELOCITY_FILTER_STRENGTH, 0.0f, 1.0f);
|
|
||||||
_filteredEyeBlinks[i] = velocityFilter * eyeBlinks[i] + (1.0f - velocityFilter) * _filteredEyeBlinks[i];
|
|
||||||
_lastEyeBlinks[i] = eyeBlinks[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finesse EyeBlink values
|
|
||||||
float eyeCoefficients[2];
|
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::BinaryEyelidControl)) {
|
|
||||||
if (_eyeStates[0] == EYE_UNCONTROLLED) {
|
|
||||||
_eyeStates[0] = EYE_OPEN;
|
|
||||||
_eyeStates[1] = EYE_OPEN;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < 2; i++) {
|
|
||||||
// Scale EyeBlink values so that they can be used to control both EyeBlink and EyeOpen
|
|
||||||
// -ve values control EyeOpen; +ve values control EyeBlink
|
|
||||||
static const float EYE_CONTROL_THRESHOLD = 0.5f; // Resting eye value
|
|
||||||
eyeCoefficients[i] = (_filteredEyeBlinks[i] - EYE_CONTROL_THRESHOLD) / (1.0f - EYE_CONTROL_THRESHOLD);
|
|
||||||
|
|
||||||
// Change to closing or opening states
|
|
||||||
const float EYE_CONTROL_HYSTERISIS = 0.25f;
|
|
||||||
float eyeClosingThreshold = getEyeClosingThreshold();
|
|
||||||
float eyeOpeningThreshold = eyeClosingThreshold - EYE_CONTROL_HYSTERISIS;
|
|
||||||
if ((_eyeStates[i] == EYE_OPEN || _eyeStates[i] == EYE_OPENING) && eyeCoefficients[i] > eyeClosingThreshold) {
|
|
||||||
_eyeStates[i] = EYE_CLOSING;
|
|
||||||
} else if ((_eyeStates[i] == EYE_CLOSED || _eyeStates[i] == EYE_CLOSING)
|
|
||||||
&& eyeCoefficients[i] < eyeOpeningThreshold) {
|
|
||||||
_eyeStates[i] = EYE_OPENING;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float EYELID_MOVEMENT_RATE = 10.0f; // units/second
|
|
||||||
const float EYE_OPEN_SCALE = 0.2f;
|
|
||||||
if (_eyeStates[i] == EYE_CLOSING) {
|
|
||||||
// Close eyelid until it's fully closed
|
|
||||||
float closingValue = _lastEyeCoefficients[i] + EYELID_MOVEMENT_RATE * _averageMessageTime;
|
|
||||||
if (closingValue >= 1.0f) {
|
|
||||||
_eyeStates[i] = EYE_CLOSED;
|
|
||||||
eyeCoefficients[i] = 1.0f;
|
|
||||||
} else {
|
|
||||||
eyeCoefficients[i] = closingValue;
|
|
||||||
}
|
|
||||||
} else if (_eyeStates[i] == EYE_OPENING) {
|
|
||||||
// Open eyelid until it meets the current adjusted value
|
|
||||||
float openingValue = _lastEyeCoefficients[i] - EYELID_MOVEMENT_RATE * _averageMessageTime;
|
|
||||||
if (openingValue < eyeCoefficients[i] * EYE_OPEN_SCALE) {
|
|
||||||
_eyeStates[i] = EYE_OPEN;
|
|
||||||
eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE;
|
|
||||||
} else {
|
|
||||||
eyeCoefficients[i] = openingValue;
|
|
||||||
}
|
|
||||||
} else if (_eyeStates[i] == EYE_OPEN) {
|
|
||||||
// Reduce eyelid movement
|
|
||||||
eyeCoefficients[i] = eyeCoefficients[i] * EYE_OPEN_SCALE;
|
|
||||||
} else if (_eyeStates[i] == EYE_CLOSED) {
|
|
||||||
// Keep eyelid fully closed
|
|
||||||
eyeCoefficients[i] = 1.0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_eyeStates[0] == EYE_OPEN && _eyeStates[1] == EYE_OPEN) {
|
|
||||||
// Couple eyelids
|
|
||||||
eyeCoefficients[0] = eyeCoefficients[1] = (eyeCoefficients[0] + eyeCoefficients[0]) / 2.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
_lastEyeCoefficients[0] = eyeCoefficients[0];
|
|
||||||
_lastEyeCoefficients[1] = eyeCoefficients[1];
|
|
||||||
} else {
|
|
||||||
_eyeStates[0] = EYE_UNCONTROLLED;
|
|
||||||
_eyeStates[1] = EYE_UNCONTROLLED;
|
|
||||||
|
|
||||||
eyeCoefficients[0] = _filteredEyeBlinks[0];
|
|
||||||
eyeCoefficients[1] = _filteredEyeBlinks[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Couple eyelid values if configured - use the most "open" value for both
|
|
||||||
if (Menu::getInstance()->isOptionChecked(MenuOption::CoupleEyelids)) {
|
|
||||||
float eyeCoefficient = std::min(eyeCoefficients[0], eyeCoefficients[1]);
|
|
||||||
eyeCoefficients[0] = eyeCoefficient;
|
|
||||||
eyeCoefficients[1] = eyeCoefficient;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Use EyeBlink values to control both EyeBlink and EyeOpen
|
|
||||||
if (eyeCoefficients[0] > 0) {
|
|
||||||
_coefficients[_leftBlinkIndex] = eyeCoefficients[0];
|
|
||||||
_coefficients[_leftEyeOpenIndex] = 0.0f;
|
|
||||||
} else {
|
|
||||||
_coefficients[_leftBlinkIndex] = 0.0f;
|
|
||||||
_coefficients[_leftEyeOpenIndex] = -eyeCoefficients[0];
|
|
||||||
}
|
|
||||||
if (eyeCoefficients[1] > 0) {
|
|
||||||
_coefficients[_rightBlinkIndex] = eyeCoefficients[1];
|
|
||||||
_coefficients[_rightEyeOpenIndex] = 0.0f;
|
|
||||||
} else {
|
|
||||||
_coefficients[_rightBlinkIndex] = 0.0f;
|
|
||||||
_coefficients[_rightEyeOpenIndex] = -eyeCoefficients[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scale all coefficients
|
|
||||||
for (int i = 0; i < NUM_EXPRESSIONS; i++) {
|
|
||||||
_blendshapeCoefficients[i]
|
|
||||||
= glm::clamp(DDE_COEFFICIENT_SCALES[i] * _coefficients[i], 0.0f, 1.0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate average frame time
|
|
||||||
const float FRAME_AVERAGING_FACTOR = 0.99f;
|
|
||||||
quint64 usecsNow = usecTimestampNow();
|
|
||||||
if (_lastMessageReceived != 0) {
|
|
||||||
_averageMessageTime = FRAME_AVERAGING_FACTOR * _averageMessageTime
|
|
||||||
+ (1.0f - FRAME_AVERAGING_FACTOR) * (float)(usecsNow - _lastMessageReceived) / 1000000.0f;
|
|
||||||
}
|
|
||||||
_lastMessageReceived = usecsNow;
|
|
||||||
|
|
||||||
FaceTracker::countFrame();
|
|
||||||
|
|
||||||
} else {
|
|
||||||
qCWarning(interfaceapp) << "DDE Face Tracker: Decode error";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_isCalibrating && _calibrationCount > CALIBRATION_SAMPLES) {
|
|
||||||
finishCalibration();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::setEyeClosingThreshold(float eyeClosingThreshold) {
|
|
||||||
_eyeClosingThreshold.set(eyeClosingThreshold);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const int CALIBRATION_BILLBOARD_WIDTH = 300;
|
|
||||||
static const int CALIBRATION_BILLBOARD_HEIGHT = 120;
|
|
||||||
static QString CALIBRATION_INSTRUCTION_MESSAGE = "Hold still to calibrate camera";
|
|
||||||
|
|
||||||
void DdeFaceTracker::calibrate() {
|
|
||||||
if (!Menu::getInstance()->isOptionChecked(MenuOption::UseCamera) || _isMuted) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!_isCalibrating) {
|
|
||||||
qCDebug(interfaceapp) << "DDE Face Tracker: Calibration started";
|
|
||||||
|
|
||||||
_isCalibrating = true;
|
|
||||||
_calibrationCount = 0;
|
|
||||||
_calibrationMessage = CALIBRATION_INSTRUCTION_MESSAGE + "\n\n";
|
|
||||||
|
|
||||||
// FIXME: this overlay probably doesn't work anymore
|
|
||||||
_calibrationBillboard = new TextOverlay();
|
|
||||||
glm::vec2 viewport = qApp->getCanvasSize();
|
|
||||||
_calibrationBillboard->setX((viewport.x - CALIBRATION_BILLBOARD_WIDTH) / 2);
|
|
||||||
_calibrationBillboard->setY((viewport.y - CALIBRATION_BILLBOARD_HEIGHT) / 2);
|
|
||||||
_calibrationBillboard->setWidth(CALIBRATION_BILLBOARD_WIDTH);
|
|
||||||
_calibrationBillboard->setHeight(CALIBRATION_BILLBOARD_HEIGHT);
|
|
||||||
_calibrationBillboardID = qApp->getOverlays().addOverlay(_calibrationBillboard);
|
|
||||||
|
|
||||||
for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) {
|
|
||||||
_calibrationValues[i] = 0.0f;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::addCalibrationDatum() {
|
|
||||||
const int LARGE_TICK_INTERVAL = 30;
|
|
||||||
const int SMALL_TICK_INTERVAL = 6;
|
|
||||||
int samplesLeft = CALIBRATION_SAMPLES - _calibrationCount;
|
|
||||||
if (samplesLeft % LARGE_TICK_INTERVAL == 0) {
|
|
||||||
_calibrationMessage += QString::number(samplesLeft / LARGE_TICK_INTERVAL);
|
|
||||||
// FIXME: set overlay text
|
|
||||||
} else if (samplesLeft % SMALL_TICK_INTERVAL == 0) {
|
|
||||||
_calibrationMessage += ".";
|
|
||||||
// FIXME: set overlay text
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) {
|
|
||||||
_calibrationValues[i] += _coefficients[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
_calibrationCount += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::cancelCalibration() {
|
|
||||||
qApp->getOverlays().deleteOverlay(_calibrationBillboardID);
|
|
||||||
_calibrationBillboard = NULL;
|
|
||||||
_isCalibrating = false;
|
|
||||||
qCDebug(interfaceapp) << "DDE Face Tracker: Calibration cancelled";
|
|
||||||
}
|
|
||||||
|
|
||||||
void DdeFaceTracker::finishCalibration() {
|
|
||||||
qApp->getOverlays().deleteOverlay(_calibrationBillboardID);
|
|
||||||
_calibrationBillboard = NULL;
|
|
||||||
_isCalibrating = false;
|
|
||||||
_isCalibrated = true;
|
|
||||||
|
|
||||||
for (int i = 0; i < NUM_FACESHIFT_BLENDSHAPES; i++) {
|
|
||||||
_coefficientAverages[i] = _calibrationValues[i] / (float)CALIBRATION_SAMPLES;
|
|
||||||
}
|
|
||||||
|
|
||||||
reset();
|
|
||||||
|
|
||||||
qCDebug(interfaceapp) << "DDE Face Tracker: Calibration finished";
|
|
||||||
}
|
|
|
@ -1,181 +0,0 @@
|
||||||
//
|
|
||||||
// DdeFaceTracker.h
|
|
||||||
//
|
|
||||||
//
|
|
||||||
// Created by Clement on 8/2/14.
|
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_DdeFaceTracker_h
|
|
||||||
#define hifi_DdeFaceTracker_h
|
|
||||||
|
|
||||||
#include <QtCore/QtGlobal>
|
|
||||||
|
|
||||||
//Disabling dde due to random crashes with closing the socket on macos. all the accompanying code is wrapped with the ifdef HAVE_DDE. uncomment the define below to enable
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_OSX)
|
|
||||||
//#define HAVE_DDE
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <QProcess>
|
|
||||||
#include <QUdpSocket>
|
|
||||||
|
|
||||||
#include <DependencyManager.h>
|
|
||||||
#include <ui/overlays/TextOverlay.h>
|
|
||||||
|
|
||||||
#include <trackers/FaceTracker.h>
|
|
||||||
|
|
||||||
/**jsdoc
|
|
||||||
* The FaceTracker API helps manage facial tracking hardware.
|
|
||||||
* @namespace FaceTracker
|
|
||||||
*
|
|
||||||
* @hifi-interface
|
|
||||||
* @hifi-client-entity
|
|
||||||
* @hifi-avatar
|
|
||||||
*/
|
|
||||||
|
|
||||||
class DdeFaceTracker : public FaceTracker, public Dependency {
|
|
||||||
Q_OBJECT
|
|
||||||
SINGLETON_DEPENDENCY
|
|
||||||
|
|
||||||
public:
|
|
||||||
virtual void init() override;
|
|
||||||
virtual void reset() override;
|
|
||||||
virtual void update(float deltaTime) override;
|
|
||||||
|
|
||||||
virtual bool isActive() const override;
|
|
||||||
virtual bool isTracking() const override;
|
|
||||||
|
|
||||||
float getLeftBlink() const { return getBlendshapeCoefficient(_leftBlinkIndex); }
|
|
||||||
float getRightBlink() const { return getBlendshapeCoefficient(_rightBlinkIndex); }
|
|
||||||
float getLeftEyeOpen() const { return getBlendshapeCoefficient(_leftEyeOpenIndex); }
|
|
||||||
float getRightEyeOpen() const { return getBlendshapeCoefficient(_rightEyeOpenIndex); }
|
|
||||||
|
|
||||||
float getBrowDownLeft() const { return getBlendshapeCoefficient(_browDownLeftIndex); }
|
|
||||||
float getBrowDownRight() const { return getBlendshapeCoefficient(_browDownRightIndex); }
|
|
||||||
float getBrowUpCenter() const { return getBlendshapeCoefficient(_browUpCenterIndex); }
|
|
||||||
float getBrowUpLeft() const { return getBlendshapeCoefficient(_browUpLeftIndex); }
|
|
||||||
float getBrowUpRight() const { return getBlendshapeCoefficient(_browUpRightIndex); }
|
|
||||||
|
|
||||||
float getMouthSize() const { return getBlendshapeCoefficient(_jawOpenIndex); }
|
|
||||||
float getMouthSmileLeft() const { return getBlendshapeCoefficient(_mouthSmileLeftIndex); }
|
|
||||||
float getMouthSmileRight() const { return getBlendshapeCoefficient(_mouthSmileRightIndex); }
|
|
||||||
|
|
||||||
float getEyeClosingThreshold() { return _eyeClosingThreshold.get(); }
|
|
||||||
void setEyeClosingThreshold(float eyeClosingThreshold);
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
|
|
||||||
/**jsdoc
|
|
||||||
* @function FaceTracker.setEnabled
|
|
||||||
* @param {boolean} enabled
|
|
||||||
*/
|
|
||||||
void setEnabled(bool enabled) override;
|
|
||||||
|
|
||||||
/**jsdoc
|
|
||||||
* @function FaceTracker.calibrate
|
|
||||||
*/
|
|
||||||
void calibrate();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void processFinished(int exitCode, QProcess::ExitStatus exitStatus);
|
|
||||||
|
|
||||||
//sockets
|
|
||||||
void socketErrorOccurred(QAbstractSocket::SocketError socketError);
|
|
||||||
void readPendingDatagrams();
|
|
||||||
void socketStateChanged(QAbstractSocket::SocketState socketState);
|
|
||||||
|
|
||||||
private:
|
|
||||||
DdeFaceTracker();
|
|
||||||
DdeFaceTracker(const QHostAddress& host, quint16 serverPort, quint16 controlPort);
|
|
||||||
virtual ~DdeFaceTracker();
|
|
||||||
|
|
||||||
QProcess* _ddeProcess;
|
|
||||||
bool _ddeStopping;
|
|
||||||
|
|
||||||
QHostAddress _host;
|
|
||||||
quint16 _serverPort;
|
|
||||||
quint16 _controlPort;
|
|
||||||
|
|
||||||
float getBlendshapeCoefficient(int index) const;
|
|
||||||
void decodePacket(const QByteArray& buffer);
|
|
||||||
|
|
||||||
// sockets
|
|
||||||
QUdpSocket _udpSocket;
|
|
||||||
quint64 _lastReceiveTimestamp;
|
|
||||||
|
|
||||||
bool _reset;
|
|
||||||
glm::vec3 _referenceTranslation;
|
|
||||||
glm::quat _referenceRotation;
|
|
||||||
|
|
||||||
int _leftBlinkIndex;
|
|
||||||
int _rightBlinkIndex;
|
|
||||||
int _leftEyeDownIndex;
|
|
||||||
int _rightEyeDownIndex;
|
|
||||||
int _leftEyeInIndex;
|
|
||||||
int _rightEyeInIndex;
|
|
||||||
int _leftEyeOpenIndex;
|
|
||||||
int _rightEyeOpenIndex;
|
|
||||||
|
|
||||||
int _browDownLeftIndex;
|
|
||||||
int _browDownRightIndex;
|
|
||||||
int _browUpCenterIndex;
|
|
||||||
int _browUpLeftIndex;
|
|
||||||
int _browUpRightIndex;
|
|
||||||
|
|
||||||
int _mouthSmileLeftIndex;
|
|
||||||
int _mouthSmileRightIndex;
|
|
||||||
|
|
||||||
int _jawOpenIndex;
|
|
||||||
|
|
||||||
QVector<float> _coefficients;
|
|
||||||
|
|
||||||
quint64 _lastMessageReceived;
|
|
||||||
float _averageMessageTime;
|
|
||||||
|
|
||||||
glm::vec3 _lastHeadTranslation;
|
|
||||||
glm::vec3 _filteredHeadTranslation;
|
|
||||||
|
|
||||||
float _lastBrowUp;
|
|
||||||
float _filteredBrowUp;
|
|
||||||
|
|
||||||
float _eyePitch; // Degrees, relative to screen
|
|
||||||
float _eyeYaw;
|
|
||||||
float _lastEyePitch;
|
|
||||||
float _lastEyeYaw;
|
|
||||||
float _filteredEyePitch;
|
|
||||||
float _filteredEyeYaw;
|
|
||||||
float _longTermAverageEyePitch = 0.0f;
|
|
||||||
float _longTermAverageEyeYaw = 0.0f;
|
|
||||||
bool _longTermAverageInitialized = false;
|
|
||||||
|
|
||||||
enum EyeState {
|
|
||||||
EYE_UNCONTROLLED,
|
|
||||||
EYE_OPEN,
|
|
||||||
EYE_CLOSING,
|
|
||||||
EYE_CLOSED,
|
|
||||||
EYE_OPENING
|
|
||||||
};
|
|
||||||
EyeState _eyeStates[2];
|
|
||||||
float _lastEyeBlinks[2];
|
|
||||||
float _filteredEyeBlinks[2];
|
|
||||||
float _lastEyeCoefficients[2];
|
|
||||||
Setting::Handle<float> _eyeClosingThreshold;
|
|
||||||
|
|
||||||
QVector<float> _coefficientAverages;
|
|
||||||
|
|
||||||
bool _isCalibrating;
|
|
||||||
int _calibrationCount;
|
|
||||||
QVector<float> _calibrationValues;
|
|
||||||
TextOverlay* _calibrationBillboard;
|
|
||||||
QUuid _calibrationBillboardID;
|
|
||||||
QString _calibrationMessage;
|
|
||||||
bool _isCalibrated;
|
|
||||||
void addCalibrationDatum();
|
|
||||||
void cancelCalibration();
|
|
||||||
void finishCalibration();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_DdeFaceTracker_h
|
|
|
@ -25,10 +25,10 @@ class LaserPointerScriptingInterface : public QObject, public Dependency {
|
||||||
* represent objects for repeatedly calculating ray intersections with avatars, entities, and overlays. Ray pointers can also
|
* represent objects for repeatedly calculating ray intersections with avatars, entities, and overlays. Ray pointers can also
|
||||||
* be configured to generate events on entities and overlays intersected.
|
* be configured to generate events on entities and overlays intersected.
|
||||||
*
|
*
|
||||||
* <p class="important">Deprecated: This API is deprecated. Use {@link Pointers} instead.
|
|
||||||
*
|
|
||||||
* @namespace LaserPointers
|
* @namespace LaserPointers
|
||||||
*
|
*
|
||||||
|
* @deprecated This API is deprecated and will be removed. Use {@link Pointers} instead.
|
||||||
|
*
|
||||||
* @hifi-interface
|
* @hifi-interface
|
||||||
* @hifi-client-entity
|
* @hifi-client-entity
|
||||||
* @hifi-avatar
|
* @hifi-avatar
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
#include <shared/QtHelpers.h>
|
#include <shared/QtHelpers.h>
|
||||||
|
#include <plugins/PluginManager.h>
|
||||||
#include <plugins/DisplayPlugin.h>
|
#include <plugins/DisplayPlugin.h>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
@ -44,7 +45,8 @@ enum AudioDeviceRole {
|
||||||
SelectedDesktopRole,
|
SelectedDesktopRole,
|
||||||
SelectedHMDRole,
|
SelectedHMDRole,
|
||||||
PeakRole,
|
PeakRole,
|
||||||
InfoRole
|
InfoRole,
|
||||||
|
TypeRole
|
||||||
};
|
};
|
||||||
|
|
||||||
QHash<int, QByteArray> AudioDeviceList::_roles {
|
QHash<int, QByteArray> AudioDeviceList::_roles {
|
||||||
|
@ -52,7 +54,8 @@ QHash<int, QByteArray> AudioDeviceList::_roles {
|
||||||
{ SelectedDesktopRole, "selectedDesktop" },
|
{ SelectedDesktopRole, "selectedDesktop" },
|
||||||
{ SelectedHMDRole, "selectedHMD" },
|
{ SelectedHMDRole, "selectedHMD" },
|
||||||
{ PeakRole, "peak" },
|
{ PeakRole, "peak" },
|
||||||
{ InfoRole, "info" }
|
{ InfoRole, "info" },
|
||||||
|
{ TypeRole, "type"}
|
||||||
};
|
};
|
||||||
|
|
||||||
static QString getTargetDevice(bool hmd, QAudio::Mode mode) {
|
static QString getTargetDevice(bool hmd, QAudio::Mode mode) {
|
||||||
|
@ -60,18 +63,33 @@ static QString getTargetDevice(bool hmd, QAudio::Mode mode) {
|
||||||
auto& setting = getSetting(hmd, mode);
|
auto& setting = getSetting(hmd, mode);
|
||||||
if (setting.isSet()) {
|
if (setting.isSet()) {
|
||||||
deviceName = setting.get();
|
deviceName = setting.get();
|
||||||
} else if (hmd) {
|
|
||||||
if (mode == QAudio::AudioInput) {
|
|
||||||
deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioInDevice();
|
|
||||||
} else { // if (_mode == QAudio::AudioOutput)
|
|
||||||
deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
deviceName = HifiAudioDeviceInfo::DEFAULT_DEVICE_NAME;
|
deviceName = HifiAudioDeviceInfo::DEFAULT_DEVICE_NAME;
|
||||||
}
|
}
|
||||||
return deviceName;
|
return deviceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void checkHmdDefaultsChange(QAudio::Mode mode) {
|
||||||
|
QString name;
|
||||||
|
foreach(DisplayPluginPointer displayPlugin, PluginManager::getInstance()->getAllDisplayPlugins()) {
|
||||||
|
if (displayPlugin && displayPlugin->isHmd()) {
|
||||||
|
if (mode == QAudio::AudioInput) {
|
||||||
|
name = displayPlugin->getPreferredAudioInDevice();
|
||||||
|
} else {
|
||||||
|
name = displayPlugin->getPreferredAudioOutDevice();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!name.isEmpty()) {
|
||||||
|
auto client = DependencyManager::get<AudioClient>().data();
|
||||||
|
QMetaObject::invokeMethod(client, "setHmdAudioName",
|
||||||
|
Q_ARG(QAudio::Mode, mode),
|
||||||
|
Q_ARG(const QString&, name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Qt::ItemFlags AudioDeviceList::_flags { Qt::ItemIsSelectable | Qt::ItemIsEnabled };
|
Qt::ItemFlags AudioDeviceList::_flags { Qt::ItemIsSelectable | Qt::ItemIsEnabled };
|
||||||
|
|
||||||
AudioDeviceList::AudioDeviceList(QAudio::Mode mode) : _mode(mode) {
|
AudioDeviceList::AudioDeviceList(QAudio::Mode mode) : _mode(mode) {
|
||||||
|
@ -144,6 +162,8 @@ QVariant AudioDeviceList::data(const QModelIndex& index, int role) const {
|
||||||
return _devices.at(index.row())->selectedHMD;
|
return _devices.at(index.row())->selectedHMD;
|
||||||
} else if (role == InfoRole) {
|
} else if (role == InfoRole) {
|
||||||
return QVariant::fromValue<HifiAudioDeviceInfo>(_devices.at(index.row())->info);
|
return QVariant::fromValue<HifiAudioDeviceInfo>(_devices.at(index.row())->info);
|
||||||
|
} else if (role == TypeRole) {
|
||||||
|
return _devices.at(index.row())->type;
|
||||||
} else {
|
} else {
|
||||||
return QVariant();
|
return QVariant();
|
||||||
}
|
}
|
||||||
|
@ -167,7 +187,7 @@ void AudioDeviceList::resetDevice(bool contextIsHMD) {
|
||||||
// FIXME can't use blocking connections here, so we can't determine whether the switch succeeded or not
|
// FIXME can't use blocking connections here, so we can't determine whether the switch succeeded or not
|
||||||
// We need to have the AudioClient emit signals on switch success / failure
|
// We need to have the AudioClient emit signals on switch success / failure
|
||||||
QMetaObject::invokeMethod(client, "switchAudioDevice",
|
QMetaObject::invokeMethod(client, "switchAudioDevice",
|
||||||
Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName));
|
Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName), Q_ARG(bool, contextIsHMD));
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
bool switchResult = false;
|
bool switchResult = false;
|
||||||
|
@ -258,20 +278,28 @@ std::shared_ptr<scripting::AudioDevice> getSimilarDevice(const QString& deviceNa
|
||||||
return devices[minDistanceIndex];
|
return devices[minDistanceIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
void AudioDeviceList::onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices) {
|
|
||||||
|
void AudioDeviceList::onDevicesChanged(QAudio::Mode mode, const QList<HifiAudioDeviceInfo>& devices) {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
|
|
||||||
QList<std::shared_ptr<AudioDevice>> newDevices;
|
QList<std::shared_ptr<AudioDevice>> newDevices;
|
||||||
bool hmdIsSelected = false;
|
bool hmdIsSelected = false;
|
||||||
bool desktopIsSelected = false;
|
bool desktopIsSelected = false;
|
||||||
|
|
||||||
|
checkHmdDefaultsChange(mode);
|
||||||
|
if (!_backupSelectedDesktopDeviceName.isEmpty() && !_backupSelectedHMDDeviceName.isEmpty()) {
|
||||||
foreach(const HifiAudioDeviceInfo& deviceInfo, devices) {
|
foreach(const HifiAudioDeviceInfo& deviceInfo, devices) {
|
||||||
for (bool isHMD : {false, true}) {
|
for (bool isHMD : {false, true}) {
|
||||||
auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName;
|
auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName;
|
||||||
if (deviceInfo.deviceName() == backupSelectedDeviceName) {
|
if (deviceInfo.deviceName() == backupSelectedDeviceName) {
|
||||||
HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
|
if (isHMD && deviceInfo.getDeviceType() != HifiAudioDeviceInfo::desktop) {
|
||||||
selectedDevice = deviceInfo;
|
_selectedHMDDevice= deviceInfo;
|
||||||
backupSelectedDeviceName.clear();
|
backupSelectedDeviceName.clear();
|
||||||
|
} else if (!isHMD && deviceInfo.getDeviceType() != HifiAudioDeviceInfo::hmd) {
|
||||||
|
_selectedDesktopDevice = deviceInfo;
|
||||||
|
backupSelectedDeviceName.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -281,11 +309,19 @@ void AudioDeviceList::onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices
|
||||||
device.info = deviceInfo;
|
device.info = deviceInfo;
|
||||||
|
|
||||||
if (deviceInfo.isDefault()) {
|
if (deviceInfo.isDefault()) {
|
||||||
|
if (deviceInfo.getDeviceType() == HifiAudioDeviceInfo::desktop) {
|
||||||
if (deviceInfo.getMode() == QAudio::AudioInput) {
|
if (deviceInfo.getMode() == QAudio::AudioInput) {
|
||||||
device.display = "Computer's default microphone (recommended)";
|
device.display = "Computer's default microphone (recommended)";
|
||||||
} else {
|
} else {
|
||||||
device.display = "Computer's default audio (recommended)";
|
device.display = "Computer's default audio (recommended)";
|
||||||
}
|
}
|
||||||
|
} else if (deviceInfo.getDeviceType() == HifiAudioDeviceInfo::hmd) {
|
||||||
|
if (deviceInfo.getMode() == QAudio::AudioInput) {
|
||||||
|
device.display = "Headset's default mic (recommended)";
|
||||||
|
} else {
|
||||||
|
device.display = "Headset's default audio (recommended)";
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
device.display = device.info.deviceName()
|
device.display = device.info.deviceName()
|
||||||
.replace("High Definition", "HD")
|
.replace("High Definition", "HD")
|
||||||
|
@ -293,6 +329,19 @@ void AudioDeviceList::onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices
|
||||||
.replace(" )", ")");
|
.replace(" )", ")");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switch (deviceInfo.getDeviceType()) {
|
||||||
|
case HifiAudioDeviceInfo::hmd:
|
||||||
|
device.type = "hmd";
|
||||||
|
break;
|
||||||
|
case HifiAudioDeviceInfo::desktop:
|
||||||
|
device.type = "desktop";
|
||||||
|
break;
|
||||||
|
case HifiAudioDeviceInfo::both:
|
||||||
|
device.type = "both";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
for (bool isHMD : {false, true}) {
|
for (bool isHMD : {false, true}) {
|
||||||
HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
|
HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
|
||||||
bool& isSelected = isHMD ? device.selectedHMD : device.selectedDesktop;
|
bool& isSelected = isHMD ? device.selectedHMD : device.selectedDesktop;
|
||||||
|
@ -302,8 +351,14 @@ void AudioDeviceList::onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
//no selected device for context. fallback to saved
|
//no selected device for context. fallback to saved
|
||||||
const QString& savedDeviceName = isHMD ? _hmdSavedDeviceName : _desktopSavedDeviceName;
|
QString& savedDeviceName = isHMD ? _hmdSavedDeviceName : _desktopSavedDeviceName;
|
||||||
isSelected = (device.info.deviceName() == savedDeviceName);
|
|
||||||
|
if (device.info.deviceName() == savedDeviceName) {
|
||||||
|
if ((isHMD && device.info.getDeviceType() != HifiAudioDeviceInfo::desktop) ||
|
||||||
|
(!isHMD && device.info.getDeviceType() != HifiAudioDeviceInfo::hmd)) {
|
||||||
|
isSelected = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
|
@ -386,6 +441,9 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) {
|
||||||
connect(client, &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection);
|
connect(client, &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection);
|
||||||
connect(client, &AudioClient::peakValueListChanged, &_inputs, &AudioInputDeviceList::onPeakValueListChanged, Qt::QueuedConnection);
|
connect(client, &AudioClient::peakValueListChanged, &_inputs, &AudioInputDeviceList::onPeakValueListChanged, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
checkHmdDefaultsChange(QAudio::AudioInput);
|
||||||
|
checkHmdDefaultsChange(QAudio::AudioOutput);
|
||||||
|
|
||||||
_inputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioInput), contextIsHMD);
|
_inputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioInput), contextIsHMD);
|
||||||
_outputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioOutput), contextIsHMD);
|
_outputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioOutput), contextIsHMD);
|
||||||
|
|
||||||
|
@ -393,9 +451,11 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) {
|
||||||
const QList<HifiAudioDeviceInfo>& devicesInput = client->getAudioDevices(QAudio::AudioInput);
|
const QList<HifiAudioDeviceInfo>& devicesInput = client->getAudioDevices(QAudio::AudioInput);
|
||||||
const QList<HifiAudioDeviceInfo>& devicesOutput = client->getAudioDevices(QAudio::AudioOutput);
|
const QList<HifiAudioDeviceInfo>& devicesOutput = client->getAudioDevices(QAudio::AudioOutput);
|
||||||
|
|
||||||
|
if (devicesInput.size() > 0 && devicesOutput.size() > 0) {
|
||||||
//setup devices
|
//setup devices
|
||||||
_inputs.onDevicesChanged(devicesInput);
|
_inputs.onDevicesChanged(QAudio::AudioInput, devicesInput);
|
||||||
_outputs.onDevicesChanged(devicesOutput);
|
_outputs.onDevicesChanged(QAudio::AudioOutput, devicesOutput);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AudioDevices::~AudioDevices() {}
|
AudioDevices::~AudioDevices() {}
|
||||||
|
@ -494,14 +554,14 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<HifiAudioDevi
|
||||||
|
|
||||||
//set devices for both contexts
|
//set devices for both contexts
|
||||||
if (mode == QAudio::AudioInput) {
|
if (mode == QAudio::AudioInput) {
|
||||||
_inputs.onDevicesChanged(devices);
|
_inputs.onDevicesChanged(mode, devices);
|
||||||
|
|
||||||
static std::once_flag onceAfterInputDevicesChanged;
|
static std::once_flag onceAfterInputDevicesChanged;
|
||||||
std::call_once(onceAfterInputDevicesChanged, [&] { // we only want 'selectedDevicePlugged' signal to be handled after initial list of input devices was populated
|
std::call_once(onceAfterInputDevicesChanged, [&] { // we only want 'selectedDevicePlugged' signal to be handled after initial list of input devices was populated
|
||||||
connect(&_inputs, &AudioDeviceList::selectedDevicePlugged, this, &AudioDevices::chooseInputDevice);
|
connect(&_inputs, &AudioDeviceList::selectedDevicePlugged, this, &AudioDevices::chooseInputDevice);
|
||||||
});
|
});
|
||||||
} else { // if (mode == QAudio::AudioOutput)
|
} else { // if (mode == QAudio::AudioOutput)
|
||||||
_outputs.onDevicesChanged(devices);
|
_outputs.onDevicesChanged(mode, devices);
|
||||||
|
|
||||||
static std::once_flag onceAfterOutputDevicesChanged;
|
static std::once_flag onceAfterOutputDevicesChanged;
|
||||||
std::call_once(onceAfterOutputDevicesChanged, [&] { // we only want 'selectedDevicePlugged' signal to be handled after initial list of output devices was populated
|
std::call_once(onceAfterOutputDevicesChanged, [&] { // we only want 'selectedDevicePlugged' signal to be handled after initial list of output devices was populated
|
||||||
|
|
|
@ -29,6 +29,7 @@ public:
|
||||||
QString display;
|
QString display;
|
||||||
bool selectedDesktop { false };
|
bool selectedDesktop { false };
|
||||||
bool selectedHMD { false };
|
bool selectedHMD { false };
|
||||||
|
QString type;
|
||||||
};
|
};
|
||||||
|
|
||||||
class AudioDeviceList : public QAbstractListModel {
|
class AudioDeviceList : public QAbstractListModel {
|
||||||
|
@ -57,7 +58,7 @@ signals:
|
||||||
|
|
||||||
protected slots:
|
protected slots:
|
||||||
void onDeviceChanged(const HifiAudioDeviceInfo& device, bool isHMD);
|
void onDeviceChanged(const HifiAudioDeviceInfo& device, bool isHMD);
|
||||||
void onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices);
|
void onDevicesChanged(QAudio::Mode mode, const QList<HifiAudioDeviceInfo>& devices);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
friend class AudioDevices;
|
friend class AudioDevices;
|
||||||
|
|
|
@ -32,106 +32,187 @@ void MenuScriptingInterface::menuItemTriggered() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuScriptingInterface::addMenu(const QString& menu, const QString& grouping) {
|
void MenuScriptingInterface::addMenu(const QString& menu, const QString& grouping) {
|
||||||
QMetaObject::invokeMethod(Menu::getInstance(), "addMenu", Q_ARG(const QString&, menu), Q_ARG(const QString&, grouping));
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(menuInstance, "addMenu", Q_ARG(const QString&, menu), Q_ARG(const QString&, grouping));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuScriptingInterface::removeMenu(const QString& menu) {
|
void MenuScriptingInterface::removeMenu(const QString& menu) {
|
||||||
QMetaObject::invokeMethod(Menu::getInstance(), "removeMenu", Q_ARG(const QString&, menu));
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(menuInstance, "removeMenu", Q_ARG(const QString&, menu));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MenuScriptingInterface::menuExists(const QString& menu) {
|
bool MenuScriptingInterface::menuExists(const QString& menu) {
|
||||||
if (QThread::currentThread() == qApp->thread()) {
|
|
||||||
Menu* menuInstance = Menu::getInstance();
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QThread::currentThread() == qApp->thread()) {
|
||||||
return menuInstance && menuInstance->menuExists(menu);
|
return menuInstance && menuInstance->menuExists(menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool result { false };
|
bool result { false };
|
||||||
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuExists",
|
|
||||||
|
BLOCKING_INVOKE_METHOD(menuInstance, "menuExists",
|
||||||
Q_RETURN_ARG(bool, result),
|
Q_RETURN_ARG(bool, result),
|
||||||
Q_ARG(const QString&, menu));
|
Q_ARG(const QString&, menu));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuScriptingInterface::addSeparator(const QString& menuName, const QString& separatorName) {
|
void MenuScriptingInterface::addSeparator(const QString& menuName, const QString& separatorName) {
|
||||||
QMetaObject::invokeMethod(Menu::getInstance(), "addSeparator",
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(menuInstance, "addSeparator",
|
||||||
Q_ARG(const QString&, menuName),
|
Q_ARG(const QString&, menuName),
|
||||||
Q_ARG(const QString&, separatorName));
|
Q_ARG(const QString&, separatorName));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuScriptingInterface::removeSeparator(const QString& menuName, const QString& separatorName) {
|
void MenuScriptingInterface::removeSeparator(const QString& menuName, const QString& separatorName) {
|
||||||
QMetaObject::invokeMethod(Menu::getInstance(), "removeSeparator",
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(menuInstance, "removeSeparator",
|
||||||
Q_ARG(const QString&, menuName),
|
Q_ARG(const QString&, menuName),
|
||||||
Q_ARG(const QString&, separatorName));
|
Q_ARG(const QString&, separatorName));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuScriptingInterface::addMenuItem(const MenuItemProperties& properties) {
|
void MenuScriptingInterface::addMenuItem(const MenuItemProperties& properties) {
|
||||||
QMetaObject::invokeMethod(Menu::getInstance(), "addMenuItem", Q_ARG(const MenuItemProperties&, properties));
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(menuInstance, "addMenuItem", Q_ARG(const MenuItemProperties&, properties));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuScriptingInterface::addMenuItem(const QString& menu, const QString& menuitem, const QString& shortcutKey) {
|
void MenuScriptingInterface::addMenuItem(const QString& menu, const QString& menuitem, const QString& shortcutKey) {
|
||||||
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MenuItemProperties properties(menu, menuitem, shortcutKey);
|
MenuItemProperties properties(menu, menuitem, shortcutKey);
|
||||||
QMetaObject::invokeMethod(Menu::getInstance(), "addMenuItem", Q_ARG(const MenuItemProperties&, properties));
|
QMetaObject::invokeMethod(menuInstance, "addMenuItem", Q_ARG(const MenuItemProperties&, properties));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuScriptingInterface::addMenuItem(const QString& menu, const QString& menuitem) {
|
void MenuScriptingInterface::addMenuItem(const QString& menu, const QString& menuitem) {
|
||||||
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MenuItemProperties properties(menu, menuitem);
|
MenuItemProperties properties(menu, menuitem);
|
||||||
QMetaObject::invokeMethod(Menu::getInstance(), "addMenuItem", Q_ARG(const MenuItemProperties&, properties));
|
QMetaObject::invokeMethod(menuInstance, "addMenuItem", Q_ARG(const MenuItemProperties&, properties));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuScriptingInterface::removeMenuItem(const QString& menu, const QString& menuitem) {
|
void MenuScriptingInterface::removeMenuItem(const QString& menu, const QString& menuitem) {
|
||||||
QMetaObject::invokeMethod(Menu::getInstance(), "removeMenuItem",
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QMetaObject::invokeMethod(menuInstance, "removeMenuItem",
|
||||||
Q_ARG(const QString&, menu),
|
Q_ARG(const QString&, menu),
|
||||||
Q_ARG(const QString&, menuitem));
|
Q_ARG(const QString&, menuitem));
|
||||||
};
|
};
|
||||||
|
|
||||||
bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString& menuitem) {
|
bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString& menuitem) {
|
||||||
if (QThread::currentThread() == qApp->thread()) {
|
|
||||||
Menu* menuInstance = Menu::getInstance();
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QThread::currentThread() == qApp->thread()) {
|
||||||
return menuInstance && menuInstance->menuItemExists(menu, menuitem);
|
return menuInstance && menuInstance->menuItemExists(menu, menuitem);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool result { false };
|
bool result { false };
|
||||||
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuItemExists",
|
|
||||||
|
BLOCKING_INVOKE_METHOD(menuInstance, "menuItemExists",
|
||||||
Q_RETURN_ARG(bool, result),
|
Q_RETURN_ARG(bool, result),
|
||||||
Q_ARG(const QString&, menu),
|
Q_ARG(const QString&, menu),
|
||||||
Q_ARG(const QString&, menuitem));
|
Q_ARG(const QString&, menuitem));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) {
|
bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) {
|
||||||
if (QThread::currentThread() == qApp->thread()) {
|
|
||||||
Menu* menuInstance = Menu::getInstance();
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QThread::currentThread() == qApp->thread()) {
|
||||||
return menuInstance && menuInstance->isOptionChecked(menuOption);
|
return menuInstance && menuInstance->isOptionChecked(menuOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool result { false };
|
bool result { false };
|
||||||
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isOptionChecked",
|
|
||||||
|
BLOCKING_INVOKE_METHOD(menuInstance, "isOptionChecked",
|
||||||
Q_RETURN_ARG(bool, result),
|
Q_RETURN_ARG(bool, result),
|
||||||
Q_ARG(const QString&, menuOption));
|
Q_ARG(const QString&, menuOption));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuScriptingInterface::setIsOptionChecked(const QString& menuOption, bool isChecked) {
|
void MenuScriptingInterface::setIsOptionChecked(const QString& menuOption, bool isChecked) {
|
||||||
QMetaObject::invokeMethod(Menu::getInstance(), "setIsOptionChecked",
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(menuInstance, "setIsOptionChecked",
|
||||||
Q_ARG(const QString&, menuOption),
|
Q_ARG(const QString&, menuOption),
|
||||||
Q_ARG(bool, isChecked));
|
Q_ARG(bool, isChecked));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MenuScriptingInterface::isMenuEnabled(const QString& menuOption) {
|
bool MenuScriptingInterface::isMenuEnabled(const QString& menuOption) {
|
||||||
if (QThread::currentThread() == qApp->thread()) {
|
|
||||||
Menu* menuInstance = Menu::getInstance();
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QThread::currentThread() == qApp->thread()) {
|
||||||
return menuInstance && menuInstance->isMenuEnabled(menuOption);
|
return menuInstance && menuInstance->isMenuEnabled(menuOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool result { false };
|
bool result { false };
|
||||||
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isMenuEnabled",
|
|
||||||
|
BLOCKING_INVOKE_METHOD(menuInstance, "isMenuEnabled",
|
||||||
Q_RETURN_ARG(bool, result),
|
Q_RETURN_ARG(bool, result),
|
||||||
Q_ARG(const QString&, menuOption));
|
Q_ARG(const QString&, menuOption));
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuScriptingInterface::setMenuEnabled(const QString& menuOption, bool isChecked) {
|
void MenuScriptingInterface::setMenuEnabled(const QString& menuOption, bool isChecked) {
|
||||||
QMetaObject::invokeMethod(Menu::getInstance(), "setMenuEnabled",
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(menuInstance, "setMenuEnabled",
|
||||||
Q_ARG(const QString&, menuOption),
|
Q_ARG(const QString&, menuOption),
|
||||||
Q_ARG(bool, isChecked));
|
Q_ARG(bool, isChecked));
|
||||||
}
|
}
|
||||||
|
|
||||||
void MenuScriptingInterface::triggerOption(const QString& menuOption) {
|
void MenuScriptingInterface::triggerOption(const QString& menuOption) {
|
||||||
QMetaObject::invokeMethod(Menu::getInstance(), "triggerOption", Q_ARG(const QString&, menuOption));
|
Menu* menuInstance = Menu::getInstance();
|
||||||
|
if (!menuInstance) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QMetaObject::invokeMethod(menuInstance, "triggerOption", Q_ARG(const QString&, menuOption));
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
|
|
||||||
#include <AudioClient.h>
|
#include <AudioClient.h>
|
||||||
#include <SettingHandle.h>
|
#include <SettingHandle.h>
|
||||||
#include <trackers/FaceTracker.h>
|
|
||||||
#include <UsersScriptingInterface.h>
|
#include <UsersScriptingInterface.h>
|
||||||
|
|
||||||
#include "Application.h"
|
#include "Application.h"
|
||||||
|
@ -76,8 +75,6 @@ void AvatarInputs::update() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking));
|
|
||||||
AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking));
|
|
||||||
AI_UPDATE(isHMD, qApp->isHMDMode());
|
AI_UPDATE(isHMD, qApp->isHMDMode());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,13 +100,6 @@ bool AvatarInputs::getIgnoreRadiusEnabled() const {
|
||||||
return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled();
|
return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AvatarInputs::toggleCameraMute() {
|
|
||||||
FaceTracker* faceTracker = qApp->getSelectedFaceTracker();
|
|
||||||
if (faceTracker) {
|
|
||||||
faceTracker->toggleMute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void AvatarInputs::resetSensors() {
|
void AvatarInputs::resetSensors() {
|
||||||
qApp->resetSensors();
|
qApp->resetSensors();
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,11 +35,11 @@ class AvatarInputs : public QObject {
|
||||||
* @property {boolean} cameraEnabled - <code>true</code> if webcam face tracking is enabled, <code>false</code> if it is
|
* @property {boolean} cameraEnabled - <code>true</code> if webcam face tracking is enabled, <code>false</code> if it is
|
||||||
* disabled.
|
* disabled.
|
||||||
* <em>Read-only.</em>
|
* <em>Read-only.</em>
|
||||||
* <p class="important">Deprecated: This property is deprecated and will be removed.</p>
|
* <p class="important">Deprecated: This property is deprecated and has been removed.</p>
|
||||||
* @property {boolean} cameraMuted - <code>true</code> if webcam face tracking is muted (temporarily disabled),
|
* @property {boolean} cameraMuted - <code>true</code> if webcam face tracking is muted (temporarily disabled),
|
||||||
* <code>false</code> it if isn't.
|
* <code>false</code> it if isn't.
|
||||||
* <em>Read-only.</em>
|
* <em>Read-only.</em>
|
||||||
* <p class="important">Deprecated: This property is deprecated and will be removed.</p>
|
* <p class="important">Deprecated: This property is deprecated and has been removed.</p>
|
||||||
* @property {boolean} ignoreRadiusEnabled - <code>true</code> if the privacy shield is enabled, <code>false</code> if it
|
* @property {boolean} ignoreRadiusEnabled - <code>true</code> if the privacy shield is enabled, <code>false</code> if it
|
||||||
* is disabled.
|
* is disabled.
|
||||||
* <em>Read-only.</em>
|
* <em>Read-only.</em>
|
||||||
|
@ -51,8 +51,6 @@ class AvatarInputs : public QObject {
|
||||||
* it is hidden.
|
* it is hidden.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
AI_PROPERTY(bool, cameraEnabled, false)
|
|
||||||
AI_PROPERTY(bool, cameraMuted, false)
|
|
||||||
AI_PROPERTY(bool, isHMD, false)
|
AI_PROPERTY(bool, isHMD, false)
|
||||||
|
|
||||||
Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged)
|
Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged)
|
||||||
|
@ -99,19 +97,17 @@ signals:
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Triggered when webcam face tracking is enabled or disabled.
|
* Triggered when webcam face tracking is enabled or disabled.
|
||||||
* @deprecated This signal is deprecated and will be removed.
|
* @deprecated This signal is deprecated and has been removed.
|
||||||
* @function AvatarInputs.cameraEnabledChanged
|
* @function AvatarInputs.cameraEnabledChanged
|
||||||
* @returns {Signal}
|
* @returns {Signal}
|
||||||
*/
|
*/
|
||||||
void cameraEnabledChanged();
|
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Triggered when webcam face tracking is muted (temporarily disabled) or unmuted.
|
* Triggered when webcam face tracking is muted (temporarily disabled) or unmuted.
|
||||||
* @deprecated This signal is deprecated and will be removed.
|
* @deprecated This signal is deprecated and has been removed.
|
||||||
* @function AvatarInputs.cameraMutedChanged
|
* @function AvatarInputs.cameraMutedChanged
|
||||||
* @returns {Signal}
|
* @returns {Signal}
|
||||||
*/
|
*/
|
||||||
void cameraMutedChanged();
|
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Triggered when the display mode changes between desktop and HMD.
|
* Triggered when the display mode changes between desktop and HMD.
|
||||||
|
@ -185,10 +181,9 @@ protected:
|
||||||
|
|
||||||
/**jsdoc
|
/**jsdoc
|
||||||
* Toggles the muting (temporary disablement) of webcam face tracking on/off.
|
* Toggles the muting (temporary disablement) of webcam face tracking on/off.
|
||||||
* <p class="important">Deprecated: This function is deprecated and will be removed.</p>
|
* <p class="important">Deprecated: This function is deprecated and has been removed.</p>
|
||||||
* @function AvatarInputs.toggleCameraMute
|
* @function AvatarInputs.toggleCameraMute
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE void toggleCameraMute();
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void onAvatarEnteredIgnoreRadius();
|
void onAvatarEnteredIgnoreRadius();
|
||||||
|
|
|
@ -10,7 +10,6 @@
|
||||||
|
|
||||||
#include <AudioClient.h>
|
#include <AudioClient.h>
|
||||||
#include <avatar/AvatarManager.h>
|
#include <avatar/AvatarManager.h>
|
||||||
#include <devices/DdeFaceTracker.h>
|
|
||||||
#include <ScriptEngines.h>
|
#include <ScriptEngines.h>
|
||||||
#include <OffscreenUi.h>
|
#include <OffscreenUi.h>
|
||||||
#include <Preferences.h>
|
#include <Preferences.h>
|
||||||
|
@ -58,18 +57,19 @@ void setupPreferences() {
|
||||||
static const QString GRAPHICS_QUALITY { "Graphics Quality" };
|
static const QString GRAPHICS_QUALITY { "Graphics Quality" };
|
||||||
{
|
{
|
||||||
auto getter = []()->float {
|
auto getter = []()->float {
|
||||||
return DependencyManager::get<LODManager>()->getWorldDetailQuality();
|
return (int)DependencyManager::get<LODManager>()->getWorldDetailQuality();
|
||||||
};
|
};
|
||||||
|
|
||||||
auto setter = [](float value) {
|
auto setter = [](int value) {
|
||||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(value);
|
DependencyManager::get<LODManager>()->setWorldDetailQuality(static_cast<WorldDetailQuality>(value));
|
||||||
};
|
};
|
||||||
|
|
||||||
auto wodSlider = new SliderPreference(GRAPHICS_QUALITY, "World Detail", getter, setter);
|
auto wodButtons = new RadioButtonsPreference(GRAPHICS_QUALITY, "World Detail", getter, setter);
|
||||||
wodSlider->setMin(0.25f);
|
QStringList items;
|
||||||
wodSlider->setMax(0.75f);
|
items << "Low World Detail" << "Medium World Detail" << "High World Detail";
|
||||||
wodSlider->setStep(0.25f);
|
wodButtons->setHeading("World Detail");
|
||||||
preferences->addPreference(wodSlider);
|
wodButtons->setItems(items);
|
||||||
|
preferences->addPreference(wodButtons);
|
||||||
|
|
||||||
auto getterShadow = []()->bool {
|
auto getterShadow = []()->bool {
|
||||||
auto menu = Menu::getInstance();
|
auto menu = Menu::getInstance();
|
||||||
|
@ -295,22 +295,6 @@ void setupPreferences() {
|
||||||
preferences->addPreference(preference);
|
preferences->addPreference(preference);
|
||||||
}
|
}
|
||||||
|
|
||||||
static const QString FACE_TRACKING{ "Face Tracking" };
|
|
||||||
{
|
|
||||||
#ifdef HAVE_DDE
|
|
||||||
auto getter = []()->float { return DependencyManager::get<DdeFaceTracker>()->getEyeClosingThreshold(); };
|
|
||||||
auto setter = [](float value) { DependencyManager::get<DdeFaceTracker>()->setEyeClosingThreshold(value); };
|
|
||||||
preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Closing Threshold", getter, setter));
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
{
|
|
||||||
auto getter = []()->float { return FaceTracker::getEyeDeflection(); };
|
|
||||||
auto setter = [](float value) { FaceTracker::setEyeDeflection(value); };
|
|
||||||
preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Deflection", getter, setter));
|
|
||||||
}
|
|
||||||
|
|
||||||
static const QString VR_MOVEMENT{ "VR Movement" };
|
static const QString VR_MOVEMENT{ "VR Movement" };
|
||||||
{
|
{
|
||||||
auto getter = [myAvatar]()->bool { return myAvatar->getAllowTeleporting(); };
|
auto getter = [myAvatar]()->bool { return myAvatar->getAllowTeleporting(); };
|
||||||
|
|
|
@ -227,6 +227,9 @@
|
||||||
<pointsize>-1</pointsize>
|
<pointsize>-1</pointsize>
|
||||||
</font>
|
</font>
|
||||||
</property>
|
</property>
|
||||||
|
<property name="textColor">
|
||||||
|
<color>black</color>
|
||||||
|
</property>
|
||||||
<property name="frameShape">
|
<property name="frameShape">
|
||||||
<enum>QFrame::NoFrame</enum>
|
<enum>QFrame::NoFrame</enum>
|
||||||
</property>
|
</property>
|
||||||
|
|
|
@ -12,7 +12,23 @@
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
void redirectLogToDocuments()
|
||||||
|
{
|
||||||
|
NSString* filePath = [[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0]
|
||||||
|
stringByAppendingString:@"/Launcher/"];
|
||||||
|
|
||||||
|
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
||||||
|
NSError * error = nil;
|
||||||
|
[[NSFileManager defaultManager] createDirectoryAtPath:filePath withIntermediateDirectories:TRUE attributes:nil error:&error];
|
||||||
|
}
|
||||||
|
NSString *pathForLog = [filePath stringByAppendingPathComponent:@"log.txt"];
|
||||||
|
|
||||||
|
freopen([pathForLog cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
|
||||||
|
}
|
||||||
|
|
||||||
int main(int argc, char const* argv[]) {
|
int main(int argc, char const* argv[]) {
|
||||||
|
redirectLogToDocuments();
|
||||||
if (argc < 3) {
|
if (argc < 3) {
|
||||||
NSLog(@"Error: wrong number of arguments");
|
NSLog(@"Error: wrong number of arguments");
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -50,6 +66,7 @@ int main(int argc, char const* argv[]) {
|
||||||
options:NSWorkspaceLaunchNewInstance
|
options:NSWorkspaceLaunchNewInstance
|
||||||
configuration:configuration
|
configuration:configuration
|
||||||
error:&error];
|
error:&error];
|
||||||
|
|
||||||
if (error != nil) {
|
if (error != nil) {
|
||||||
NSLog(@"couldn't start launcher: %@", error);
|
NSLog(@"couldn't start launcher: %@", error);
|
||||||
return 1;
|
return 1;
|
||||||
|
|
290
launchers/qt/CMakeLists.txt
Normal file
|
@ -0,0 +1,290 @@
|
||||||
|
cmake_minimum_required(VERSION 3.0)
|
||||||
|
if (APPLE)
|
||||||
|
set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.10)
|
||||||
|
endif()
|
||||||
|
project(HQLauncher)
|
||||||
|
|
||||||
|
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules")
|
||||||
|
include("cmake/macros/SetPackagingParameters.cmake")
|
||||||
|
set(CMAKE_CXX_STANDARD 14)
|
||||||
|
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||||
|
set(CMAKE_AUTOMOC ON)
|
||||||
|
set(CMAKE_AUTORCC ON)
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
|
||||||
|
include("cmake/init.cmake")
|
||||||
|
include("cmake/macros/SetPackagingParameters.cmake")
|
||||||
|
|
||||||
|
if(MSVC)
|
||||||
|
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||||
|
|
||||||
|
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
|
||||||
|
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /MT")
|
||||||
|
set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} /MT")
|
||||||
|
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
endif()
|
||||||
|
endfunction()
|
||||||
|
|
||||||
|
include(ExternalProject)
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
set(CMAKE_EXE_LINKER_FLAGS "-framework Cocoa -framework CoreServices -framework Carbon -framework IOKit -framework Security -framework SystemConfiguration")
|
||||||
|
add_compile_options(-W -Wall -Wextra -Wpedantic)
|
||||||
|
endif()
|
||||||
|
if (WIN32)
|
||||||
|
|
||||||
|
ExternalProject_Add(
|
||||||
|
qtlite
|
||||||
|
URL "https://public.highfidelity.com/dependencies/qtlite/qt-lite-5.9.9-win-oct-15-2019.zip"
|
||||||
|
URL_HASH MD5=0176277bca37d219e83738caf3a698eb
|
||||||
|
CONFIGURE_COMMAND ""
|
||||||
|
BUILD_COMMAND ""
|
||||||
|
INSTALL_COMMAND ""
|
||||||
|
LOG_DOWNLOAD 1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ExternalProject_Get_Property(qtlite SOURCE_DIR)
|
||||||
|
ExternalProject_Get_Property(qtlite STAMP_DIR)
|
||||||
|
|
||||||
|
include("${STAMP_DIR}/download-qtlite.cmake")
|
||||||
|
include("${STAMP_DIR}/extract-qtlite.cmake")
|
||||||
|
include("${STAMP_DIR}/verify-qtlite.cmake")
|
||||||
|
|
||||||
|
message("${SOURCE_DIR}/lib/cmake")
|
||||||
|
|
||||||
|
list(APPEND CMAKE_PREFIX_PATH ${SOURCE_DIR}/lib/cmake)
|
||||||
|
|
||||||
|
set(SSL_DIR ${SOURCE_DIR}/ssl)
|
||||||
|
set(OPENSSL_ROOT_DIR ${SSL_DIR})
|
||||||
|
message("SSL dir is ${SSL_DIR}")
|
||||||
|
set(OPENSSL_USE_STATIC_LIBS TRUE)
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
|
||||||
|
message("-- Found OpenSSL Libs ${OPENSSL_LIBRARIES}")
|
||||||
|
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
ExternalProject_Add(
|
||||||
|
qtlite
|
||||||
|
URL "https://public.highfidelity.com/dependencies/qtlite/qt-lite-5.9.9-mac.zip"
|
||||||
|
URL_HASH MD5=0cd78d40e5f539a7e314cf99b6cae0d0
|
||||||
|
CONFIGURE_COMMAND ""
|
||||||
|
BUILD_COMMAND ""
|
||||||
|
INSTALL_COMMAND ""
|
||||||
|
LOG_DOWNLOAD 1
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
ExternalProject_Get_Property(qtlite SOURCE_DIR)
|
||||||
|
ExternalProject_Get_Property(qtlite STAMP_DIR)
|
||||||
|
|
||||||
|
include("${STAMP_DIR}/download-qtlite.cmake")
|
||||||
|
include("${STAMP_DIR}/extract-qtlite.cmake")
|
||||||
|
include("${STAMP_DIR}/verify-qtlite.cmake")
|
||||||
|
|
||||||
|
message("${SOURCE_DIR}/lib/cmake")
|
||||||
|
|
||||||
|
list(APPEND CMAKE_PREFIX_PATH ${SOURCE_DIR}/lib/cmake)
|
||||||
|
|
||||||
|
set(SSL_DIR ${SOURCE_DIR}/ssl)
|
||||||
|
set(OPENSSL_ROOT_DIR ${SSL_DIR})
|
||||||
|
message("SSL dir is ${SSL_DIR}")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
set(OPENSSL_USE_STATIC_LIBS TRUE)
|
||||||
|
find_package(OpenSSL REQUIRED)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
find_package(Qt5 COMPONENTS Core Gui Qml Quick QuickControls2 Network REQUIRED)
|
||||||
|
find_package(OpenGL REQUIRED)
|
||||||
|
find_package(QtStaticDeps REQUIRED)
|
||||||
|
|
||||||
|
set(CUSTOM_LAUNCHER_QRC_PATHS "")
|
||||||
|
set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc)
|
||||||
|
set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/resources.rcc)
|
||||||
|
generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_LAUNCHER_QRC_PATHS} GLOBS *)
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${RESOURCES_RCC}
|
||||||
|
DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS}
|
||||||
|
COMMAND "${_qt5Core_install_prefix}/bin/rcc"
|
||||||
|
ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC})
|
||||||
|
|
||||||
|
QT5_ADD_RESOURCES(RES_SOURCES ${RESOURCES_QRC})
|
||||||
|
|
||||||
|
list(APPEND GENERATE_QRC_DEPENDS ${RESOURCES_RCC})
|
||||||
|
add_custom_target(resources ALL DEPENDS ${GENERATE_QRC_DEPENDS})
|
||||||
|
|
||||||
|
foreach(plugin ${Qt5Gui_PLUGINS})
|
||||||
|
get_target_property(_loc ${plugin} LOCATION)
|
||||||
|
set(plugin_libs ${plugin_libs} ${_loc})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set(src_files
|
||||||
|
src/main.cpp
|
||||||
|
src/Launcher.h
|
||||||
|
src/Launcher.cpp
|
||||||
|
src/LauncherState.h
|
||||||
|
src/LauncherState.cpp
|
||||||
|
src/LauncherWindow.h
|
||||||
|
src/LauncherWindow.cpp
|
||||||
|
src/LoginRequest.h
|
||||||
|
src/LoginRequest.cpp
|
||||||
|
src/SignupRequest.h
|
||||||
|
src/SignupRequest.cpp
|
||||||
|
src/BuildsRequest.h
|
||||||
|
src/BuildsRequest.cpp
|
||||||
|
src/UserSettingsRequest.h
|
||||||
|
src/UserSettingsRequest.cpp
|
||||||
|
src/PathUtils.h
|
||||||
|
src/PathUtils.cpp
|
||||||
|
src/Unzipper.h
|
||||||
|
src/Unzipper.cpp
|
||||||
|
src/Helper.h
|
||||||
|
src/Helper.cpp
|
||||||
|
src/CommandlineOptions.h
|
||||||
|
src/CommandlineOptions.cpp
|
||||||
|
deps/miniz/miniz.h
|
||||||
|
deps/miniz/miniz.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
set(src_files ${src_files}
|
||||||
|
src/Helper_darwin.mm
|
||||||
|
src/NSTask+NSTaskExecveAdditions.h
|
||||||
|
src/NSTask+NSTaskExecveAdditions.m
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
set(src_files ${src_files}
|
||||||
|
src/Helper_windows.cpp
|
||||||
|
src/LauncherInstaller_windows.h
|
||||||
|
src/LauncherInstaller_windows.cpp
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
set(TARGET_NAME ${PROJECT_NAME})
|
||||||
|
|
||||||
|
|
||||||
|
set_packaging_parameters()
|
||||||
|
if (WIN32)
|
||||||
|
set(CONFIGURE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/resources/images/interface.ico")
|
||||||
|
message(${CONFIGURE_ICON_PATH})
|
||||||
|
set(CONFIGURE_ICON_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Icon.rc")
|
||||||
|
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/Icon.rc.in" ${CONFIGURE_ICON_RC_OUTPUT})
|
||||||
|
add_executable(${PROJECT_NAME} WIN32 ${src_files} ${RES_SOURCES} ${CONFIGURE_ICON_RC_OUTPUT})
|
||||||
|
elseif (APPLE)
|
||||||
|
set(APP_NAME "HQ Launcher")
|
||||||
|
set_target_properties(${this_target} PROPERTIES
|
||||||
|
MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in)
|
||||||
|
|
||||||
|
set(MACOSX_BUNDLE_ICON_FILE "interface.icns")
|
||||||
|
add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files} ${RES_SOURCES})
|
||||||
|
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME}
|
||||||
|
MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME})
|
||||||
|
|
||||||
|
|
||||||
|
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||||
|
${CMAKE_SOURCE_DIR}/resources/images "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/"
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/MacOS/" ln -sf ./HQ\ Launcher updater
|
||||||
|
# Older versions of Launcher put updater in `/Contents/Resources/updater`.
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources" ln -sf ../MacOS/HQ\ Launcher updater
|
||||||
|
)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_link_libraries(${PROJECT_NAME}
|
||||||
|
Qt5::Core
|
||||||
|
Qt5::Quick
|
||||||
|
Qt5::QuickControls2
|
||||||
|
Qt5::Qml
|
||||||
|
Qt5::Gui
|
||||||
|
Qt5::Network
|
||||||
|
${Qt_LIBRARIES}
|
||||||
|
${OPENGL_LIBRARIES}
|
||||||
|
${plugin_libs}
|
||||||
|
${QT_STATIC_LIBS}
|
||||||
|
)
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
target_link_libraries(${PROJECT_NAME}
|
||||||
|
wsock32 ws2_32 Winmm version imm32 dwmapi
|
||||||
|
Crypt32 Iphlpapi
|
||||||
|
#"${SSL_DIR}/lib/libeay32.lib"
|
||||||
|
#"${SSL_DIR}/lib/ssleay32.lib"
|
||||||
|
${OPENSSL_LIBRARIES}
|
||||||
|
"${_qt5Core_install_prefix}/qml/QtQuick.2/qtquick2plugin.lib"
|
||||||
|
"${_qt5Core_install_prefix}/qml/QtQuick/Controls.2/qtquickcontrols2plugin.lib"
|
||||||
|
"${_qt5Core_install_prefix}/qml/QtQuick/Templates.2/qtquicktemplates2plugin.lib")
|
||||||
|
elseif (APPLE)
|
||||||
|
target_link_libraries(${PROJECT_NAME}
|
||||||
|
${OPENSSL_LIBRARIES}
|
||||||
|
"${_qt5Core_install_prefix}/qml/QtQuick.2/libqtquick2plugin.a"
|
||||||
|
"${_qt5Core_install_prefix}/qml/QtQuick/Controls.2/libqtquickcontrols2plugin.a"
|
||||||
|
"${_qt5Core_install_prefix}/qml/QtQuick/Templates.2/libqtquicktemplates2plugin.a"
|
||||||
|
"${_qt5Core_install_prefix}/plugins/platforms/libqcocoa.a")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||||
|
${CMAKE_CURRENT_SOURCE_DIR}/deps/
|
||||||
|
${Qt5Core_INCLUDE_DIRS}
|
||||||
|
${Qt5Quick_INCLUDE_DIRS}
|
||||||
|
${Qt5Gui_INCLUDE_DIRS}
|
||||||
|
${Qt5Qml_INCLUDE_DIRS})
|
||||||
|
|
||||||
|
add_dependencies(${PROJECT_NAME} resources)
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||||
|
${OPENSSL_INCLUDE_DIR})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
if (LAUNCHER_SOURCE_TREE_RESOURCES)
|
||||||
|
target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="${CMAKE_CURRENT_SOURCE_DIR}/resources/")
|
||||||
|
target_compile_definitions(${PROJECT_NAME} PRIVATE HIFI_USE_LOCAL_FILE)
|
||||||
|
message("Use source tree resources path: file://${CMAKE_CURRENT_SOURCE_DIR}/resources/")
|
||||||
|
else()
|
||||||
|
target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="qrc:/")
|
||||||
|
message("Use resource.rcc path: qrc:/")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}")
|
||||||
|
|
||||||
|
if (APPLE)
|
||||||
|
install(
|
||||||
|
TARGETS HQLauncher
|
||||||
|
BUNDLE DESTINATION "."
|
||||||
|
COMPONENT applications)
|
||||||
|
|
||||||
|
set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR})
|
||||||
|
|
||||||
|
include(CPackComponent)
|
||||||
|
|
||||||
|
set(CPACK_PACKAGE_NAME "HQ Launcher")
|
||||||
|
set(CPACK_PACKAGE_VENDOR "High Fidelity")
|
||||||
|
set(CPACK_PACKAGE_FILE_NAME "HQ Launcher")
|
||||||
|
|
||||||
|
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
|
||||||
|
|
||||||
|
set(DMG_SUBFOLDER_NAME "High Fidelity")
|
||||||
|
set(ESCAPED_DMG_SUBFOLDER_NAME "")
|
||||||
|
set(DMG_SUBFOLDER_ICON "${CMAKE_SOURCE_DIR}/cmake/installer/install-folder.rsrc")
|
||||||
|
|
||||||
|
set(CPACK_GENERATOR "DragNDrop")
|
||||||
|
include(CPack)
|
||||||
|
endif()
|
20
launchers/qt/HQ Launcher.entitlements
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
<?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.application-groups</key>
|
||||||
|
<array>
|
||||||
|
<string>high-fidelity.hifi</string>
|
||||||
|
</array>
|
||||||
|
<key>com.apple.security.device.audio-input</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-jit</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||||
|
<true/>
|
||||||
|
<key>com.apple.security.cs.disable-library-validation</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
14
launchers/qt/cmake/init.cmake
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
|
||||||
|
# Hide automoc folders (for IDEs)
|
||||||
|
set(AUTOGEN_TARGETS_FOLDER "hidden/generated")
|
||||||
|
# Find includes in corresponding build directories
|
||||||
|
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||||
|
|
||||||
|
# CMAKE_CURRENT_SOURCE_DIR is the parent folder here
|
||||||
|
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/")
|
||||||
|
|
||||||
|
file(GLOB LAUNCHER_CUSTOM_MACROS "cmake/macros/*.cmake")
|
||||||
|
foreach(CUSTOM_MACRO ${LAUNCHER_CUSTOM_MACROS})
|
||||||
|
include(${CUSTOM_MACRO})
|
||||||
|
endforeach()
|
||||||
|
unset(LAUNCHER_CUSTOM_MACROS)
|
1634
launchers/qt/cmake/installer/install-folder.rsrc
Normal file
BIN
launchers/qt/cmake/installer/installer-header.bmp
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
launchers/qt/cmake/installer/installer.ico
Normal file
After Width: | Height: | Size: 299 KiB |
BIN
launchers/qt/cmake/installer/uninstaller-header.bmp
Normal file
After Width: | Height: | Size: 100 KiB |
31
launchers/qt/cmake/macros/GenerateQrc.cmake
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
function(GENERATE_QRC)
|
||||||
|
set(oneValueArgs OUTPUT PREFIX PATH)
|
||||||
|
set(multiValueArgs CUSTOM_PATHS GLOBS)
|
||||||
|
cmake_parse_arguments(GENERATE_QRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
|
||||||
|
if ("${GENERATE_QRC_PREFIX}" STREQUAL "")
|
||||||
|
set(QRC_PREFIX_PATH /)
|
||||||
|
else()
|
||||||
|
set(QRC_PREFIX_PATH ${GENERATE_QRC_PREFIX})
|
||||||
|
endif()
|
||||||
|
|
||||||
|
foreach(GLOB ${GENERATE_QRC_GLOBS})
|
||||||
|
file(GLOB_RECURSE FOUND_FILES RELATIVE ${GENERATE_QRC_PATH} ${GLOB})
|
||||||
|
foreach(FILENAME ${FOUND_FILES})
|
||||||
|
if (${FILENAME} MATCHES "^\\.\\.")
|
||||||
|
continue()
|
||||||
|
endif()
|
||||||
|
list(APPEND ALL_FILES "${GENERATE_QRC_PATH}/${FILENAME}")
|
||||||
|
set(QRC_CONTENTS "${QRC_CONTENTS}<file alias=\"${FILENAME}\">${GENERATE_QRC_PATH}/${FILENAME}</file>\n")
|
||||||
|
endforeach()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
foreach(CUSTOM_PATH ${GENERATE_QRC_CUSTOM_PATHS})
|
||||||
|
string(REPLACE "=" ";" CUSTOM_PATH ${CUSTOM_PATH})
|
||||||
|
list(GET CUSTOM_PATH 0 IMPORT_PATH)
|
||||||
|
list(GET CUSTOM_PATH 1 LOCAL_PATH)
|
||||||
|
set(QRC_CONTENTS "${QRC_CONTENTS}<file alias=\"${LOCAL_PATH}\">${IMPORT_PATH}</file>\n")
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
set(GENERATE_QRC_DEPENDS ${ALL_FILES} PARENT_SCOPE)
|
||||||
|
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT})
|
||||||
|
endfunction()
|
45
launchers/qt/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)
|
33
launchers/qt/cmake/modules/FindQtStaticDeps.cmake
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
|
||||||
|
set(qt_static_lib_dependices
|
||||||
|
"qtpcre2"
|
||||||
|
"qtlibpng"
|
||||||
|
"qtfreetype"
|
||||||
|
"Qt5AccessibilitySupport"
|
||||||
|
"Qt5FbSupport"
|
||||||
|
"Qt5OpenGLExtensions"
|
||||||
|
"Qt5QuickTemplates2"
|
||||||
|
"Qt5FontDatabaseSupport"
|
||||||
|
"Qt5ThemeSupport"
|
||||||
|
"Qt5EventDispatcherSupport")
|
||||||
|
|
||||||
|
if (WIN32)
|
||||||
|
elseif(APPLE)
|
||||||
|
set(qt_static_lib_dependices
|
||||||
|
${qt_static_lib_dependices}
|
||||||
|
"Qt5GraphicsSupport"
|
||||||
|
"Qt5CglSupport"
|
||||||
|
"Qt5ClipboardSupport")
|
||||||
|
endif()
|
||||||
|
|
||||||
|
set(LIBS_PREFIX "${_qt5Core_install_prefix}/lib/")
|
||||||
|
foreach (_qt_static_dep ${qt_static_lib_dependices})
|
||||||
|
if (WIN32)
|
||||||
|
set(lib_path "${LIBS_PREFIX}${_qt_static_dep}.lib")
|
||||||
|
else()
|
||||||
|
set(lib_path "${LIBS_PREFIX}lib${_qt_static_dep}.a")
|
||||||
|
endif()
|
||||||
|
set(QT_STATIC_LIBS ${QT_STATIC_LIBS} ${lib_path})
|
||||||
|
endforeach()
|
||||||
|
|
||||||
|
unset(qt_static_lib_dependices)
|
37
launchers/qt/cmake/modules/MacOSXBundleInfo.plist.in
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
<?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>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>${APP_NAME}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>com.highfidelity.launcher</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>${PRODUCT_NAME}</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>NSAppTransportSecurity</key>
|
||||||
|
<dict>
|
||||||
|
<key>NSAllowsArbitraryLoads</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>NSMainNibFile</key>
|
||||||
|
<string>Window</string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||||
|
<key>CFBundleDisplayName</key>
|
||||||
|
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
1
launchers/qt/cmake/templates/Icon.rc.in
Normal file
|
@ -0,0 +1 @@
|
||||||
|
IDI_ICON1 ICON DISCARDABLE "@CONFIGURE_ICON_PATH@"
|
5
launchers/qt/cmake/templates/resources.qrc.in
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<!DOCTYPE RCC><RCC version="1.0">
|
||||||
|
<qresource prefix="@QRC_PREFIX_PATH@">
|
||||||
|
@QRC_CONTENTS@
|
||||||
|
</qresource>
|
||||||
|
</RCC>
|
7658
launchers/qt/deps/miniz/miniz.cpp
Normal file
1338
launchers/qt/deps/miniz/miniz.h
Normal file
0
launchers/qt/deps/miniz/stdafx.h
Normal file
BIN
launchers/qt/resources/fonts/Graphik-Medium.ttf
Normal file
BIN
launchers/qt/resources/fonts/Graphik-Regular.ttf
Normal file
BIN
launchers/qt/resources/fonts/Graphik-Semibold.ttf
Normal file
BIN
launchers/qt/resources/images/HiFi_Voxel.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
launchers/qt/resources/images/HiFi_Window.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
13
launchers/qt/resources/images/Launcher.rc2
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
//
|
||||||
|
// Launcher.rc2 - resources Microsoft Visual C++ does not edit directly
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifdef APSTUDIO_INVOKED
|
||||||
|
#error this file is not editable by Microsoft Visual C++
|
||||||
|
#endif //APSTUDIO_INVOKED
|
||||||
|
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
||||||
|
// Add manually edited resources here...
|
||||||
|
|
||||||
|
/////////////////////////////////////////////////////////////////////////////
|
BIN
launchers/qt/resources/images/hidePass.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
launchers/qt/resources/images/hifi_logo_large@2x.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
launchers/qt/resources/images/hifi_logo_small@2x.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
launchers/qt/resources/images/hifi_window@2x.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
launchers/qt/resources/images/interface.icns
Normal file
BIN
launchers/qt/resources/images/interface.ico
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
launchers/qt/resources/images/showPass.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
102
launchers/qt/resources/qml/Download.qml
Normal file
|
@ -0,0 +1,102 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import "HFControls"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
mirror: true
|
||||||
|
source: PathUtils.resourcePath("images/hifi_window@2x.png");
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
rotation: 180
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: logo
|
||||||
|
width: 150
|
||||||
|
height: 150
|
||||||
|
source: PathUtils.resourcePath("images/HiFi_Voxel.png");
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: root.top
|
||||||
|
topMargin: 98
|
||||||
|
horizontalCenter: root.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
RotationAnimator {
|
||||||
|
target: logo;
|
||||||
|
loops: Animation.Infinite
|
||||||
|
from: 0;
|
||||||
|
to: 360;
|
||||||
|
duration: 5000
|
||||||
|
running: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextHeader {
|
||||||
|
id: firstText
|
||||||
|
|
||||||
|
text: "Setup will take a moment"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: logo.bottom
|
||||||
|
topMargin: 46
|
||||||
|
horizontalCenter: logo.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextRegular {
|
||||||
|
id: secondText
|
||||||
|
|
||||||
|
text: "We're getting everything set up for you."
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: firstText.bottom
|
||||||
|
topMargin: 8
|
||||||
|
horizontalCenter: logo.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ProgressBar {
|
||||||
|
id: progressBar
|
||||||
|
width: 394
|
||||||
|
height: 8
|
||||||
|
|
||||||
|
value: LauncherState.downloadProgress;
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: secondText.bottom
|
||||||
|
topMargin: 30
|
||||||
|
horizontalCenter: logo.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
implicitWidth: progressBar.width
|
||||||
|
implicitHeight: progressBar.height
|
||||||
|
radius: 8
|
||||||
|
color: "#252525"
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
implicitWidth: progressBar.width
|
||||||
|
implicitHeight: progressBar.height * 0.85
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
width: progressBar.visualPosition * parent.width
|
||||||
|
height: parent.height
|
||||||
|
radius: 6
|
||||||
|
color: "#01B2ED"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.parent.setBuildInfoState("left");
|
||||||
|
}
|
||||||
|
}
|
61
launchers/qt/resources/qml/DownloadFinished.qml
Normal file
|
@ -0,0 +1,61 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import "HFControls"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
mirror: true
|
||||||
|
source: PathUtils.resourcePath("images/hifi_window@2x.png");
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
rotation: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: logo
|
||||||
|
width: 132
|
||||||
|
height: 134
|
||||||
|
source: PathUtils.resourcePath("images/HiFi_Voxel.png");
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: root.top
|
||||||
|
topMargin: 144
|
||||||
|
horizontalCenter: root.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextHeader {
|
||||||
|
id: header
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
text: "You're all set!"
|
||||||
|
anchors {
|
||||||
|
top: logo.bottom
|
||||||
|
topMargin: 26
|
||||||
|
horizontalCenter: logo.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextRegular {
|
||||||
|
id: description
|
||||||
|
text: "We will see you in world."
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
anchors {
|
||||||
|
top: header.bottom
|
||||||
|
topMargin: 8
|
||||||
|
horizontalCenter: header.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.parent.setBuildInfoState("left");
|
||||||
|
}
|
||||||
|
}
|
213
launchers/qt/resources/qml/HFBase/CreateAccountBase.qml
Normal file
|
@ -0,0 +1,213 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick 2.1
|
||||||
|
|
||||||
|
import "../HFControls"
|
||||||
|
import HQLauncher 1.0
|
||||||
|
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
anchors.centerIn: parent
|
||||||
|
property string titleText: "Create Your Username and Password"
|
||||||
|
property string usernamePlaceholder: "Username"
|
||||||
|
property string passwordPlaceholder: "Set a password (must be at least 6 characters)"
|
||||||
|
property int marginLeft: root.width * 0.15
|
||||||
|
|
||||||
|
property bool enabled: LauncherState.applicationState == ApplicationState.WaitingForSignup
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
mirror: true
|
||||||
|
source: PathUtils.resourcePath("images/hifi_window@2x.png");
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
rotation: 180
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextHeader {
|
||||||
|
id: title
|
||||||
|
width: 481
|
||||||
|
wrapMode: Text.WordWrap
|
||||||
|
lineHeight: 35
|
||||||
|
lineHeightMode: Text.FixedHeight
|
||||||
|
text: LauncherState.lastSignupErrorMessage.length == 0 ? root.titleText : "Uh oh"
|
||||||
|
anchors {
|
||||||
|
top: root.top
|
||||||
|
topMargin: 29
|
||||||
|
left: root.left
|
||||||
|
leftMargin: root.marginLeft
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextError {
|
||||||
|
id: error
|
||||||
|
|
||||||
|
width: 425
|
||||||
|
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
|
||||||
|
visible: LauncherState.lastSignupErrorMessage.length > 0
|
||||||
|
text: LauncherState.lastSignupErrorMessage
|
||||||
|
|
||||||
|
textFormat: Text.StyledText
|
||||||
|
|
||||||
|
onLinkActivated: {
|
||||||
|
if (link == "login") {
|
||||||
|
LauncherState.gotoLogin();
|
||||||
|
} else {
|
||||||
|
LauncherState.openURLInBrowser(link)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
left: root.left
|
||||||
|
leftMargin: root.marginLeft
|
||||||
|
top: title.bottom
|
||||||
|
topMargin: 18
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextField {
|
||||||
|
id: email
|
||||||
|
width: 430
|
||||||
|
|
||||||
|
enabled: root.enabled
|
||||||
|
|
||||||
|
placeholderText: "Verify Your Email"
|
||||||
|
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||||
|
anchors {
|
||||||
|
top: error.visible ? error.bottom : title.bottom
|
||||||
|
left: root.left
|
||||||
|
leftMargin: root.marginLeft
|
||||||
|
topMargin: 18
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextField {
|
||||||
|
id: username
|
||||||
|
width: 430
|
||||||
|
|
||||||
|
enabled: root.enabled
|
||||||
|
|
||||||
|
placeholderText: root.usernamePlaceholder
|
||||||
|
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||||
|
anchors {
|
||||||
|
top: email.bottom
|
||||||
|
left: root.left
|
||||||
|
leftMargin: root.marginLeft
|
||||||
|
topMargin: 18
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextField {
|
||||||
|
id: password
|
||||||
|
width: 430
|
||||||
|
|
||||||
|
enabled: root.enabled
|
||||||
|
|
||||||
|
placeholderText: root.passwordPlaceholder
|
||||||
|
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||||
|
togglePasswordField: true
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
anchors {
|
||||||
|
top: username.bottom
|
||||||
|
left: root.left
|
||||||
|
leftMargin: root.marginLeft
|
||||||
|
topMargin: 18
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HFTextRegular {
|
||||||
|
id: displayNameText
|
||||||
|
|
||||||
|
text: "This is the display name other people see in High Fidelity. It can be changed at any time from your profile."
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
|
||||||
|
width: 430
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: password.bottom
|
||||||
|
left: root.left
|
||||||
|
leftMargin: root.marginLeft
|
||||||
|
topMargin: 22
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextField {
|
||||||
|
id: displayName
|
||||||
|
width: 430
|
||||||
|
|
||||||
|
enabled: root.enabled
|
||||||
|
|
||||||
|
placeholderText: "Display Name"
|
||||||
|
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||||
|
anchors {
|
||||||
|
top: displayNameText.bottom
|
||||||
|
left: root.left
|
||||||
|
leftMargin: root.marginLeft
|
||||||
|
topMargin: 4
|
||||||
|
}
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
if (root.enabled && email.text.length > 0 && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0) {
|
||||||
|
LauncherState.signup(email.text, username.text, password.text, displayName.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFButton {
|
||||||
|
id: button
|
||||||
|
width: 134
|
||||||
|
|
||||||
|
enabled: root.enabled && email.text.length > 0 && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0
|
||||||
|
|
||||||
|
text: "NEXT"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: displayName.bottom
|
||||||
|
left: root.left
|
||||||
|
leftMargin: root.marginLeft
|
||||||
|
topMargin: 21
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: LauncherState.signup(email.text, username.text, password.text, displayName.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "Already have an account?"
|
||||||
|
font.family: "Graphik"
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: "#009EE0"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: button.bottom
|
||||||
|
topMargin: 16
|
||||||
|
left: button.left
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
LauncherState.gotoLogin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextLogo {
|
||||||
|
anchors {
|
||||||
|
bottom: root.bottom
|
||||||
|
bottomMargin: 46
|
||||||
|
right: displayName.right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.parent.setBuildInfoState("left");
|
||||||
|
}
|
||||||
|
}
|
89
launchers/qt/resources/qml/HFBase/Error.qml
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick 2.1
|
||||||
|
import "../HFControls"
|
||||||
|
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
mirror: false
|
||||||
|
source: "qrc:/images/hifi_window@2x.png"
|
||||||
|
//fillMode: Image.PreserveAspectFit
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
//rotation: 90
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: logo
|
||||||
|
width: 132
|
||||||
|
height: 134
|
||||||
|
source: "qrc:/images/HiFi_Voxel.png"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: root.top
|
||||||
|
topMargin: 98
|
||||||
|
horizontalCenter: root.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextHeader {
|
||||||
|
id: header
|
||||||
|
text: "Uh oh."
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: logo.bottom
|
||||||
|
topMargin: 26
|
||||||
|
horizontalCenter: logo.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextRegular {
|
||||||
|
id: description
|
||||||
|
|
||||||
|
text: "We seem to have a problem."
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: header.bottom
|
||||||
|
topMargin: 8
|
||||||
|
horizontalCenter: header.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextRegular {
|
||||||
|
text: "Please restart."
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: description.bottom
|
||||||
|
topMargin: 1
|
||||||
|
horizontalCenter: header.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HFButton {
|
||||||
|
id: button
|
||||||
|
width: 166
|
||||||
|
|
||||||
|
text: "RESTART"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: description.bottom
|
||||||
|
topMargin: 60
|
||||||
|
horizontalCenter: description.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
LauncherState.restart();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.parent.setBuildInfoState("right");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
203
launchers/qt/resources/qml/HFBase/LoginBase.qml
Normal file
|
@ -0,0 +1,203 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick 2.1
|
||||||
|
|
||||||
|
import "../HFControls"
|
||||||
|
import HQLauncher 1.0
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
property bool enabled: LauncherState.applicationState == ApplicationState.WaitingForLogin
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
mirror: false
|
||||||
|
source: PathUtils.resourcePath("images/hifi_window@2x.png");
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
rotation: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: 430
|
||||||
|
height: root.height
|
||||||
|
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: root.top
|
||||||
|
horizontalCenter: root.horizontalCenter
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextHeader {
|
||||||
|
id: title
|
||||||
|
lineHeight: 35
|
||||||
|
lineHeightMode: Text.FixedHeight
|
||||||
|
text: "Please Log in"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
topMargin: 40
|
||||||
|
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextRegular {
|
||||||
|
id: instruction
|
||||||
|
|
||||||
|
visible: LauncherState.lastLoginErrorMessage.length == 0
|
||||||
|
text: "Use the account credentials you created at sign-up"
|
||||||
|
anchors {
|
||||||
|
top: title.bottom
|
||||||
|
topMargin: 18
|
||||||
|
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextError {
|
||||||
|
id: error
|
||||||
|
|
||||||
|
visible: LauncherState.lastLoginErrorMessage.length > 0
|
||||||
|
text: LauncherState.lastLoginErrorMessage
|
||||||
|
anchors {
|
||||||
|
top: title.bottom
|
||||||
|
topMargin: 18
|
||||||
|
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextField {
|
||||||
|
id: username
|
||||||
|
|
||||||
|
enabled: root.enabled
|
||||||
|
width: 430
|
||||||
|
|
||||||
|
text: LauncherState.lastUsedUsername
|
||||||
|
placeholderText: "Username or Email address"
|
||||||
|
|
||||||
|
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||||
|
anchors {
|
||||||
|
top: error.bottom
|
||||||
|
topMargin: 24
|
||||||
|
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextField {
|
||||||
|
id: password
|
||||||
|
width: 430
|
||||||
|
enabled: root.enabled
|
||||||
|
|
||||||
|
placeholderText: "Password"
|
||||||
|
togglePasswordField: true
|
||||||
|
echoMode: TextInput.Password
|
||||||
|
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||||
|
anchors {
|
||||||
|
top: username.bottom
|
||||||
|
topMargin: 25
|
||||||
|
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HFTextRegular {
|
||||||
|
id: displayText
|
||||||
|
|
||||||
|
text: "This is the display name other people see in High Fidelity. It can be changed at any time from your profile."
|
||||||
|
wrapMode: Text.Wrap
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: password.bottom
|
||||||
|
topMargin: 50
|
||||||
|
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextField {
|
||||||
|
id: displayName
|
||||||
|
width: 430
|
||||||
|
enabled: root.enabled
|
||||||
|
|
||||||
|
placeholderText: "Display name"
|
||||||
|
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||||
|
anchors {
|
||||||
|
top: displayText.bottom
|
||||||
|
topMargin: 4
|
||||||
|
|
||||||
|
left: parent.left
|
||||||
|
right: parent.right;
|
||||||
|
}
|
||||||
|
onAccepted: {
|
||||||
|
if (root.enabled && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0) {
|
||||||
|
LauncherState.login(username.text, password.text, displayName.text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFButton {
|
||||||
|
id: button
|
||||||
|
width: 134
|
||||||
|
|
||||||
|
enabled: root.enabled && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0
|
||||||
|
|
||||||
|
text: "NEXT"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: displayName.bottom
|
||||||
|
topMargin: 25
|
||||||
|
|
||||||
|
left: parent.left
|
||||||
|
}
|
||||||
|
|
||||||
|
onClicked: LauncherState.login(username.text, password.text, displayName.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: createAccountLink
|
||||||
|
|
||||||
|
text: "Sign up"
|
||||||
|
font.family: "Graphik"
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: "#009EE0"
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
top: button.bottom
|
||||||
|
topMargin: 16
|
||||||
|
left: parent.left
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
console.log("clicked");
|
||||||
|
LauncherState.gotoSignup();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextLogo {
|
||||||
|
anchors {
|
||||||
|
bottom: createAccountLink.bottom
|
||||||
|
|
||||||
|
right: parent.right
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.parent.setBuildInfoState("right");
|
||||||
|
}
|
||||||
|
}
|
44
launchers/qt/resources/qml/HFControls/HFButton.qml
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id: control
|
||||||
|
|
||||||
|
height: 50
|
||||||
|
|
||||||
|
property string backgroundColor: "#00000000"
|
||||||
|
property string borderColor: enabled ? "#FFFFFF" : "#7e8c81"
|
||||||
|
property string textColor: borderColor
|
||||||
|
property int backgroundOpacity: 1
|
||||||
|
property int backgroundRadius: 1
|
||||||
|
property int backgroundWidth: 2
|
||||||
|
|
||||||
|
font.family: "Graphik Semibold"
|
||||||
|
font.pixelSize: 15
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
implicitWidth: 100
|
||||||
|
implicitHeight: 40
|
||||||
|
color: control.backgroundColor
|
||||||
|
opacity: control.backgroundOpacity
|
||||||
|
border.color: control.borderColor
|
||||||
|
border.width: control.backgroundWidth
|
||||||
|
radius: control.backgroundRadius
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: Text {
|
||||||
|
text: control.text
|
||||||
|
font: control.font
|
||||||
|
color: control.textColor
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
elide: Text.ElideRight
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
id: mouseArea
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
anchors.fill: parent
|
||||||
|
onPressed: mouse.accepted = false
|
||||||
|
}
|
||||||
|
}
|
7
launchers/qt/resources/qml/HFControls/HFTextError.qml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick 2.1
|
||||||
|
|
||||||
|
HFTextRegular {
|
||||||
|
color: "#FF0014"
|
||||||
|
}
|
||||||
|
|
90
launchers/qt/resources/qml/HFControls/HFTextField.qml
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
import QtQuick 2.5
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: control
|
||||||
|
|
||||||
|
height: 50
|
||||||
|
|
||||||
|
font.family: "Graphik Regular"
|
||||||
|
font.pixelSize: 14
|
||||||
|
color: (text.length == 0 || !enabled) ? "#7e8c81" : "#000000"
|
||||||
|
|
||||||
|
property bool togglePasswordField: false
|
||||||
|
verticalAlignment: TextInput.AlignVCenter
|
||||||
|
horizontalAlignment: TextInput.AlignLeft
|
||||||
|
placeholderText: "PlaceHolder"
|
||||||
|
property string seperatorColor: "#FFFFFF"
|
||||||
|
selectByMouse: true
|
||||||
|
|
||||||
|
background: Item {
|
||||||
|
anchors.fill: parent
|
||||||
|
Rectangle {
|
||||||
|
id: background
|
||||||
|
color: "#FFFFFF"
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Image {
|
||||||
|
id: hide
|
||||||
|
visible: control.togglePasswordField
|
||||||
|
source: (control.echoMode == TextInput.Password) ? PathUtils.resourcePath("images/showPass.png") :
|
||||||
|
PathUtils.resourcePath("images/hidePass.png");
|
||||||
|
fillMode: Image.PreserveAspectFit
|
||||||
|
width: 24
|
||||||
|
smooth: true
|
||||||
|
anchors {
|
||||||
|
top: parent.top
|
||||||
|
topMargin: 18
|
||||||
|
bottom: parent.bottom
|
||||||
|
bottomMargin: 18
|
||||||
|
right: parent.right
|
||||||
|
rightMargin: 13
|
||||||
|
}
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
onClicked: {
|
||||||
|
if (control.echoMode === TextInput.Password) {
|
||||||
|
control.echoMode = TextInput.Normal;
|
||||||
|
} else {
|
||||||
|
control.echoMode = TextInput.Password;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: {
|
||||||
|
event.accepted = false;
|
||||||
|
|
||||||
|
if (Platform === "MacOS") {
|
||||||
|
if (event.key == Qt.Key_Left) {
|
||||||
|
if (control.cursorPosition > 0) {
|
||||||
|
var index = control.cursorPosition - 1;
|
||||||
|
control.select(index, index);
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key == Qt.Key_Right) {
|
||||||
|
if (control.cursorPosition < control.text.length) {
|
||||||
|
var index = control.cursorPosition + 1;
|
||||||
|
control.select(index, index);
|
||||||
|
}
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.modifiers & Qt.ControlModifier) {
|
||||||
|
if (event.key == Qt.Key_C) {
|
||||||
|
control.copy();
|
||||||
|
event.accepted = true;
|
||||||
|
} else if (event.key == Qt.Key_V) {
|
||||||
|
control.paste();
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
launchers/qt/resources/qml/HFControls/HFTextHeader.qml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick 2.1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
font.family: "Graphik Semibold"
|
||||||
|
font.pixelSize: 32
|
||||||
|
color: "#ffffff"
|
||||||
|
}
|
11
launchers/qt/resources/qml/HFControls/HFTextLogo.qml
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick 2.1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
text: "High Fidelity"
|
||||||
|
font.bold: true
|
||||||
|
font.family: "Graphik Semibold"
|
||||||
|
font.pixelSize: 17
|
||||||
|
font.letterSpacing: -1
|
||||||
|
color: "#FFFFFF"
|
||||||
|
}
|
18
launchers/qt/resources/qml/HFControls/HFTextRegular.qml
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick 2.1
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
font.family: "Graphik Regular"
|
||||||
|
font.pixelSize: 14
|
||||||
|
|
||||||
|
color: "#C4C4C4"
|
||||||
|
linkColor: color
|
||||||
|
|
||||||
|
MouseArea {
|
||||||
|
anchors.fill: root
|
||||||
|
cursorShape: root.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
acceptedButtons: Qt.NoButton
|
||||||
|
}
|
||||||
|
}
|
7
launchers/qt/resources/qml/Login.qml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
// login
|
||||||
|
|
||||||
|
import "HFBase"
|
||||||
|
|
||||||
|
LoginBase {
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
28
launchers/qt/resources/qml/SplashScreen.qml
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
anchors.fill: parent
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
mirror: false
|
||||||
|
source: PathUtils.resourcePath("images/hifi_window@2x.png");
|
||||||
|
transformOrigin: Item.Center
|
||||||
|
rotation: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: 240
|
||||||
|
height: 180
|
||||||
|
source: PathUtils.resourcePath("images/hifi_logo_large@2x.png");
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
root.parent.setBuildInfoState("right");
|
||||||
|
}
|
||||||
|
}
|
67
launchers/qt/resources/qml/root.qml
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
// root.qml
|
||||||
|
|
||||||
|
import QtQuick 2.3
|
||||||
|
import QtQuick.Controls 2.1
|
||||||
|
import HQLauncher 1.0
|
||||||
|
import "HFControls"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
Loader {
|
||||||
|
anchors.fill: parent
|
||||||
|
id: loader
|
||||||
|
|
||||||
|
|
||||||
|
function setBuildInfoState(state) {
|
||||||
|
buildInfo.state = state;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
loader.source = "./SplashScreen.qml";
|
||||||
|
LauncherState.updateSourceUrl.connect(function(url) {
|
||||||
|
loader.source = url;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function loadPage(url) {
|
||||||
|
loader.source = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
HFTextRegular {
|
||||||
|
id: buildInfo
|
||||||
|
|
||||||
|
anchors {
|
||||||
|
leftMargin: 10
|
||||||
|
rightMargin: 10
|
||||||
|
bottomMargin: 10
|
||||||
|
|
||||||
|
right: root.right
|
||||||
|
bottom: root.bottom
|
||||||
|
}
|
||||||
|
|
||||||
|
color: "#666"
|
||||||
|
text: "V." + LauncherState.buildVersion;
|
||||||
|
|
||||||
|
states: [
|
||||||
|
State {
|
||||||
|
name: "left"
|
||||||
|
AnchorChanges {
|
||||||
|
target: buildInfo
|
||||||
|
anchors.left: root.left
|
||||||
|
anchors.right: undefined
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
State {
|
||||||
|
name: "right"
|
||||||
|
AnchorChanges {
|
||||||
|
target: buildInfo
|
||||||
|
anchors.right: root.right
|
||||||
|
anchors.left: undefined
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
115
launchers/qt/src/BuildsRequest.cpp
Normal file
|
@ -0,0 +1,115 @@
|
||||||
|
#include "BuildsRequest.h"
|
||||||
|
|
||||||
|
#include "Helper.h"
|
||||||
|
|
||||||
|
#include <QUrlQuery>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QProcessEnvironment>
|
||||||
|
|
||||||
|
bool Builds::getBuild(QString tag, Build* outBuild) {
|
||||||
|
if (tag.isNull()) {
|
||||||
|
tag = defaultTag;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& build : builds) {
|
||||||
|
if (build.tag == tag) {
|
||||||
|
*outBuild = build;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuildsRequest::send(QNetworkAccessManager& nam) {
|
||||||
|
QString latestBuildRequestUrl { "https://thunder.highfidelity.com/builds/api/tags/latest/?format=json" };
|
||||||
|
QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment();
|
||||||
|
|
||||||
|
if (processEnvironment.contains("HQ_LAUNCHER_BUILDS_URL")) {
|
||||||
|
latestBuildRequestUrl = processEnvironment.value("HQ_LAUNCHER_BUILDS_URL");
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << latestBuildRequestUrl;
|
||||||
|
|
||||||
|
QNetworkRequest request{ QUrl(latestBuildRequestUrl) };
|
||||||
|
auto reply = nam.get(request);
|
||||||
|
|
||||||
|
QObject::connect(reply, &QNetworkReply::finished, this, &BuildsRequest::receivedResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BuildsRequest::receivedResponse() {
|
||||||
|
_state = State::Finished;
|
||||||
|
|
||||||
|
auto reply = static_cast<QNetworkReply*>(sender());
|
||||||
|
|
||||||
|
if (reply->error()) {
|
||||||
|
qDebug() << "Error getting builds from thunder: " << reply->errorString();
|
||||||
|
_error = Error::Unknown;
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Builds reply has been received";
|
||||||
|
|
||||||
|
auto data = reply->readAll();
|
||||||
|
|
||||||
|
QJsonParseError parseError;
|
||||||
|
auto doc = QJsonDocument::fromJson(data, &parseError);
|
||||||
|
|
||||||
|
if (parseError.error) {
|
||||||
|
qDebug() << "Error parsing response from thunder: " << data;
|
||||||
|
_error = Error::Unknown;
|
||||||
|
} else {
|
||||||
|
auto root = doc.object();
|
||||||
|
if (!root.contains("default_tag")) {
|
||||||
|
_error = Error::MissingDefaultTag;
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_latestBuilds.defaultTag = root["default_tag"].toString();
|
||||||
|
|
||||||
|
auto results = root["results"];
|
||||||
|
if (!results.isArray()) {
|
||||||
|
_error = Error::MalformedResponse;
|
||||||
|
emit finished();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto result : results.toArray()) {
|
||||||
|
auto entry = result.toObject();
|
||||||
|
Build build;
|
||||||
|
build.tag = entry["name"].toString();
|
||||||
|
build.latestVersion = entry["latest_version"].toInt();
|
||||||
|
build.buildNumber = entry["build_number"].toInt();
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
build.installerZipURL = entry["installers"].toObject()["windows"].toObject()["zip_url"].toString();
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
build.installerZipURL = entry["installers"].toObject()["mac"].toObject()["zip_url"].toString();
|
||||||
|
#else
|
||||||
|
#error "Launcher is only supported on Windows and Mac OS"
|
||||||
|
#endif
|
||||||
|
_latestBuilds.builds.push_back(build);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto launcherResults = root["launcher"].toObject();
|
||||||
|
|
||||||
|
Build launcherBuild;
|
||||||
|
launcherBuild.latestVersion = launcherResults["version"].toInt();
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
launcherBuild.installerZipURL = launcherResults["windows"].toObject()["url"].toString();
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
launcherBuild.installerZipURL = launcherResults["mac"].toObject()["url"].toString();
|
||||||
|
#else
|
||||||
|
#error "Launcher is only supported on Windows and Mac OS"
|
||||||
|
#endif
|
||||||
|
_latestBuilds.launcherBuild = launcherBuild;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
emit finished();
|
||||||
|
}
|
54
launchers/qt/src/BuildsRequest.h
Normal file
|
@ -0,0 +1,54 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
|
||||||
|
struct Build {
|
||||||
|
QString tag{ QString::null };
|
||||||
|
int latestVersion{ 0 };
|
||||||
|
int buildNumber{ 0 };
|
||||||
|
QString installerZipURL{ QString::null };
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Builds {
|
||||||
|
bool getBuild(QString tag, Build* outBuild);
|
||||||
|
|
||||||
|
QString defaultTag;
|
||||||
|
std::vector<Build> builds;
|
||||||
|
Build launcherBuild;
|
||||||
|
};
|
||||||
|
|
||||||
|
class BuildsRequest : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
public:
|
||||||
|
enum class State {
|
||||||
|
Unsent,
|
||||||
|
Sending,
|
||||||
|
Finished
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class Error {
|
||||||
|
None = 0,
|
||||||
|
Unknown,
|
||||||
|
MalformedResponse,
|
||||||
|
MissingDefaultTag,
|
||||||
|
};
|
||||||
|
Q_ENUM(Error)
|
||||||
|
|
||||||
|
void send(QNetworkAccessManager& nam);
|
||||||
|
Error getError() const { return _error; }
|
||||||
|
|
||||||
|
const Builds& getLatestBuilds() const { return _latestBuilds; }
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void finished();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void receivedResponse();
|
||||||
|
|
||||||
|
private:
|
||||||
|
State _state { State::Unsent };
|
||||||
|
Error _error { Error::None };
|
||||||
|
|
||||||
|
Builds _latestBuilds;
|
||||||
|
};
|
35
launchers/qt/src/CommandlineOptions.cpp
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
#include "CommandlineOptions.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <iostream>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
bool isCommandlineOption(const std::string& option) {
|
||||||
|
if (option.rfind("--", 0) == 0 && option.at(2) != '-') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool CommandlineOptions::contains(const std::string& option) {
|
||||||
|
auto iter = std::find(_commandlineOptions.begin(), _commandlineOptions.end(), option);
|
||||||
|
return (iter != _commandlineOptions.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandlineOptions::parse(const int argc, char** argv) {
|
||||||
|
for (int index = 1; index < argc; index++) {
|
||||||
|
std::string option = argv[index];
|
||||||
|
if (isCommandlineOption(option)) {
|
||||||
|
_commandlineOptions.push_back(option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandlineOptions::append(const std::string& command) {
|
||||||
|
_commandlineOptions.push_back(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandlineOptions* CommandlineOptions::getInstance() {
|
||||||
|
static CommandlineOptions commandlineOptions;
|
||||||
|
return &commandlineOptions;
|
||||||
|
}
|
17
launchers/qt/src/CommandlineOptions.h
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
class CommandlineOptions {
|
||||||
|
public:
|
||||||
|
CommandlineOptions() = default;
|
||||||
|
~CommandlineOptions() = default;
|
||||||
|
|
||||||
|
void parse(const int argc, char** argv);
|
||||||
|
bool contains(const std::string& option);
|
||||||
|
void append(const std::string& command);
|
||||||
|
static CommandlineOptions* getInstance();
|
||||||
|
private:
|
||||||
|
std::vector<std::string> _commandlineOptions;
|
||||||
|
};
|
105
launchers/qt/src/Helper.cpp
Normal file
|
@ -0,0 +1,105 @@
|
||||||
|
#include "Helper.h"
|
||||||
|
|
||||||
|
#include "PathUtils.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QDir>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QProcess>
|
||||||
|
#include <QProcessEnvironment>
|
||||||
|
#include <QDateTime>
|
||||||
|
#include <QTextStream>
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
|
||||||
|
QString getMetaverseAPIDomain() {
|
||||||
|
QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment();
|
||||||
|
if (processEnvironment.contains("HIFI_METAVERSE_URL")) {
|
||||||
|
return processEnvironment.value("HIFI_METAVERSE_URL");
|
||||||
|
}
|
||||||
|
return "https://metaverse.highfidelity.com";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||||
|
Q_UNUSED(context);
|
||||||
|
|
||||||
|
QString date = QDateTime::currentDateTime().toString("dd/MM/yyyy hh:mm:ss");
|
||||||
|
QString txt = QString("[%1] ").arg(date);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case QtDebugMsg:
|
||||||
|
txt += QString("{Debug} \t\t %1").arg(message);
|
||||||
|
break;
|
||||||
|
case QtWarningMsg:
|
||||||
|
txt += QString("{Warning} \t %1").arg(message);
|
||||||
|
break;
|
||||||
|
case QtCriticalMsg:
|
||||||
|
txt += QString("{Critical} \t %1").arg(message);
|
||||||
|
break;
|
||||||
|
case QtFatalMsg:
|
||||||
|
txt += QString("{Fatal} \t\t %1").arg(message);
|
||||||
|
break;
|
||||||
|
case QtInfoMsg:
|
||||||
|
txt += QString("{Info} \t %1").arg(message);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
QDir logsDir = PathUtils::getLogsDirectory();
|
||||||
|
logsDir.mkpath(logsDir.absolutePath());
|
||||||
|
QString filename = logsDir.absoluteFilePath("Log.txt");
|
||||||
|
|
||||||
|
QFile outFile(filename);
|
||||||
|
outFile.open(QIODevice::WriteOnly | QIODevice::Append);
|
||||||
|
|
||||||
|
QTextStream textStream(&outFile);
|
||||||
|
std::cout << txt.toStdString() << "\n";
|
||||||
|
textStream << txt << "\n";
|
||||||
|
outFile.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPath) {
|
||||||
|
if (!(QFileInfo::exists(oldLauncherPath) && QFileInfo::exists(newLauncherPath))) {
|
||||||
|
qDebug() << "old launcher: " << oldLauncherPath << "new launcher: " << newLauncherPath << " file does not exist";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool success = false;
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
qDebug() << "replacing launchers -> old launcher: " << oldLauncherPath << " new launcher: " << newLauncherPath;
|
||||||
|
success = replaceDirectory(oldLauncherPath, newLauncherPath);
|
||||||
|
#endif
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void cleanLogFile() {
|
||||||
|
QDir launcherDirectory = PathUtils::getLogsDirectory();
|
||||||
|
launcherDirectory.mkpath(launcherDirectory.absolutePath());
|
||||||
|
QString filename = launcherDirectory.absoluteFilePath("Log.txt");
|
||||||
|
QString tmpFilename = launcherDirectory.absoluteFilePath("Log-last.txt");
|
||||||
|
if (QFile::exists(filename)) {
|
||||||
|
if (QFile::exists(tmpFilename)) {
|
||||||
|
QFile::remove(tmpFilename);
|
||||||
|
}
|
||||||
|
QFile::rename(filename, tmpFilename);
|
||||||
|
QFile::remove(filename);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getHTTPUserAgent() {
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
return "HQLauncher/fixme (Windows)";
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
return "HQLauncher/fixme (MacOS)";
|
||||||
|
#else
|
||||||
|
#error Unsupported platform
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString& getInterfaceSharedMemoryName() {
|
||||||
|
static const QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME");
|
||||||
|
return applicationName;
|
||||||
|
}
|
39
launchers/qt/src/Helper.h
Normal file
|
@ -0,0 +1,39 @@
|
||||||
|
#include <QString>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include "Windows.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString getMetaverseAPIDomain();
|
||||||
|
|
||||||
|
void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride,
|
||||||
|
const QString& displayName, const QString& contentCachePath, QString loginResponseToken = QString());
|
||||||
|
|
||||||
|
|
||||||
|
void launchAutoUpdater(const QString& autoUpdaterPath);
|
||||||
|
bool swapLaunchers(const QString& oldLauncherPath = QString(), const QString& newLauncherPath = QString());
|
||||||
|
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message);
|
||||||
|
void cleanLogFile();
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory);
|
||||||
|
void closeInterfaceIfRunning();
|
||||||
|
void waitForInterfaceToClose();
|
||||||
|
bool isLauncherAlreadyRunning();
|
||||||
|
QString getBundlePath();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
HRESULT createSymbolicLink(LPCSTR lpszPathObj, LPCSTR lpszPathLink, LPCSTR lpszDesc, LPCSTR lpszArgs = (LPCSTR)"");
|
||||||
|
bool insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value);
|
||||||
|
bool insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value);
|
||||||
|
bool deleteRegistryKey(const std::string& regPath);
|
||||||
|
|
||||||
|
BOOL isProcessRunning(const char* processName, int& processID);
|
||||||
|
BOOL shutdownProcess(DWORD dwProcessId, UINT uExitCode);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString getHTTPUserAgent();
|
||||||
|
|
||||||
|
const QString& getInterfaceSharedMemoryName();
|
150
launchers/qt/src/Helper_darwin.mm
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
#include "Helper.h"
|
||||||
|
|
||||||
|
#import "NSTask+NSTaskExecveAdditions.h"
|
||||||
|
|
||||||
|
#import <Cocoa/Cocoa.h>
|
||||||
|
#include <QString>
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride,
|
||||||
|
const QString& displayName, const QString& contentCachePath, QString loginTokenResponse) {
|
||||||
|
|
||||||
|
NSString* homeBookmark = [[NSString stringWithFormat:@"hqhome="] stringByAppendingString:homePath.toNSString()];
|
||||||
|
NSArray* arguments;
|
||||||
|
if (!loginTokenResponse.isEmpty()) {
|
||||||
|
arguments = [NSArray arrayWithObjects:
|
||||||
|
@"--url" , homePath.toNSString(),
|
||||||
|
@"--tokens", loginTokenResponse.toNSString(),
|
||||||
|
@"--cache", contentCachePath.toNSString(),
|
||||||
|
@"--displayName", displayName.toNSString(),
|
||||||
|
@"--defaultScriptsOverride", defaultScriptOverride.toNSString(),
|
||||||
|
@"--setBookmark", homeBookmark,
|
||||||
|
@"--no-updater",
|
||||||
|
@"--no-launcher",
|
||||||
|
@"--suppress-settings-reset", nil];
|
||||||
|
} else {
|
||||||
|
arguments = [NSArray arrayWithObjects:
|
||||||
|
@"--url" , homePath.toNSString(),
|
||||||
|
@"--cache", contentCachePath.toNSString(),
|
||||||
|
@"--defaultScriptsOverride", defaultScriptOverride.toNSString(),
|
||||||
|
@"--setBookmark", homeBookmark,
|
||||||
|
@"--no-updater",
|
||||||
|
@"--no-launcher",
|
||||||
|
@"--suppress-settings-reset", nil];
|
||||||
|
}
|
||||||
|
|
||||||
|
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
|
||||||
|
NSURL *url = [NSURL fileURLWithPath:[workspace fullPathForApplication:clientPath.toNSString()]];
|
||||||
|
NSTask *task = [[NSTask alloc] init];
|
||||||
|
task.launchPath = [url path];
|
||||||
|
task.arguments = arguments;
|
||||||
|
[task replaceThisProcess];
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
QString getBundlePath() {
|
||||||
|
return QString::fromNSString([[NSBundle mainBundle] bundlePath]);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void launchAutoUpdater(const QString& autoUpdaterPath) {
|
||||||
|
NSException *exception;
|
||||||
|
bool launched = false;
|
||||||
|
// Older versions of Launcher put updater in `/Contents/Resources/updater`.
|
||||||
|
NSString* newLauncher = autoUpdaterPath.toNSString();
|
||||||
|
for (NSString *bundlePath in @[@"/Contents/MacOS/updater",
|
||||||
|
@"/Contents/Resources/updater",
|
||||||
|
]) {
|
||||||
|
NSTask* task = [[NSTask alloc] init];
|
||||||
|
task.launchPath = [newLauncher stringByAppendingString: bundlePath];
|
||||||
|
task.arguments = @[[[NSBundle mainBundle] bundlePath], newLauncher];
|
||||||
|
|
||||||
|
qDebug() << "launching updater: " << task.launchPath << task.arguments;
|
||||||
|
|
||||||
|
@try {
|
||||||
|
[task launch];
|
||||||
|
}
|
||||||
|
@catch (NSException *e) {
|
||||||
|
qDebug() << "couldn't launch updater: " << QString::fromNSString(e.name) << QString::fromNSString(e.reason);
|
||||||
|
exception = e;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
launched = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!launched) {
|
||||||
|
@throw exception;
|
||||||
|
}
|
||||||
|
|
||||||
|
QCoreApplication::instance()->quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@interface UpdaterHelper : NSObject
|
||||||
|
+(NSURL*) NSStringToNSURL: (NSString*) path;
|
||||||
|
@end
|
||||||
|
|
||||||
|
@implementation UpdaterHelper
|
||||||
|
+(NSURL*) NSStringToNSURL: (NSString*) path
|
||||||
|
{
|
||||||
|
return [NSURL URLWithString: [path stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]] relativeToURL: [NSURL URLWithString:@"file://"]];
|
||||||
|
}
|
||||||
|
@end
|
||||||
|
|
||||||
|
|
||||||
|
bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory) {
|
||||||
|
NSError *error = nil;
|
||||||
|
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||||
|
NSURL* destinationUrl = [UpdaterHelper NSStringToNSURL:newDirectory.toNSString()];
|
||||||
|
bool success = (bool) [fileManager replaceItemAtURL:[UpdaterHelper NSStringToNSURL:orginalDirectory.toNSString()] withItemAtURL:[UpdaterHelper NSStringToNSURL:newDirectory.toNSString()]
|
||||||
|
backupItemName:nil options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:&destinationUrl error:&error];
|
||||||
|
|
||||||
|
if (error != nil) {
|
||||||
|
qDebug() << "NSFileManager::replaceItemAtURL -> error: " << error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return success;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void waitForInterfaceToClose() {
|
||||||
|
bool interfaceRunning = true;
|
||||||
|
|
||||||
|
while (interfaceRunning) {
|
||||||
|
interfaceRunning = false;
|
||||||
|
NSWorkspace* workspace = [NSWorkspace sharedWorkspace];
|
||||||
|
NSArray* apps = [workspace runningApplications];
|
||||||
|
for (NSRunningApplication* app in apps) {
|
||||||
|
if ([[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface"] ||
|
||||||
|
[[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface-pr"]) {
|
||||||
|
interfaceRunning = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isLauncherAlreadyRunning() {
|
||||||
|
NSArray* apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.highfidelity.launcher"];
|
||||||
|
if ([apps count] > 1) {
|
||||||
|
qDebug() << "launcher is already running";
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void closeInterfaceIfRunning() {
|
||||||
|
NSWorkspace* workspace = [NSWorkspace sharedWorkspace];
|
||||||
|
NSArray* apps = [workspace runningApplications];
|
||||||
|
for (NSRunningApplication* app in apps) {
|
||||||
|
if ([[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface"] ||
|
||||||
|
[[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface-pr"]) {
|
||||||
|
[app terminate];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
188
launchers/qt/src/Helper_windows.cpp
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
#include "Helper.h"
|
||||||
|
|
||||||
|
#include <QCoreApplication>
|
||||||
|
|
||||||
|
#include "windows.h"
|
||||||
|
#include "winnls.h"
|
||||||
|
#include "shobjidl.h"
|
||||||
|
#include "objbase.h"
|
||||||
|
#include "objidl.h"
|
||||||
|
#include "shlguid.h"
|
||||||
|
#include <atlstr.h>
|
||||||
|
#include <tlhelp32.h>
|
||||||
|
#include <strsafe.h>
|
||||||
|
|
||||||
|
void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptsPath,
|
||||||
|
const QString& displayName, const QString& contentCachePath, QString loginResponseToken) {
|
||||||
|
|
||||||
|
// TODO Fix parameters
|
||||||
|
QString params = "\"" + clientPath + "\"" + " --url \"" + homePath + "\""
|
||||||
|
+ " --setBookmark \"hqhome=" + homePath + "\""
|
||||||
|
+ " --defaultScriptsOverride \"file:///" + defaultScriptsPath + "\""
|
||||||
|
+ " --cache \"" + contentCachePath + "\""
|
||||||
|
+ " --suppress-settings-reset --no-launcher --no-updater";
|
||||||
|
|
||||||
|
if (!displayName.isEmpty()) {
|
||||||
|
params += " --displayName \"" + displayName + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loginResponseToken.isEmpty()) {
|
||||||
|
params += " --tokens \"" + loginResponseToken.replace("\"", "\\\"") + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
STARTUPINFO si;
|
||||||
|
PROCESS_INFORMATION pi;
|
||||||
|
|
||||||
|
// set the size of the structures
|
||||||
|
ZeroMemory(&si, sizeof(si));
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
ZeroMemory(&pi, sizeof(pi));
|
||||||
|
|
||||||
|
// start the program up
|
||||||
|
BOOL success = CreateProcess(
|
||||||
|
clientPath.toLatin1().data(),
|
||||||
|
params.toLatin1().data(),
|
||||||
|
nullptr, // Process handle not inheritable
|
||||||
|
nullptr, // Thread handle not inheritable
|
||||||
|
FALSE, // Set handle inheritance to FALSE
|
||||||
|
CREATE_NEW_CONSOLE, // Opens file in a separate console
|
||||||
|
nullptr, // Use parent's environment block
|
||||||
|
nullptr, // Use parent's starting directory
|
||||||
|
&si, // Pointer to STARTUPINFO structure
|
||||||
|
&pi // Pointer to PROCESS_INFORMATION structure
|
||||||
|
);
|
||||||
|
|
||||||
|
// Close process and thread handles.
|
||||||
|
CloseHandle(pi.hProcess);
|
||||||
|
CloseHandle(pi.hThread);
|
||||||
|
}
|
||||||
|
|
||||||
|
void launchAutoUpdater(const QString& autoUpdaterPath) {
|
||||||
|
QString params = "\"" + QCoreApplication::applicationFilePath() + "\"" + " --restart --noUpdate";
|
||||||
|
STARTUPINFO si;
|
||||||
|
PROCESS_INFORMATION pi;
|
||||||
|
|
||||||
|
ZeroMemory(&si, sizeof(si));
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
ZeroMemory(&pi, sizeof(pi));
|
||||||
|
|
||||||
|
BOOL success = CreateProcess(
|
||||||
|
autoUpdaterPath.toUtf8().data(),
|
||||||
|
params.toUtf8().data(),
|
||||||
|
nullptr, // Process handle not inheritable
|
||||||
|
nullptr, // Thread handle not inheritable
|
||||||
|
FALSE, // Set handle inheritance to FALSE
|
||||||
|
CREATE_NEW_CONSOLE, // Opens file in a separate console
|
||||||
|
nullptr, // Use parent's environment block
|
||||||
|
nullptr, // Use parent's starting directory
|
||||||
|
&si, // Pointer to STARTUPINFO structure
|
||||||
|
&pi // Pointer to PROCESS_INFORMATION structure
|
||||||
|
);
|
||||||
|
|
||||||
|
QCoreApplication::instance()->quit();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HRESULT createSymbolicLink(LPCSTR lpszPathObj, LPCSTR lpszPathLink, LPCSTR lpszDesc, LPCSTR lpszArgs) {
|
||||||
|
IShellLink* psl;
|
||||||
|
|
||||||
|
// Get a pointer to the IShellLink interface. It is assumed that CoInitialize
|
||||||
|
// has already been called.
|
||||||
|
CoInitialize(NULL);
|
||||||
|
HRESULT hres = E_INVALIDARG;
|
||||||
|
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
|
||||||
|
if (SUCCEEDED(hres)) {
|
||||||
|
IPersistFile* ppf;
|
||||||
|
|
||||||
|
// Set the path to the shortcut target and add the description.
|
||||||
|
psl->SetPath(lpszPathObj);
|
||||||
|
psl->SetDescription(lpszDesc);
|
||||||
|
psl->SetArguments(lpszArgs);
|
||||||
|
|
||||||
|
// Query IShellLink for the IPersistFile interface, used for saving the
|
||||||
|
// shortcut in persistent storage.
|
||||||
|
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
|
||||||
|
|
||||||
|
if (SUCCEEDED(hres)) {
|
||||||
|
WCHAR wsz[MAX_PATH];
|
||||||
|
|
||||||
|
// Ensure that the string is Unicode.
|
||||||
|
MultiByteToWideChar(CP_ACP, 0, lpszPathLink, -1, wsz, MAX_PATH);
|
||||||
|
|
||||||
|
// Add code here to check return value from MultiByteWideChar
|
||||||
|
// for success.
|
||||||
|
|
||||||
|
// Save the link by calling IPersistFile::Save.
|
||||||
|
hres = ppf->Save(wsz, TRUE);
|
||||||
|
ppf->Release();
|
||||||
|
}
|
||||||
|
psl->Release();
|
||||||
|
}
|
||||||
|
CoUninitialize();
|
||||||
|
return SUCCEEDED(hres);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value) {
|
||||||
|
HKEY key;
|
||||||
|
auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL);
|
||||||
|
if (status == ERROR_SUCCESS) {
|
||||||
|
status = RegSetValueExA(key, name.c_str(), 0, REG_SZ, (const BYTE*)value.c_str(), (DWORD)(value.size() + 1));
|
||||||
|
return (bool) (status == ERROR_SUCCESS);
|
||||||
|
}
|
||||||
|
RegCloseKey(key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value) {
|
||||||
|
HKEY key;
|
||||||
|
auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL);
|
||||||
|
if (status == ERROR_SUCCESS) {
|
||||||
|
status = RegSetValueExA(key, name.c_str(), 0, REG_DWORD, (const BYTE*)&value, sizeof(value));
|
||||||
|
return (bool) TRUE;
|
||||||
|
}
|
||||||
|
RegCloseKey(key);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool deleteRegistryKey(const std::string& regPath) {
|
||||||
|
TCHAR szDelKey[MAX_PATH * 2];
|
||||||
|
StringCchCopy(szDelKey, MAX_PATH * 2, regPath.c_str());
|
||||||
|
auto status = RegDeleteKey(HKEY_CURRENT_USER, szDelKey);
|
||||||
|
if (status == ERROR_SUCCESS) {
|
||||||
|
return (bool) TRUE;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
BOOL isProcessRunning(const char* processName, int& processID) {
|
||||||
|
bool exists = false;
|
||||||
|
PROCESSENTRY32 entry;
|
||||||
|
entry.dwSize = sizeof(PROCESSENTRY32);
|
||||||
|
|
||||||
|
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
|
||||||
|
|
||||||
|
if (Process32First(snapshot, &entry)) {
|
||||||
|
while (Process32Next(snapshot, &entry)) {
|
||||||
|
if (!_stricmp(entry.szExeFile, processName)) {
|
||||||
|
exists = true;
|
||||||
|
processID = entry.th32ProcessID;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CloseHandle(snapshot);
|
||||||
|
return exists;
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL shutdownProcess(DWORD dwProcessId, UINT uExitCode) {
|
||||||
|
DWORD dwDesiredAccess = PROCESS_TERMINATE;
|
||||||
|
BOOL bInheritHandle = FALSE;
|
||||||
|
HANDLE hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
|
||||||
|
if (hProcess == NULL) {
|
||||||
|
return FALSE;
|
||||||
|
}
|
||||||
|
BOOL result = TerminateProcess(hProcess, uExitCode);
|
||||||
|
CloseHandle(hProcess);
|
||||||
|
return result;
|
||||||
|
}
|
42
launchers/qt/src/Launcher.cpp
Normal file
|
@ -0,0 +1,42 @@
|
||||||
|
#include "Launcher.h"
|
||||||
|
|
||||||
|
#include <QResource>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QQmlContext>
|
||||||
|
#include <QFontDatabase>
|
||||||
|
|
||||||
|
#include "LauncherWindow.h"
|
||||||
|
#include "LauncherState.h"
|
||||||
|
#include "PathUtils.h"
|
||||||
|
|
||||||
|
Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) {
|
||||||
|
_launcherState = std::make_shared<LauncherState>();
|
||||||
|
QString platform;
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
platform = "Windows";
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
platform = "MacOS";
|
||||||
|
#endif
|
||||||
|
_launcherWindow = std::make_unique<LauncherWindow>();
|
||||||
|
_launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get());
|
||||||
|
_launcherWindow->rootContext()->setContextProperty("PathUtils", new PathUtils());
|
||||||
|
_launcherWindow->rootContext()->setContextProperty("Platform", platform);
|
||||||
|
_launcherWindow->setTitle("High Fidelity");
|
||||||
|
_launcherWindow->setFlags(Qt::FramelessWindowHint | Qt::Window);
|
||||||
|
_launcherWindow->setLauncherStatePtr(_launcherState);
|
||||||
|
|
||||||
|
LauncherState::declareQML();
|
||||||
|
|
||||||
|
QFontDatabase::addApplicationFont(PathUtils::fontPath("Graphik-Regular.ttf"));
|
||||||
|
QFontDatabase::addApplicationFont(PathUtils::fontPath("Graphik-Medium.ttf"));
|
||||||
|
QFontDatabase::addApplicationFont(PathUtils::fontPath("Graphik-Semibold.ttf"));
|
||||||
|
|
||||||
|
_launcherWindow->setSource(QUrl(PathUtils::resourcePath("qml/root.qml")));
|
||||||
|
_launcherWindow->setHeight(540);
|
||||||
|
_launcherWindow->setWidth(627);
|
||||||
|
_launcherWindow->setResizeMode(QQuickView::SizeRootObjectToView);
|
||||||
|
_launcherWindow->show();
|
||||||
|
}
|
||||||
|
|
||||||
|
Launcher::~Launcher() {
|
||||||
|
}
|
16
launchers/qt/src/Launcher.h
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
#pragma once
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include <QGuiApplication>
|
||||||
|
|
||||||
|
class LauncherWindow;
|
||||||
|
class LauncherState;
|
||||||
|
class Launcher : public QGuiApplication {
|
||||||
|
public:
|
||||||
|
Launcher(int& argc, char** argv);
|
||||||
|
~Launcher();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<LauncherWindow> _launcherWindow;
|
||||||
|
std::shared_ptr<LauncherState> _launcherState;
|
||||||
|
};
|
243
launchers/qt/src/LauncherInstaller_windows.cpp
Normal file
|
@ -0,0 +1,243 @@
|
||||||
|
#include "LauncherInstaller_windows.h"
|
||||||
|
|
||||||
|
#include "CommandlineOptions.h"
|
||||||
|
#include "Helper.h"
|
||||||
|
#include "PathUtils.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <chrono>
|
||||||
|
#include <iomanip>
|
||||||
|
#include <ctime>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
#include <QStandardPaths>
|
||||||
|
#include <QFileInfo>
|
||||||
|
#include <QFile>
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
LauncherInstaller::LauncherInstaller() {
|
||||||
|
_launcherInstallDir = PathUtils::getLauncherDirectory();
|
||||||
|
_launcherApplicationsDir = PathUtils::getApplicationsDirectory();
|
||||||
|
qDebug() << "Launcher install dir: " << _launcherInstallDir.absolutePath();
|
||||||
|
qDebug() << "Launcher Application dir: " << _launcherApplicationsDir.absolutePath();
|
||||||
|
|
||||||
|
_launcherInstallDir.mkpath(_launcherInstallDir.absolutePath());
|
||||||
|
_launcherApplicationsDir.mkpath(_launcherApplicationsDir.absolutePath());
|
||||||
|
char appPath[MAX_PATH];
|
||||||
|
GetModuleFileNameA(NULL, appPath, MAX_PATH);
|
||||||
|
QString applicationRunningPath = appPath;
|
||||||
|
QFileInfo fileInfo(applicationRunningPath);
|
||||||
|
_launcherRunningFilePath = fileInfo.absoluteFilePath();
|
||||||
|
_launcherRunningDirPath = fileInfo.absoluteDir().absolutePath();
|
||||||
|
qDebug() << "Launcher running file path: " << _launcherRunningFilePath;
|
||||||
|
qDebug() << "Launcher running dir path: " << _launcherRunningDirPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LauncherInstaller::runningOutsideOfInstallDir() {
|
||||||
|
return (QString::compare(_launcherInstallDir.absolutePath(), _launcherRunningDirPath) != 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherInstaller::install() {
|
||||||
|
if (runningOutsideOfInstallDir()) {
|
||||||
|
qDebug() << "Installing HQ Launcher....";
|
||||||
|
uninstallOldLauncher();
|
||||||
|
QString oldLauncherPath = PathUtils::getLauncherFilePath();
|
||||||
|
|
||||||
|
if (QFile::exists(oldLauncherPath)) {
|
||||||
|
bool didRemove = QFile::remove(oldLauncherPath);
|
||||||
|
qDebug() << "did remove file: " << didRemove;
|
||||||
|
}
|
||||||
|
qDebug() << "Current launcher location: " << _launcherRunningFilePath;
|
||||||
|
bool success = QFile::copy(_launcherRunningFilePath, oldLauncherPath);
|
||||||
|
if (success) {
|
||||||
|
qDebug() << "Launcher installed: " << oldLauncherPath;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Failed to install: " << oldLauncherPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteShortcuts();
|
||||||
|
createShortcuts();
|
||||||
|
deleteApplicationRegistryKeys();
|
||||||
|
createApplicationRegistryKeys();
|
||||||
|
} else {
|
||||||
|
qDebug() << "Failed to install HQ Launcher";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherInstaller::createShortcuts() {
|
||||||
|
QString launcherPath = PathUtils::getLauncherFilePath();
|
||||||
|
|
||||||
|
QString uninstallLinkPath = _launcherInstallDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||||
|
|
||||||
|
QDir desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||||
|
|
||||||
|
QString appStartLinkPath = _launcherApplicationsDir.absoluteFilePath("HQ Launcher.lnk");
|
||||||
|
QString uninstallAppStartLinkPath = _launcherApplicationsDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||||
|
QString desktopAppLinkPath = desktopDir.absoluteFilePath("HQ Launcher.lnk");
|
||||||
|
|
||||||
|
|
||||||
|
createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)uninstallLinkPath.toStdString().c_str(),
|
||||||
|
(LPCSTR)("Click to Uninstall HQ"), (LPCSTR)("--uninstall"));
|
||||||
|
|
||||||
|
createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)uninstallAppStartLinkPath.toStdString().c_str(),
|
||||||
|
(LPCSTR)("Click to Uninstall HQ"), (LPCSTR)("--uninstall"));
|
||||||
|
|
||||||
|
createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)desktopAppLinkPath.toStdString().c_str(),
|
||||||
|
(LPCSTR)("Click to Setup and Launch HQ"));
|
||||||
|
|
||||||
|
createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)appStartLinkPath.toStdString().c_str(),
|
||||||
|
(LPCSTR)("Click to Setup and Launch HQ"));
|
||||||
|
}
|
||||||
|
|
||||||
|
QString randomQtString() {
|
||||||
|
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||||
|
const int randomStringLength = 5;
|
||||||
|
auto now = std::chrono::system_clock::now();
|
||||||
|
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
|
||||||
|
qsrand(duration.count());
|
||||||
|
|
||||||
|
QString randomString;
|
||||||
|
for(int i = 0; i < randomStringLength; i++)
|
||||||
|
{
|
||||||
|
int index = qrand() % possibleCharacters.length();
|
||||||
|
QChar nextChar = possibleCharacters.at(index);
|
||||||
|
randomString.append(nextChar);
|
||||||
|
}
|
||||||
|
return randomString;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherInstaller::uninstall() {
|
||||||
|
qDebug() << "Uninstall Launcher";
|
||||||
|
deleteShortcuts();
|
||||||
|
CommandlineOptions* options = CommandlineOptions::getInstance();
|
||||||
|
if (!options->contains("--resumeUninstall")) {
|
||||||
|
QDir tmpDirectory = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
|
||||||
|
QString destination = tmpDirectory.absolutePath() + "/" + randomQtString() + ".exe";
|
||||||
|
qDebug() << "temp file destination: " << destination;
|
||||||
|
bool copied = QFile::copy(_launcherRunningFilePath, destination);
|
||||||
|
|
||||||
|
if (copied) {
|
||||||
|
QString params = "\"" + _launcherRunningFilePath + "\"" + " --resumeUninstall";
|
||||||
|
STARTUPINFO si;
|
||||||
|
PROCESS_INFORMATION pi;
|
||||||
|
|
||||||
|
ZeroMemory(&si, sizeof(si));
|
||||||
|
si.cb = sizeof(si);
|
||||||
|
ZeroMemory(&pi, sizeof(pi));
|
||||||
|
|
||||||
|
BOOL success = CreateProcess(
|
||||||
|
destination.toUtf8().data(),
|
||||||
|
params.toUtf8().data(),
|
||||||
|
nullptr, // Process handle not inheritable
|
||||||
|
nullptr, // Thread handle not inheritable
|
||||||
|
FALSE, // Set handle inheritance to FALSE
|
||||||
|
CREATE_NEW_CONSOLE, // Opens file in a separate console
|
||||||
|
nullptr, // Use parent's environment block
|
||||||
|
nullptr, // Use parent's starting directory
|
||||||
|
&si, // Pointer to STARTUPINFO structure
|
||||||
|
&pi // Pointer to PROCESS_INFORMATION structure
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Failed to complete uninstall launcher";
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QString launcherPath = _launcherInstallDir.absoluteFilePath("HQ Launcher.exe");
|
||||||
|
if (QFile::exists(launcherPath)) {
|
||||||
|
bool removed = QFile::remove(launcherPath);
|
||||||
|
qDebug() << "Successfully removed " << launcherPath << ": " << removed;
|
||||||
|
}
|
||||||
|
deleteApplicationRegistryKeys();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherInstaller::deleteShortcuts() {
|
||||||
|
QDir desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||||
|
QString applicationPath = _launcherApplicationsDir.absolutePath();
|
||||||
|
|
||||||
|
QString uninstallLinkPath = _launcherInstallDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||||
|
if (QFile::exists(uninstallLinkPath)) {
|
||||||
|
QFile::remove(uninstallLinkPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString appStartLinkPath = _launcherApplicationsDir.absoluteFilePath("HQ Launcher.lnk");
|
||||||
|
if (QFile::exists(appStartLinkPath)) {
|
||||||
|
QFile::remove(appStartLinkPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString uninstallAppStartLinkPath = _launcherApplicationsDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||||
|
if (QFile::exists(uninstallAppStartLinkPath)) {
|
||||||
|
QFile::remove(uninstallAppStartLinkPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString desktopAppLinkPath = desktopDir.absoluteFilePath("HQ Launcher.lnk");
|
||||||
|
if (QFile::exists(desktopAppLinkPath)) {
|
||||||
|
QFile::remove(desktopAppLinkPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherInstaller::uninstallOldLauncher() {
|
||||||
|
QDir localAppDir = QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation).value(0) + "/../../HQ";
|
||||||
|
QDir startAppDir = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).value(0) + "/HQ";
|
||||||
|
QDir desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||||
|
|
||||||
|
qDebug() << localAppDir.absolutePath();
|
||||||
|
qDebug() << startAppDir.absolutePath();
|
||||||
|
QString desktopAppLinkPath = desktopDir.absoluteFilePath("HQ Launcher.lnk");
|
||||||
|
if (QFile::exists(desktopAppLinkPath)) {
|
||||||
|
QFile::remove(desktopAppLinkPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString uninstallLinkPath = localAppDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||||
|
if (QFile::exists(uninstallLinkPath)) {
|
||||||
|
QFile::remove(uninstallLinkPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString applicationPath = localAppDir.absoluteFilePath("HQ Launcher.exe");
|
||||||
|
if (QFile::exists(applicationPath)) {
|
||||||
|
QFile::remove(applicationPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString appStartLinkPath = startAppDir.absoluteFilePath("HQ Launcher.lnk");
|
||||||
|
if (QFile::exists(appStartLinkPath)) {
|
||||||
|
QFile::remove(appStartLinkPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString uninstallAppStartLinkPath = startAppDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||||
|
if (QFile::exists(uninstallAppStartLinkPath)) {
|
||||||
|
QFile::remove(uninstallAppStartLinkPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
void LauncherInstaller::createApplicationRegistryKeys() {
|
||||||
|
const std::string REGISTRY_PATH = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ";
|
||||||
|
bool success = insertRegistryKey(REGISTRY_PATH, "DisplayName", "HQ");
|
||||||
|
std::string installPath = _launcherInstallDir.absolutePath().replace("/", "\\").toStdString();
|
||||||
|
success = insertRegistryKey(REGISTRY_PATH, "InstallLocation", installPath);
|
||||||
|
std::string applicationExe = installPath + "\\HQ Launcher.exe";
|
||||||
|
std::string uninstallPath = applicationExe + " --uninstall";
|
||||||
|
qDebug() << QString::fromStdString(applicationExe);
|
||||||
|
qDebug() << QString::fromStdString(uninstallPath);
|
||||||
|
success = insertRegistryKey(REGISTRY_PATH, "UninstallString", uninstallPath);
|
||||||
|
success = insertRegistryKey(REGISTRY_PATH, "DisplayVersion", std::string(LAUNCHER_BUILD_VERSION));
|
||||||
|
success = insertRegistryKey(REGISTRY_PATH, "DisplayIcon", applicationExe);
|
||||||
|
success = insertRegistryKey(REGISTRY_PATH, "Publisher", "High Fidelity");
|
||||||
|
|
||||||
|
auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||||
|
|
||||||
|
std::stringstream date;
|
||||||
|
date << std::put_time(std::localtime(&now), "%Y-%m-%d") ;
|
||||||
|
success = insertRegistryKey(REGISTRY_PATH, "InstallDate", date.str());
|
||||||
|
success = insertRegistryKey(REGISTRY_PATH, "EstimatedSize", (DWORD)14181);
|
||||||
|
success = insertRegistryKey(REGISTRY_PATH, "NoModify", (DWORD)1);
|
||||||
|
success = insertRegistryKey(REGISTRY_PATH, "NoRepair", (DWORD)1);
|
||||||
|
|
||||||
|
qDebug() << "Did succcessfully insertRegistyKeys: " << success;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherInstaller::deleteApplicationRegistryKeys() {
|
||||||
|
const std::string regPath= "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ";
|
||||||
|
bool success = deleteRegistryKey(regPath.c_str());
|
||||||
|
qDebug() << "Did delete Application Registry Keys: " << success;
|
||||||
|
}
|
23
launchers/qt/src/LauncherInstaller_windows.h
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
class LauncherInstaller {
|
||||||
|
public:
|
||||||
|
LauncherInstaller();
|
||||||
|
~LauncherInstaller() = default;
|
||||||
|
|
||||||
|
void install();
|
||||||
|
void uninstall();
|
||||||
|
bool runningOutsideOfInstallDir();
|
||||||
|
private:
|
||||||
|
void createShortcuts();
|
||||||
|
void uninstallOldLauncher();
|
||||||
|
void createApplicationRegistryKeys();
|
||||||
|
void deleteShortcuts();
|
||||||
|
void deleteApplicationRegistryKeys();
|
||||||
|
|
||||||
|
QDir _launcherInstallDir;
|
||||||
|
QDir _launcherApplicationsDir;
|
||||||
|
QString _launcherRunningFilePath;
|
||||||
|
QString _launcherRunningDirPath;
|
||||||
|
};
|
797
launchers/qt/src/LauncherState.cpp
Normal file
|
@ -0,0 +1,797 @@
|
||||||
|
#include "LauncherState.h"
|
||||||
|
|
||||||
|
#include "CommandlineOptions.h"
|
||||||
|
#include "PathUtils.h"
|
||||||
|
#include "Unzipper.h"
|
||||||
|
#include "Helper.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstdlib>
|
||||||
|
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
|
#include <QNetworkRequest>
|
||||||
|
#include <QNetworkReply>
|
||||||
|
|
||||||
|
#include <QJsonDocument>
|
||||||
|
#include <QJsonObject>
|
||||||
|
#include <QJsonArray>
|
||||||
|
#include <QUrlQuery>
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
#include <QThreadPool>
|
||||||
|
|
||||||
|
#include <QEventLoop>
|
||||||
|
|
||||||
|
#include <qregularexpression.h>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <shellapi.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
//#define BREAK_ON_ERROR
|
||||||
|
//#define DEBUG_UI
|
||||||
|
|
||||||
|
const QString configHomeLocationKey { "homeLocation" };
|
||||||
|
const QString configLastLoginKey { "lastLogin" };
|
||||||
|
const QString configLoggedInKey{ "loggedIn" };
|
||||||
|
const QString configLauncherPathKey{ "launcherPath" };
|
||||||
|
|
||||||
|
Q_INVOKABLE void LauncherState::openURLInBrowser(QString url) {
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
ShellExecute(0, 0, url.toLatin1(), 0, 0 , SW_SHOW);
|
||||||
|
#elif defined(Q_OS_DARWIN)
|
||||||
|
system("open \"" + url.toLatin1() + "\"");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::toggleDebugState() {
|
||||||
|
#ifdef DEBUG_UI
|
||||||
|
_isDebuggingScreens = !_isDebuggingScreens;
|
||||||
|
|
||||||
|
UIState updatedUIState = getUIState();
|
||||||
|
if (_uiState != updatedUIState) {
|
||||||
|
emit uiStateChanged();
|
||||||
|
emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource()));
|
||||||
|
_uiState = getUIState();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
void LauncherState::gotoNextDebugScreen() {
|
||||||
|
#ifdef DEBUG_UI
|
||||||
|
if (_currentDebugScreen < (UIState::UI_STATE_NUM - 1)) {
|
||||||
|
_currentDebugScreen = (UIState)(_currentDebugScreen + 1);
|
||||||
|
emit uiStateChanged();
|
||||||
|
emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource()));
|
||||||
|
_uiState = getUIState();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
void LauncherState::gotoPreviousDebugScreen() {
|
||||||
|
#ifdef DEBUG_UI
|
||||||
|
if (_currentDebugScreen > 0) {
|
||||||
|
_currentDebugScreen = (UIState)(_currentDebugScreen - 1);
|
||||||
|
emit uiStateChanged();
|
||||||
|
emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource()));
|
||||||
|
_uiState = getUIState();
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LauncherState::shouldDownloadContentCache() const {
|
||||||
|
return !_contentCacheURL.isEmpty() && !QFile::exists(PathUtils::getContentCachePath());
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::setLastSignupErrorMessage(const QString& msg) {
|
||||||
|
_lastSignupErrorMessage = msg;
|
||||||
|
emit lastSignupErrorMessageChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::setLastLoginErrorMessage(const QString& msg) {
|
||||||
|
_lastLoginErrorMessage = msg;
|
||||||
|
emit lastLoginErrorMessageChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
static const std::array<QString, LauncherState::UIState::UI_STATE_NUM> QML_FILE_FOR_UI_STATE =
|
||||||
|
{ { "qml/SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "qml/HFBase/LoginBase.qml",
|
||||||
|
"qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } };
|
||||||
|
|
||||||
|
void LauncherState::ASSERT_STATE(ApplicationState state) {
|
||||||
|
if (_applicationState != state) {
|
||||||
|
qDebug() << "Unexpected state, current: " << _applicationState << ", expected: " << state;
|
||||||
|
#ifdef BREAK_ON_ERROR
|
||||||
|
__debugbreak();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::ASSERT_STATE(const std::vector<ApplicationState>& states) {
|
||||||
|
for (auto state : states) {
|
||||||
|
if (_applicationState == state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Unexpected state, current: " << _applicationState << ", expected: " << states;
|
||||||
|
#ifdef BREAK_ON_ERROR
|
||||||
|
__debugbreak();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
LauncherState::LauncherState() {
|
||||||
|
_launcherDirectory = PathUtils::getLauncherDirectory();
|
||||||
|
qDebug() << "Launcher directory: " << _launcherDirectory.absolutePath();
|
||||||
|
_launcherDirectory.mkpath(_launcherDirectory.absolutePath());
|
||||||
|
_launcherDirectory.mkpath(PathUtils::getDownloadDirectory().absolutePath());
|
||||||
|
requestBuilds();
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LauncherState::getCurrentUISource() const {
|
||||||
|
return QML_FILE_FOR_UI_STATE[getUIState()];
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::declareQML() {
|
||||||
|
qmlRegisterType<LauncherState>("HQLauncher", 1, 0, "ApplicationState");
|
||||||
|
}
|
||||||
|
|
||||||
|
LauncherState::UIState LauncherState::getUIState() const {
|
||||||
|
if (_isDebuggingScreens) {
|
||||||
|
return _currentDebugScreen;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (_applicationState) {
|
||||||
|
case ApplicationState::Init:
|
||||||
|
case ApplicationState::RequestingBuilds:
|
||||||
|
case ApplicationState::GettingCurrentClientVersion:
|
||||||
|
return UIState::SplashScreen;
|
||||||
|
case ApplicationState::WaitingForLogin:
|
||||||
|
case ApplicationState::RequestingLogin:
|
||||||
|
return UIState::LoginScreen;
|
||||||
|
case ApplicationState::WaitingForSignup:
|
||||||
|
case ApplicationState::RequestingSignup:
|
||||||
|
case ApplicationState::RequestingLoginAfterSignup:
|
||||||
|
return UIState::SignupScreen;
|
||||||
|
case ApplicationState::DownloadingClient:
|
||||||
|
case ApplicationState::InstallingClient:
|
||||||
|
case ApplicationState::DownloadingContentCache:
|
||||||
|
case ApplicationState::InstallingContentCache:
|
||||||
|
return UIState::DownloadScreen;
|
||||||
|
case ApplicationState::LaunchingHighFidelity:
|
||||||
|
return UIState::DownloadFinishedScreen;
|
||||||
|
case ApplicationState::UnexpectedError:
|
||||||
|
#ifdef BREAK_ON_ERROR
|
||||||
|
__debugbreak();
|
||||||
|
#endif
|
||||||
|
return UIState::ErrorScreen;
|
||||||
|
default:
|
||||||
|
qDebug() << "FATAL: No UI for" << _applicationState;
|
||||||
|
#ifdef BREAK_ON_ERROR
|
||||||
|
__debugbreak();
|
||||||
|
#endif
|
||||||
|
return UIState::ErrorScreen;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::restart() {
|
||||||
|
setApplicationState(ApplicationState::Init);
|
||||||
|
requestBuilds();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::requestBuilds() {
|
||||||
|
ASSERT_STATE(ApplicationState::Init);
|
||||||
|
setApplicationState(ApplicationState::RequestingBuilds);
|
||||||
|
|
||||||
|
auto request = new BuildsRequest();
|
||||||
|
|
||||||
|
QObject::connect(request, &BuildsRequest::finished, this, [=] {
|
||||||
|
ASSERT_STATE(ApplicationState::RequestingBuilds);
|
||||||
|
if (request->getError() != BuildsRequest::Error::None) {
|
||||||
|
setApplicationStateError("Could not retrieve latest builds");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_latestBuilds = request->getLatestBuilds();
|
||||||
|
|
||||||
|
CommandlineOptions* options = CommandlineOptions::getInstance();
|
||||||
|
qDebug() << "Latest version: " << _latestBuilds.launcherBuild.latestVersion
|
||||||
|
<< "Curretn version: " << getBuildVersion().toInt();
|
||||||
|
if (shouldDownloadLauncher() && !options->contains("--noUpdate")) {
|
||||||
|
downloadLauncher();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
getCurrentClientVersion();
|
||||||
|
});
|
||||||
|
|
||||||
|
request->send(_networkAccessManager);
|
||||||
|
}
|
||||||
|
|
||||||
|
QString LauncherState::getBuildVersion() {
|
||||||
|
QString buildVersion { LAUNCHER_BUILD_VERSION };
|
||||||
|
QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment();
|
||||||
|
if (processEnvironment.contains("HQ_LAUNCHER_BUILD_VERSION")) {
|
||||||
|
buildVersion = processEnvironment.value("HQ_LAUNCHER_BUILD_VERSION");
|
||||||
|
}
|
||||||
|
return buildVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LauncherState::shouldDownloadLauncher() {
|
||||||
|
return _latestBuilds.launcherBuild.latestVersion != getBuildVersion().toInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::getCurrentClientVersion() {
|
||||||
|
ASSERT_STATE(ApplicationState::RequestingBuilds);
|
||||||
|
|
||||||
|
setApplicationState(ApplicationState::GettingCurrentClientVersion);
|
||||||
|
|
||||||
|
QProcess client;
|
||||||
|
QEventLoop loop;
|
||||||
|
|
||||||
|
connect(&client, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), &loop, &QEventLoop::exit, Qt::QueuedConnection);
|
||||||
|
connect(&client, &QProcess::errorOccurred, &loop, &QEventLoop::exit, Qt::QueuedConnection);
|
||||||
|
|
||||||
|
client.start(PathUtils::getClientExecutablePath(), { "--version" });
|
||||||
|
loop.exec();
|
||||||
|
|
||||||
|
// TODO Handle errors
|
||||||
|
auto output = client.readAllStandardOutput();
|
||||||
|
|
||||||
|
QRegularExpression regex { "Interface (?<version>\\d+)(-.*)?" };
|
||||||
|
|
||||||
|
auto match = regex.match(output);
|
||||||
|
|
||||||
|
if (match.hasMatch()) {
|
||||||
|
_currentClientVersion = match.captured("version");
|
||||||
|
} else {
|
||||||
|
_currentClientVersion = QString::null;
|
||||||
|
}
|
||||||
|
qDebug() << "Current client version is: " << _currentClientVersion;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto path = PathUtils::getConfigFilePath();
|
||||||
|
QFile configFile{ path };
|
||||||
|
|
||||||
|
if (configFile.open(QIODevice::ReadOnly)) {
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll());
|
||||||
|
auto root = doc.object();
|
||||||
|
|
||||||
|
_config.launcherPath = PathUtils::getLauncherFilePath();
|
||||||
|
_config.loggedIn = false;
|
||||||
|
if (root.contains(configLoggedInKey)) {
|
||||||
|
_config.loggedIn = root[configLoggedInKey].toBool();
|
||||||
|
}
|
||||||
|
if (root.contains(configLastLoginKey)) {
|
||||||
|
_config.lastLogin = root[configLastLoginKey].toString();
|
||||||
|
}
|
||||||
|
if (root.contains(configHomeLocationKey)) {
|
||||||
|
_config.homeLocation = root[configHomeLocationKey].toString();
|
||||||
|
}
|
||||||
|
if (root.contains(configLauncherPathKey)) {
|
||||||
|
_config.launcherPath = root[configLauncherPathKey].toString();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
qDebug() << "Failed to open config.json";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Is logged-in: " << _config.loggedIn;
|
||||||
|
if (_config.loggedIn) {
|
||||||
|
downloadClient();
|
||||||
|
} else {
|
||||||
|
if (_config.lastLogin.isEmpty()) {
|
||||||
|
setApplicationState(ApplicationState::WaitingForSignup);
|
||||||
|
} else {
|
||||||
|
_lastUsedUsername = _config.lastLogin;
|
||||||
|
setApplicationState(ApplicationState::WaitingForLogin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LauncherState::gotoSignup() {
|
||||||
|
if (_applicationState == ApplicationState::WaitingForLogin) {
|
||||||
|
setLastSignupErrorMessage("");
|
||||||
|
_lastLoginErrorMessage = "";
|
||||||
|
setApplicationState(ApplicationState::WaitingForSignup);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Error, can't switch to signup page, current state is: " << _applicationState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::gotoLogin() {
|
||||||
|
if (_applicationState == ApplicationState::WaitingForSignup) {
|
||||||
|
setLastLoginErrorMessage("");
|
||||||
|
setApplicationState(ApplicationState::WaitingForLogin);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Error, can't switch to signup page, current state is: " << _applicationState;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::signup(QString email, QString username, QString password, QString displayName) {
|
||||||
|
ASSERT_STATE(ApplicationState::WaitingForSignup);
|
||||||
|
|
||||||
|
_username = username;
|
||||||
|
_password = password;
|
||||||
|
|
||||||
|
setApplicationState(ApplicationState::RequestingSignup);
|
||||||
|
|
||||||
|
auto signupRequest = new SignupRequest();
|
||||||
|
|
||||||
|
_displayName = displayName;
|
||||||
|
|
||||||
|
{
|
||||||
|
_lastSignupError = SignupRequest::Error::None;
|
||||||
|
emit lastSignupErrorChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
QObject::connect(signupRequest, &SignupRequest::finished, this, [this, signupRequest, username] {
|
||||||
|
signupRequest->deleteLater();
|
||||||
|
|
||||||
|
|
||||||
|
_lastSignupError = signupRequest->getError();
|
||||||
|
emit lastSignupErrorChanged();
|
||||||
|
|
||||||
|
auto err = signupRequest->getError();
|
||||||
|
if (err == SignupRequest::Error::ExistingUsername) {
|
||||||
|
setLastSignupErrorMessage(_username + " is already taken. Please try a different username.");
|
||||||
|
setApplicationState(ApplicationState::WaitingForSignup);
|
||||||
|
return;
|
||||||
|
} else if (err == SignupRequest::Error::BadPassword) {
|
||||||
|
setLastSignupErrorMessage("That's an invalid password. Must be at least 6 characters.");
|
||||||
|
setApplicationState(ApplicationState::WaitingForSignup);
|
||||||
|
return;
|
||||||
|
} else if (err == SignupRequest::Error::BadUsername) {
|
||||||
|
setLastSignupErrorMessage("That's an invalid username. Please try another username.");
|
||||||
|
setApplicationState(ApplicationState::WaitingForSignup);
|
||||||
|
return;
|
||||||
|
} else if (err == SignupRequest::Error::UserProfileAlreadyCompleted) {
|
||||||
|
setLastSignupErrorMessage("An account with this email already exists. Please <b><a href='login'>log in</a></b>.");
|
||||||
|
setApplicationState(ApplicationState::WaitingForSignup);
|
||||||
|
return;
|
||||||
|
} else if (err == SignupRequest::Error::NoSuchEmail) {
|
||||||
|
setLastSignupErrorMessage("That email isn't setup yet. <a href='https://www.highfidelity.com/hq-support'>Request access</a>.");
|
||||||
|
setApplicationState(ApplicationState::WaitingForSignup);
|
||||||
|
return;
|
||||||
|
} else if (err != SignupRequest::Error::None) {
|
||||||
|
setApplicationStateError("Failed to sign up. Please try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setApplicationState(ApplicationState::RequestingLoginAfterSignup);
|
||||||
|
|
||||||
|
// After successfully signing up, attempt to login
|
||||||
|
auto loginRequest = new LoginRequest();
|
||||||
|
|
||||||
|
_lastUsedUsername = username;
|
||||||
|
_config.lastLogin = username;
|
||||||
|
|
||||||
|
connect(loginRequest, &LoginRequest::finished, this, [this, loginRequest]() {
|
||||||
|
ASSERT_STATE(ApplicationState::RequestingLoginAfterSignup);
|
||||||
|
|
||||||
|
loginRequest->deleteLater();
|
||||||
|
|
||||||
|
auto err = loginRequest->getError();
|
||||||
|
if (err == LoginRequest::Error::BadUsernameOrPassword) {
|
||||||
|
setLastLoginErrorMessage("Invalid username or password.");
|
||||||
|
setApplicationState(ApplicationState::WaitingForLogin);
|
||||||
|
return;
|
||||||
|
} else if (err != LoginRequest::Error::None) {
|
||||||
|
setApplicationStateError("Failed to login. Please try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
_config.loggedIn = true;
|
||||||
|
_loginResponse = loginRequest->getToken();
|
||||||
|
_loginTokenResponse = loginRequest->getRawToken();
|
||||||
|
|
||||||
|
requestSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
setApplicationState(ApplicationState::RequestingLoginAfterSignup);
|
||||||
|
loginRequest->send(_networkAccessManager, _username, _password);
|
||||||
|
});
|
||||||
|
signupRequest->send(_networkAccessManager, email, username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LauncherState::login(QString username, QString password, QString displayName) {
|
||||||
|
ASSERT_STATE(ApplicationState::WaitingForLogin);
|
||||||
|
|
||||||
|
setApplicationState(ApplicationState::RequestingLogin);
|
||||||
|
|
||||||
|
_displayName = displayName;
|
||||||
|
|
||||||
|
auto request = new LoginRequest();
|
||||||
|
|
||||||
|
connect(request, &LoginRequest::finished, this, [this, request, username]() {
|
||||||
|
ASSERT_STATE(ApplicationState::RequestingLogin);
|
||||||
|
|
||||||
|
request->deleteLater();
|
||||||
|
|
||||||
|
auto err = request->getError();
|
||||||
|
if (err == LoginRequest::Error::BadUsernameOrPassword) {
|
||||||
|
setLastLoginErrorMessage("Invalid username or password");
|
||||||
|
setApplicationState(ApplicationState::WaitingForLogin);
|
||||||
|
return;
|
||||||
|
} else if (err != LoginRequest::Error::None) {
|
||||||
|
setApplicationStateError("Failed to login. Please try again.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_lastUsedUsername = username;
|
||||||
|
_config.lastLogin = username;
|
||||||
|
_config.loggedIn = true;
|
||||||
|
_loginResponse = request->getToken();
|
||||||
|
_loginTokenResponse = request->getRawToken();
|
||||||
|
|
||||||
|
requestSettings();
|
||||||
|
});
|
||||||
|
|
||||||
|
request->send(_networkAccessManager, username, password);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::requestSettings() {
|
||||||
|
// TODO Request settings if already logged in
|
||||||
|
qDebug() << "Requesting settings";
|
||||||
|
|
||||||
|
auto request = new UserSettingsRequest();
|
||||||
|
|
||||||
|
connect(request, &UserSettingsRequest::finished, this, [this, request]() {
|
||||||
|
auto userSettings = request->getUserSettings();
|
||||||
|
if (userSettings.homeLocation.isEmpty()) {
|
||||||
|
_config.homeLocation = "file:///~/serverless/tutorial.json";
|
||||||
|
_contentCacheURL = "";
|
||||||
|
} else {
|
||||||
|
_config.homeLocation = userSettings.homeLocation;
|
||||||
|
auto host = QUrl(_config.homeLocation).host();
|
||||||
|
_contentCacheURL = "http://orgs.highfidelity.com/host-content-cache/" + host + ".zip";
|
||||||
|
|
||||||
|
qDebug() << "Content cache url: " << _contentCacheURL;
|
||||||
|
}
|
||||||
|
|
||||||
|
qDebug() << "Home location is: " << _config.homeLocation;
|
||||||
|
qDebug() << "Content cache url is: " << _contentCacheURL;
|
||||||
|
|
||||||
|
downloadClient();
|
||||||
|
});
|
||||||
|
|
||||||
|
request->send(_networkAccessManager, _loginResponse);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::downloadClient() {
|
||||||
|
ASSERT_STATE({ ApplicationState::RequestingLogin, ApplicationState::RequestingLoginAfterSignup });
|
||||||
|
|
||||||
|
Build build;
|
||||||
|
if (!_latestBuilds.getBuild(_buildTag, &build)) {
|
||||||
|
qDebug() << "Cannot determine latest build";
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (QString::number(build.latestVersion) == _currentClientVersion) {
|
||||||
|
qDebug() << "Existing client install is up-to-date.";
|
||||||
|
downloadContentCache();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_interfaceDownloadProgress = 0;
|
||||||
|
setApplicationState(ApplicationState::DownloadingClient);
|
||||||
|
|
||||||
|
// Start client download
|
||||||
|
{
|
||||||
|
qDebug() << "Latest build: " << build.tag << build.buildNumber << build.latestVersion << build.installerZipURL;
|
||||||
|
auto request = new QNetworkRequest(QUrl(build.installerZipURL));
|
||||||
|
auto reply = _networkAccessManager.get(*request);
|
||||||
|
|
||||||
|
QDir downloadDir{ PathUtils::getDownloadDirectory() };
|
||||||
|
_clientZipFile.setFileName(downloadDir.absoluteFilePath("client.zip"));
|
||||||
|
|
||||||
|
qDebug() << "Opening " << _clientZipFile.fileName();
|
||||||
|
if (!_clientZipFile.open(QIODevice::WriteOnly)) {
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, &LauncherState::clientDownloadComplete);
|
||||||
|
connect(reply, &QNetworkReply::readyRead, this, [this, reply]() {
|
||||||
|
char buf[4096];
|
||||||
|
while (reply->bytesAvailable() > 0) {
|
||||||
|
qint64 size;
|
||||||
|
size = reply->read(buf, (qint64)sizeof(buf));
|
||||||
|
if (size == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_clientZipFile.write(buf, size);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) {
|
||||||
|
_interfaceDownloadProgress = (float)received / (float)total;
|
||||||
|
emit downloadProgressChanged();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::launcherDownloadComplete() {
|
||||||
|
_launcherZipFile.close();
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
installLauncher();
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
launchAutoUpdater(_launcherZipFile.fileName());
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::clientDownloadComplete() {
|
||||||
|
ASSERT_STATE(ApplicationState::DownloadingClient);
|
||||||
|
_clientZipFile.close();
|
||||||
|
installClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
float LauncherState::calculateDownloadProgress() const{
|
||||||
|
if (shouldDownloadContentCache()) {
|
||||||
|
return (_interfaceDownloadProgress * 0.40f) + (_interfaceInstallProgress * 0.10f) +
|
||||||
|
(_contentInstallProgress * 0.40f) + (_contentDownloadProgress * 0.10f);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (_interfaceDownloadProgress * 0.80f) + (_interfaceInstallProgress * 0.20f);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::installClient() {
|
||||||
|
ASSERT_STATE(ApplicationState::DownloadingClient);
|
||||||
|
setApplicationState(ApplicationState::InstallingClient);
|
||||||
|
|
||||||
|
|
||||||
|
auto clientDir = PathUtils::getClientDirectory();
|
||||||
|
|
||||||
|
auto clientPath = clientDir.absolutePath();
|
||||||
|
_launcherDirectory.rmpath(clientPath);
|
||||||
|
_launcherDirectory.mkpath(clientPath);
|
||||||
|
|
||||||
|
_interfaceInstallProgress = 0;
|
||||||
|
|
||||||
|
qDebug() << "Unzipping " << _clientZipFile.fileName() << " to " << clientDir.absolutePath();
|
||||||
|
|
||||||
|
auto unzipper = new Unzipper(_clientZipFile.fileName(), clientDir);
|
||||||
|
unzipper->setAutoDelete(true);
|
||||||
|
connect(unzipper, &Unzipper::progress, this, [this](float progress) {
|
||||||
|
_interfaceInstallProgress = progress;
|
||||||
|
emit downloadProgressChanged();
|
||||||
|
});
|
||||||
|
connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) {
|
||||||
|
if (error) {
|
||||||
|
qDebug() << "Unzipper finished with error: " << errorMessage;
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Unzipper finished without error";
|
||||||
|
downloadContentCache();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
QThreadPool::globalInstance()->start(unzipper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::downloadLauncher() {
|
||||||
|
auto request = new QNetworkRequest(QUrl(_latestBuilds.launcherBuild.installerZipURL));
|
||||||
|
auto reply = _networkAccessManager.get(*request);
|
||||||
|
|
||||||
|
#ifdef Q_OS_MAC
|
||||||
|
_launcherZipFile.setFileName(_launcherDirectory.absoluteFilePath("launcher.zip"));
|
||||||
|
#elif defined(Q_OS_WIN)
|
||||||
|
_launcherZipFile.setFileName(_launcherDirectory.absoluteFilePath("launcher.exe"));
|
||||||
|
#endif
|
||||||
|
|
||||||
|
qDebug() << "opening " << _launcherZipFile.fileName();
|
||||||
|
|
||||||
|
if (!_launcherZipFile.open(QIODevice::WriteOnly)) {
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, &LauncherState::launcherDownloadComplete);
|
||||||
|
connect(reply, &QNetworkReply::readyRead, this, [this, reply]() {
|
||||||
|
char buf[4096];
|
||||||
|
while (reply->bytesAvailable() > 0) {
|
||||||
|
qint64 size;
|
||||||
|
size = reply->read(buf, (qint64)sizeof(buf));
|
||||||
|
if (size == 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
_launcherZipFile.write(buf, size);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::installLauncher() {
|
||||||
|
_launcherDirectory.rmpath("launcher_install");
|
||||||
|
_launcherDirectory.mkpath("launcher_install");
|
||||||
|
auto installDir = _launcherDirectory.absoluteFilePath("launcher_install");
|
||||||
|
|
||||||
|
qDebug() << "Uzipping " << _launcherZipFile.fileName() << " to " << installDir;
|
||||||
|
|
||||||
|
auto unzipper = new Unzipper(_launcherZipFile.fileName(), QDir(installDir));
|
||||||
|
unzipper->setAutoDelete(true);
|
||||||
|
connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) {
|
||||||
|
if (error) {
|
||||||
|
qDebug() << "Unzipper finished with error: " << errorMessage;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Unzipper finished without error";
|
||||||
|
|
||||||
|
QDir installDirectory = _launcherDirectory.filePath("launcher_install");
|
||||||
|
QString launcherPath;
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
launcherPath = installDirectory.absoluteFilePath("HQ Launcher.exe");
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
launcherPath = installDirectory.absoluteFilePath("HQ Launcher.app");
|
||||||
|
#endif
|
||||||
|
::launchAutoUpdater(launcherPath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
QThreadPool::globalInstance()->start(unzipper);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::downloadContentCache() {
|
||||||
|
ASSERT_STATE({ ApplicationState::RequestingLogin, ApplicationState::InstallingClient });
|
||||||
|
|
||||||
|
// Start content set cache download
|
||||||
|
if (shouldDownloadContentCache()) {
|
||||||
|
setApplicationState(ApplicationState::DownloadingContentCache);
|
||||||
|
|
||||||
|
_contentDownloadProgress = 0;
|
||||||
|
|
||||||
|
qDebug() << "Downloading content cache from: " << _contentCacheURL;
|
||||||
|
QNetworkRequest request{ QUrl(_contentCacheURL) };
|
||||||
|
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||||
|
auto reply = _networkAccessManager.get(request);
|
||||||
|
|
||||||
|
QDir downloadDir{ PathUtils::getDownloadDirectory() };
|
||||||
|
_contentZipFile.setFileName(downloadDir.absoluteFilePath("content_cache.zip"));
|
||||||
|
|
||||||
|
qDebug() << "Opening " << _contentZipFile.fileName();
|
||||||
|
if (!_contentZipFile.open(QIODevice::WriteOnly)) {
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
connect(reply, &QNetworkReply::finished, this, &LauncherState::contentCacheDownloadComplete);
|
||||||
|
connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) {
|
||||||
|
_contentDownloadProgress = (float)received / (float)total;
|
||||||
|
emit downloadProgressChanged();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
launchClient();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::contentCacheDownloadComplete() {
|
||||||
|
ASSERT_STATE(ApplicationState::DownloadingContentCache);
|
||||||
|
|
||||||
|
auto reply = static_cast<QNetworkReply*>(sender());
|
||||||
|
|
||||||
|
if (reply->error()) {
|
||||||
|
qDebug() << "Error downloading content cache: " << reply->error() << reply->readAll();
|
||||||
|
qDebug() << "Continuing to launch client";
|
||||||
|
_contentDownloadProgress = 100.0f;
|
||||||
|
_contentInstallProgress = 100.0f;
|
||||||
|
launchClient();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
char buf[4096];
|
||||||
|
while (reply->bytesAvailable() > 0) {
|
||||||
|
qint64 size;
|
||||||
|
size = reply->read(buf, (qint64)sizeof(buf));
|
||||||
|
_contentZipFile.write(buf, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
_contentZipFile.close();
|
||||||
|
|
||||||
|
installContentCache();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void LauncherState::installContentCache() {
|
||||||
|
ASSERT_STATE(ApplicationState::DownloadingContentCache);
|
||||||
|
setApplicationState(ApplicationState::InstallingContentCache);
|
||||||
|
|
||||||
|
auto installDir = PathUtils::getContentCachePath();
|
||||||
|
|
||||||
|
qDebug() << "Unzipping " << _contentZipFile.fileName() << " to " << installDir;
|
||||||
|
|
||||||
|
_contentInstallProgress = 0;
|
||||||
|
|
||||||
|
auto unzipper = new Unzipper(_contentZipFile.fileName(), QDir(installDir));
|
||||||
|
unzipper->setAutoDelete(true);
|
||||||
|
connect(unzipper, &Unzipper::progress, this, [this](float progress) {
|
||||||
|
qDebug() << "Unzipper progress (content cache): " << progress;
|
||||||
|
_contentInstallProgress = progress;
|
||||||
|
emit downloadProgressChanged();
|
||||||
|
});
|
||||||
|
connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) {
|
||||||
|
if (error) {
|
||||||
|
qDebug() << "Unzipper finished with error: " << errorMessage;
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
} else {
|
||||||
|
qDebug() << "Unzipper finished without error";
|
||||||
|
launchClient();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
QThreadPool::globalInstance()->start(unzipper);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#include <QTimer>
|
||||||
|
#include <QCoreApplication>
|
||||||
|
void LauncherState::launchClient() {
|
||||||
|
ASSERT_STATE({
|
||||||
|
ApplicationState::RequestingLogin,
|
||||||
|
ApplicationState::InstallingClient,
|
||||||
|
ApplicationState::InstallingContentCache
|
||||||
|
});
|
||||||
|
|
||||||
|
setApplicationState(ApplicationState::LaunchingHighFidelity);
|
||||||
|
|
||||||
|
QDir installDirectory = PathUtils::getClientDirectory();
|
||||||
|
QString clientPath = PathUtils::getClientExecutablePath();
|
||||||
|
|
||||||
|
auto path = PathUtils::getConfigFilePath();
|
||||||
|
QFile configFile{ path };
|
||||||
|
if (configFile.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
|
||||||
|
QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll());
|
||||||
|
doc.setObject({
|
||||||
|
{ configHomeLocationKey, _config.homeLocation },
|
||||||
|
{ configLastLoginKey, _config.lastLogin },
|
||||||
|
{ configLoggedInKey, _config.loggedIn },
|
||||||
|
{ configLauncherPathKey, PathUtils::getLauncherFilePath() },
|
||||||
|
});
|
||||||
|
qint64 result = configFile.write(doc.toJson());
|
||||||
|
configFile.close();
|
||||||
|
qDebug() << "Wrote data to config data: " << result;
|
||||||
|
} else {
|
||||||
|
qDebug() << "Failed to open config file";
|
||||||
|
}
|
||||||
|
|
||||||
|
QString defaultScriptsPath;
|
||||||
|
#if defined(Q_OS_WIN)
|
||||||
|
defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper.js");
|
||||||
|
#elif defined(Q_OS_MACOS)
|
||||||
|
defaultScriptsPath = installDirectory.filePath("interface.app/Contents/Resources/scripts/simplifiedUIBootstrapper.js");
|
||||||
|
#endif
|
||||||
|
|
||||||
|
QString contentCachePath = _launcherDirectory.filePath("cache");
|
||||||
|
|
||||||
|
::launchClient(clientPath, _config.homeLocation, defaultScriptsPath, _displayName, contentCachePath, _loginTokenResponse);
|
||||||
|
QTimer::singleShot(3000, QCoreApplication::instance(), &QCoreApplication::quit);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::setApplicationStateError(QString errorMessage) {
|
||||||
|
_applicationErrorMessage = errorMessage;
|
||||||
|
setApplicationState(ApplicationState::UnexpectedError);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherState::setApplicationState(ApplicationState state) {
|
||||||
|
qDebug() << "Changing application state: " << _applicationState << " -> " << state;
|
||||||
|
|
||||||
|
if (state == ApplicationState::UnexpectedError) {
|
||||||
|
#ifdef BREAK_ON_ERROR
|
||||||
|
__debugbreak();
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
_applicationState = state;
|
||||||
|
UIState updatedUIState = getUIState();
|
||||||
|
if (_uiState != updatedUIState) {
|
||||||
|
emit uiStateChanged();
|
||||||
|
emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource()));
|
||||||
|
_uiState = getUIState();
|
||||||
|
}
|
||||||
|
|
||||||
|
emit applicationStateChanged();
|
||||||
|
}
|
||||||
|
|
||||||
|
LauncherState::ApplicationState LauncherState::getApplicationState() const {
|
||||||
|
return _applicationState;
|
||||||
|
}
|
||||||
|
|
194
launchers/qt/src/LauncherState.h
Normal file
|
@ -0,0 +1,194 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QDir>
|
||||||
|
#include <QObject>
|
||||||
|
#include <QString>
|
||||||
|
#include <QUrl>
|
||||||
|
#include <QNetworkAccessManager>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
#include "LoginRequest.h"
|
||||||
|
#include "SignupRequest.h"
|
||||||
|
#include "UserSettingsRequest.h"
|
||||||
|
#include "BuildsRequest.h"
|
||||||
|
|
||||||
|
struct LauncherConfig {
|
||||||
|
QString lastLogin{ "" };
|
||||||
|
QString launcherPath{ "" };
|
||||||
|
bool loggedIn{ false };
|
||||||
|
QString homeLocation{ "" };
|
||||||
|
};
|
||||||
|
|
||||||
|
class LauncherState : public QObject {
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_PROPERTY(UIState uiState READ getUIState NOTIFY uiStateChanged)
|
||||||
|
Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged)
|
||||||
|
Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged)
|
||||||
|
Q_PROPERTY(QString lastLoginErrorMessage READ getLastLoginErrorMessage NOTIFY lastLoginErrorMessageChanged)
|
||||||
|
Q_PROPERTY(QString lastSignupErrorMessage READ getLastSignupErrorMessage NOTIFY lastSignupErrorMessageChanged)
|
||||||
|
Q_PROPERTY(QString buildVersion READ getBuildVersion)
|
||||||
|
Q_PROPERTY(QString lastUsedUsername READ getLastUsedUsername)
|
||||||
|
|
||||||
|
public:
|
||||||
|
LauncherState();
|
||||||
|
~LauncherState() = default;
|
||||||
|
|
||||||
|
enum UIState {
|
||||||
|
SplashScreen = 0,
|
||||||
|
SignupScreen,
|
||||||
|
LoginScreen,
|
||||||
|
DownloadScreen,
|
||||||
|
DownloadFinishedScreen,
|
||||||
|
ErrorScreen,
|
||||||
|
|
||||||
|
UI_STATE_NUM
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class ApplicationState {
|
||||||
|
Init,
|
||||||
|
|
||||||
|
UnexpectedError,
|
||||||
|
|
||||||
|
RequestingBuilds,
|
||||||
|
GettingCurrentClientVersion,
|
||||||
|
|
||||||
|
WaitingForLogin,
|
||||||
|
RequestingLogin,
|
||||||
|
|
||||||
|
WaitingForSignup,
|
||||||
|
RequestingSignup,
|
||||||
|
RequestingLoginAfterSignup,
|
||||||
|
|
||||||
|
DownloadingClient,
|
||||||
|
DownloadingLauncher,
|
||||||
|
DownloadingContentCache,
|
||||||
|
|
||||||
|
InstallingClient,
|
||||||
|
InstallingLauncher,
|
||||||
|
InstallingContentCache,
|
||||||
|
|
||||||
|
LaunchingHighFidelity,
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_ENUM(ApplicationState)
|
||||||
|
|
||||||
|
bool _isDebuggingScreens{ false };
|
||||||
|
UIState _currentDebugScreen{ UIState::SplashScreen };
|
||||||
|
void toggleDebugState();
|
||||||
|
void gotoNextDebugScreen();
|
||||||
|
void gotoPreviousDebugScreen();
|
||||||
|
|
||||||
|
Q_INVOKABLE QString getCurrentUISource() const;
|
||||||
|
|
||||||
|
void ASSERT_STATE(ApplicationState state);
|
||||||
|
void ASSERT_STATE(const std::vector<ApplicationState>& states);
|
||||||
|
|
||||||
|
static void declareQML();
|
||||||
|
|
||||||
|
UIState getUIState() const;
|
||||||
|
|
||||||
|
void setLastLoginErrorMessage(const QString& msg);
|
||||||
|
QString getLastLoginErrorMessage() const { return _lastLoginErrorMessage; }
|
||||||
|
|
||||||
|
void setLastSignupErrorMessage(const QString& msg);
|
||||||
|
QString getLastSignupErrorMessage() const { return _lastSignupErrorMessage; }
|
||||||
|
|
||||||
|
QString getBuildVersion();
|
||||||
|
QString getLastUsedUsername() const { return _lastUsedUsername; }
|
||||||
|
|
||||||
|
void setApplicationStateError(QString errorMessage);
|
||||||
|
void setApplicationState(ApplicationState state);
|
||||||
|
ApplicationState getApplicationState() const;
|
||||||
|
|
||||||
|
Q_INVOKABLE void gotoSignup();
|
||||||
|
Q_INVOKABLE void gotoLogin();
|
||||||
|
|
||||||
|
// Request builds
|
||||||
|
void requestBuilds();
|
||||||
|
|
||||||
|
// Signup
|
||||||
|
Q_INVOKABLE void signup(QString email, QString username, QString password, QString displayName);
|
||||||
|
|
||||||
|
// Login
|
||||||
|
Q_INVOKABLE void login(QString username, QString password, QString displayName);
|
||||||
|
|
||||||
|
// Request Settings
|
||||||
|
void requestSettings();
|
||||||
|
|
||||||
|
Q_INVOKABLE void restart();
|
||||||
|
|
||||||
|
// Launcher
|
||||||
|
void downloadLauncher();
|
||||||
|
void installLauncher();
|
||||||
|
|
||||||
|
// Client
|
||||||
|
void downloadClient();
|
||||||
|
void installClient();
|
||||||
|
|
||||||
|
// Content Cache
|
||||||
|
void downloadContentCache();
|
||||||
|
void installContentCache();
|
||||||
|
|
||||||
|
// Launching
|
||||||
|
void launchClient();
|
||||||
|
|
||||||
|
Q_INVOKABLE float getDownloadProgress() const { return calculateDownloadProgress(); }
|
||||||
|
|
||||||
|
Q_INVOKABLE void openURLInBrowser(QString url);
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void updateSourceUrl(QUrl sourceUrl);
|
||||||
|
void uiStateChanged();
|
||||||
|
void applicationStateChanged();
|
||||||
|
void downloadProgressChanged();
|
||||||
|
void lastSignupErrorChanged();
|
||||||
|
void lastSignupErrorMessageChanged();
|
||||||
|
void lastLoginErrorMessageChanged();
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void clientDownloadComplete();
|
||||||
|
void contentCacheDownloadComplete();
|
||||||
|
void launcherDownloadComplete();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool shouldDownloadContentCache() const;
|
||||||
|
void getCurrentClientVersion();
|
||||||
|
|
||||||
|
float calculateDownloadProgress() const;
|
||||||
|
|
||||||
|
bool shouldDownloadLauncher();
|
||||||
|
|
||||||
|
QNetworkAccessManager _networkAccessManager;
|
||||||
|
Builds _latestBuilds;
|
||||||
|
QDir _launcherDirectory;
|
||||||
|
|
||||||
|
LauncherConfig _config;
|
||||||
|
|
||||||
|
// Application State
|
||||||
|
ApplicationState _applicationState { ApplicationState::Init };
|
||||||
|
UIState _uiState { UIState::SplashScreen };
|
||||||
|
LoginToken _loginResponse;
|
||||||
|
SignupRequest::Error _lastSignupError{ SignupRequest::Error::None };
|
||||||
|
QString _lastLoginErrorMessage{ "" };
|
||||||
|
QString _lastSignupErrorMessage{ "" };
|
||||||
|
QString _lastUsedUsername;
|
||||||
|
QString _displayName;
|
||||||
|
QString _applicationErrorMessage;
|
||||||
|
QString _currentClientVersion;
|
||||||
|
QString _buildTag { QString::null };
|
||||||
|
QString _contentCacheURL;
|
||||||
|
QString _loginTokenResponse;
|
||||||
|
QFile _clientZipFile;
|
||||||
|
QFile _launcherZipFile;
|
||||||
|
QFile _contentZipFile;
|
||||||
|
|
||||||
|
QString _username;
|
||||||
|
QString _password;
|
||||||
|
|
||||||
|
float _downloadProgress { 0 };
|
||||||
|
float _contentDownloadProgress { 0 };
|
||||||
|
float _contentInstallProgress { 0 };
|
||||||
|
float _interfaceDownloadProgress { 0 };
|
||||||
|
float _interfaceInstallProgress { 0 };
|
||||||
|
};
|
73
launchers/qt/src/LauncherWindow.cpp
Normal file
|
@ -0,0 +1,73 @@
|
||||||
|
#include "LauncherWindow.h"
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
|
#include <QCursor>
|
||||||
|
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
#include <shellapi.h>
|
||||||
|
#include <propsys.h>
|
||||||
|
#include <propkey.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
LauncherWindow::LauncherWindow() {
|
||||||
|
#ifdef Q_OS_WIN
|
||||||
|
// On Windows, disable pinning of the launcher.
|
||||||
|
IPropertyStore* pps;
|
||||||
|
HWND id = (HWND)this->winId();
|
||||||
|
if (id == NULL) {
|
||||||
|
qDebug() << "Failed to disable pinning, window id is null";
|
||||||
|
} else {
|
||||||
|
HRESULT hr = SHGetPropertyStoreForWindow(id, IID_PPV_ARGS(&pps));
|
||||||
|
if (SUCCEEDED(hr)) {
|
||||||
|
PROPVARIANT var;
|
||||||
|
var.vt = VT_BOOL;
|
||||||
|
var.boolVal = VARIANT_TRUE;
|
||||||
|
hr = pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
|
||||||
|
pps->Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherWindow::keyPressEvent(QKeyEvent* event) {
|
||||||
|
QQuickView::keyPressEvent(event);
|
||||||
|
if (!event->isAccepted()) {
|
||||||
|
if (event->key() == Qt::Key_Escape) {
|
||||||
|
exit(0);
|
||||||
|
} else if (event->key() == Qt::Key_Up) {
|
||||||
|
_launcherState->toggleDebugState();
|
||||||
|
} else if (event->key() == Qt::Key_Left) {
|
||||||
|
_launcherState->gotoPreviousDebugScreen();
|
||||||
|
} else if (event->key() == Qt::Key_Right) {
|
||||||
|
_launcherState->gotoNextDebugScreen();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherWindow::mousePressEvent(QMouseEvent* event) {
|
||||||
|
QQuickView::mousePressEvent(event);
|
||||||
|
if (!event->isAccepted()) {
|
||||||
|
if (event->button() == Qt::LeftButton) {
|
||||||
|
_drag = true;
|
||||||
|
_previousMousePos = QCursor::pos();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherWindow::mouseReleaseEvent(QMouseEvent* event) {
|
||||||
|
QQuickView::mouseReleaseEvent(event);
|
||||||
|
_drag = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LauncherWindow::mouseMoveEvent(QMouseEvent* event) {
|
||||||
|
QQuickView::mouseMoveEvent(event);
|
||||||
|
if (!event->isAccepted()) {
|
||||||
|
if (_drag) {
|
||||||
|
QPoint cursorPos = QCursor::pos();
|
||||||
|
QPoint offset = _previousMousePos - cursorPos;
|
||||||
|
_previousMousePos = cursorPos;
|
||||||
|
setPosition(position() - offset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|