merge with master
1
.gitignore
vendored
|
@ -92,6 +92,7 @@ npm-debug.log
|
|||
|
||||
# Resource binary file
|
||||
interface/compiledResources
|
||||
*.rcc
|
||||
|
||||
# GPUCache
|
||||
interface/resources/GPUCache/*
|
||||
|
|
|
@ -376,7 +376,6 @@ void Agent::executeScript() {
|
|||
// setup an Avatar for the script to use
|
||||
auto scriptedAvatar = DependencyManager::get<ScriptableAvatar>();
|
||||
scriptedAvatar->setID(getSessionUUID());
|
||||
scriptedAvatar->setForceFaceTrackerConnected(true);
|
||||
|
||||
// call model URL setters with empty URLs so our avatar, if user, will have the default models
|
||||
scriptedAvatar->setSkeletonModelURL(QUrl());
|
||||
|
|
|
@ -32,24 +32,42 @@ MixerAvatar::MixerAvatar() {
|
|||
|
||||
_challengeTimer.setSingleShot(true);
|
||||
_challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS);
|
||||
|
||||
_challengeTimer.callOnTimeout(this, [this]() {
|
||||
if (_verifyState == challengeClient) {
|
||||
_pendingEvent = false;
|
||||
_verifyState = verificationFailed;
|
||||
_needsIdentityUpdate = true;
|
||||
qCDebug(avatars) << "Dynamic verification TIMED-OUT for" << getDisplayName() << getSessionUUID();
|
||||
} else {
|
||||
qCDebug(avatars) << "Ignoring timeout of avatar challenge";
|
||||
}
|
||||
});
|
||||
|
||||
_challengeTimer.callOnTimeout(this, &MixerAvatar::challengeTimeout);
|
||||
// QTimer::start is a set of overloaded functions.
|
||||
connect(this, &MixerAvatar::startChallengeTimer, &_challengeTimer, static_cast<void(QTimer::*)()>(&QTimer::start));
|
||||
}
|
||||
|
||||
const char* MixerAvatar::stateToName(VerifyState 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() {
|
||||
if (_verifyState >= requestingFST && _verifyState <= challengeClient) {
|
||||
qCDebug(avatars) << "WARNING: Avatar verification restarted; old state:" << stateToName(_verifyState);
|
||||
|
@ -58,7 +76,7 @@ void MixerAvatar::fetchAvatarFST() {
|
|||
|
||||
_pendingEvent = false;
|
||||
|
||||
QUrl avatarURL = getSkeletonModelURL();
|
||||
QUrl avatarURL = _skeletonModelURL;
|
||||
if (avatarURL.isEmpty() || avatarURL.isLocalFile() || avatarURL.scheme() == "qrc") {
|
||||
// Not network FST.
|
||||
return;
|
||||
|
@ -210,6 +228,23 @@ void MixerAvatar::ownerRequestComplete() {
|
|||
networkReply->deleteLater();
|
||||
}
|
||||
|
||||
void MixerAvatar::requestCurrentOwnership() {
|
||||
// Get registered owner's public key from metaverse.
|
||||
static const QString POP_MARKETPLACE_API { "/api/v1/commerce/proof_of_purchase_status/transfer" };
|
||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest;
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
|
||||
requestURL.setPath(POP_MARKETPLACE_API);
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
QJsonObject request;
|
||||
request["certificate_id"] = _certificateIdFromFST;
|
||||
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||
connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete);
|
||||
}
|
||||
|
||||
void MixerAvatar::processCertifyEvents() {
|
||||
if (!_pendingEvent) {
|
||||
return;
|
||||
|
@ -221,26 +256,15 @@ void MixerAvatar::processCertifyEvents() {
|
|||
case receivedFST:
|
||||
{
|
||||
generateFSTHash();
|
||||
_numberChallenges = 0;
|
||||
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" };
|
||||
auto& networkAccessManager = NetworkAccessManager::getInstance();
|
||||
QNetworkRequest networkRequest;
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
|
||||
requestURL.setPath(POP_MARKETPLACE_API);
|
||||
networkRequest.setUrl(requestURL);
|
||||
|
||||
QJsonObject request;
|
||||
request["certificate_id"] = _certificateIdFromFST;
|
||||
requestCurrentOwnership();
|
||||
_verifyState = requestingOwner;
|
||||
QNetworkReply* networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson());
|
||||
connect(networkReply, &QNetworkReply::finished, this, &MixerAvatar::ownerRequestComplete);
|
||||
} else {
|
||||
_needsIdentityUpdate = true;
|
||||
_pendingEvent = false;
|
||||
|
@ -248,11 +272,21 @@ void MixerAvatar::processCertifyEvents() {
|
|||
}
|
||||
} else { // FST doesn't have a certificate, so noncertified rather than failed:
|
||||
_pendingEvent = false;
|
||||
_certifyFailed = false;
|
||||
_needsIdentityUpdate = true;
|
||||
_verifyState = nonCertified;
|
||||
qCDebug(avatars) << "Avatar " << getDisplayName() << "(" << getSessionUUID() << ") isn't certified";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case staticValidation:
|
||||
{
|
||||
requestCurrentOwnership();
|
||||
_verifyState = requestingOwner;
|
||||
break;
|
||||
}
|
||||
|
||||
case ownerResponse:
|
||||
{
|
||||
QJsonDocument responseJson = QJsonDocument::fromJson(_dynamicMarketResponse.toUtf8());
|
||||
|
@ -310,23 +344,28 @@ void MixerAvatar::processCertifyEvents() {
|
|||
void MixerAvatar::sendOwnerChallenge() {
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
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,
|
||||
2 * sizeof(int) + nonce.length() + avatarID.length(), true);
|
||||
2 * sizeof(int) + _challengeNonce.length() + avatarID.length(), true);
|
||||
challengeOwnershipPacket->writePrimitive(avatarID.length());
|
||||
challengeOwnershipPacket->writePrimitive(nonce.length());
|
||||
challengeOwnershipPacket->writePrimitive(_challengeNonce.length());
|
||||
challengeOwnershipPacket->write(avatarID);
|
||||
challengeOwnershipPacket->write(nonce);
|
||||
challengeOwnershipPacket->write(_challengeNonce);
|
||||
|
||||
nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(getSessionUUID())) );
|
||||
QCryptographicHash nonceHash(QCryptographicHash::Sha256);
|
||||
nonceHash.addData(nonce);
|
||||
nonceHash.addData(_challengeNonce);
|
||||
_challengeNonceHash = nonceHash.result();
|
||||
_pendingEvent = false;
|
||||
|
||||
// QTimer::start is a set of overloaded functions.
|
||||
QMetaObject::invokeMethod(&_challengeTimer, static_cast<void(QTimer::*)()>(&QTimer::start));
|
||||
emit startChallengeTimer();
|
||||
}
|
||||
|
||||
void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
||||
|
@ -337,7 +376,7 @@ void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
|||
QByteArray responseData = response.readAll();
|
||||
if (responseData.length() < 8) {
|
||||
_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;
|
||||
}
|
||||
|
||||
|
@ -354,11 +393,14 @@ void MixerAvatar::processChallengeResponse(ReceivedMessage& response) {
|
|||
bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash,
|
||||
QByteArray::fromBase64(signedNonce));
|
||||
_verifyState = challengeResult ? verificationSucceeded : verificationFailed;
|
||||
_certifyFailed = !challengeResult;
|
||||
_needsIdentityUpdate = true;
|
||||
if (_verifyState == verificationFailed) {
|
||||
if (_certifyFailed) {
|
||||
qCDebug(avatars) << "Dynamic verification FAILED for" << getDisplayName() << getSessionUUID();
|
||||
emit startChallengeTimer();
|
||||
} else {
|
||||
qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID();
|
||||
_challengeNonce.clear();
|
||||
}
|
||||
|
||||
} else {
|
||||
|
|
|
@ -27,7 +27,7 @@ public:
|
|||
void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; }
|
||||
|
||||
void fetchAvatarFST();
|
||||
virtual bool isCertifyFailed() const override { return _verifyState == verificationFailed; }
|
||||
virtual bool isCertifyFailed() const override { return _certifyFailed; }
|
||||
bool needsIdentityUpdate() const { return _needsIdentityUpdate; }
|
||||
void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; }
|
||||
|
||||
|
@ -58,13 +58,18 @@ private:
|
|||
QString _certificateIdFromFST;
|
||||
QString _dynamicMarketResponse;
|
||||
QString _ownerPublicKey;
|
||||
QByteArray _challengeNonce;
|
||||
QByteArray _challengeNonceHash;
|
||||
QTimer _challengeTimer;
|
||||
static constexpr int NUM_CHALLENGES_BEFORE_FAIL = 1;
|
||||
int _numberChallenges { 0 };
|
||||
bool _certifyFailed { false };
|
||||
bool _needsIdentityUpdate { false };
|
||||
|
||||
bool generateFSTHash();
|
||||
bool validateFSTHash(const QString& publicKey) const;
|
||||
QByteArray canonicalJson(const QString fstFile);
|
||||
void requestCurrentOwnership();
|
||||
void sendOwnerChallenge();
|
||||
|
||||
static const QString VERIFY_FAIL_MODEL;
|
||||
|
@ -72,6 +77,10 @@ private:
|
|||
private slots:
|
||||
void fstRequestComplete();
|
||||
void ownerRequestComplete();
|
||||
void challengeTimeout();
|
||||
|
||||
signals:
|
||||
void startChallengeTimer();
|
||||
};
|
||||
|
||||
using MixerAvatarSharedPointer = std::shared_ptr<MixerAvatar>;
|
||||
|
|
|
@ -279,18 +279,6 @@ void ScriptableAvatar::setJointMappingsFromNetworkReply() {
|
|||
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 {
|
||||
// DANGER: Now that we store the AvatarEntityData in packed format this call is potentially Very Expensive!
|
||||
// Avoid calling this method if possible.
|
||||
|
|
|
@ -153,13 +153,6 @@ public:
|
|||
|
||||
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
|
||||
* Gets details of all avatar entities.
|
||||
* <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'
|
||||
else:
|
||||
print('UNKNOWN LINUX VERSION!!!')
|
||||
return;
|
||||
else:
|
||||
print('UNKNOWN OPERATING SYSTEM!!!')
|
||||
return;
|
||||
|
||||
print('Extracting ' + url + ' to ' + dest)
|
||||
hifi_utils.downloadAndExtract(url, dest)
|
||||
|
|
|
@ -211,10 +211,10 @@ endif()
|
|||
link_hifi_libraries(
|
||||
shared workload task octree ktx gpu gl procedural graphics graphics-scripting render
|
||||
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
|
||||
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
|
||||
# Platform specific GL libraries
|
||||
${PLATFORM_GL_BACKEND}
|
||||
|
|
|
@ -166,9 +166,70 @@
|
|||
{ "from": "Standard.LeftEye", "to": "Actions.LeftEye" },
|
||||
{ "from": "Standard.RightEye", "to": "Actions.RightEye" },
|
||||
|
||||
{ "from": "Standard.LeftEyeBlink", "to": "Actions.LeftEyeBlink" },
|
||||
{ "from": "Standard.RightEyeBlink", "to": "Actions.RightEyeBlink" },
|
||||
|
||||
{ "from": "Standard.EyeBlink_L", "to": "Actions.EyeBlink_L" },
|
||||
{ "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.TrackedObject01", "to" : "Actions.TrackedObject01" },
|
||||
|
|
|
@ -61,8 +61,70 @@
|
|||
{ "from": "Standard.LeftEye", "to": "Actions.LeftEye" },
|
||||
{ "from": "Standard.RightEye", "to": "Actions.RightEye" },
|
||||
|
||||
{ "from": "Standard.LeftEyeBlink", "to": "Actions.LeftEyeBlink" },
|
||||
{ "from": "Standard.RightEyeBlink", "to": "Actions.RightEyeBlink" },
|
||||
{ "from": "Standard.EyeBlink_L", "to": "Actions.EyeBlink_L" },
|
||||
{ "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.TrackedObject01", "to" : "Actions.TrackedObject01" },
|
||||
|
|
|
@ -98,8 +98,9 @@
|
|||
{ "from": "Vive.Head", "to" : "Standard.Head" },
|
||||
{ "from": "Vive.LeftEye", "to" : "Standard.LeftEye" },
|
||||
{ "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",
|
||||
|
|
|
@ -65,6 +65,15 @@ Windows.Window {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
Timer {
|
||||
id: timer
|
||||
interval: 500;
|
||||
repeat: false;
|
||||
onTriggered: {
|
||||
updateContentParent();
|
||||
}
|
||||
}
|
||||
|
||||
function updateInteractiveWindowPositionForMode() {
|
||||
if (presentationMode === Desktop.PresentationMode.VIRTUAL) {
|
||||
|
@ -107,13 +116,11 @@ Windows.Window {
|
|||
if (nativeWindow) {
|
||||
nativeWindow.setVisible(false);
|
||||
}
|
||||
updateContentParent();
|
||||
updateInteractiveWindowPositionForMode();
|
||||
shown = interactiveWindowVisible;
|
||||
} else if (presentationMode === Desktop.PresentationMode.NATIVE) {
|
||||
shown = false;
|
||||
if (nativeWindow) {
|
||||
updateContentParent();
|
||||
updateInteractiveWindowPositionForMode();
|
||||
nativeWindow.setVisible(interactiveWindowVisible);
|
||||
}
|
||||
|
@ -123,10 +130,7 @@ Windows.Window {
|
|||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Fix for parent loss on OSX:
|
||||
parent.heightChanged.connect(updateContentParent);
|
||||
parent.widthChanged.connect(updateContentParent);
|
||||
|
||||
|
||||
x = interactiveWindowPosition.x;
|
||||
y = interactiveWindowPosition.y;
|
||||
width = interactiveWindowSize.width;
|
||||
|
@ -140,6 +144,11 @@ Windows.Window {
|
|||
id: root;
|
||||
width: interactiveWindowSize.width
|
||||
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 {
|
||||
color: hifi.colors.baseGray
|
||||
|
@ -170,6 +179,7 @@ Windows.Window {
|
|||
interactiveWindowPosition = Qt.point(nativeWindow.x, interactiveWindowPosition.y);
|
||||
}
|
||||
});
|
||||
|
||||
nativeWindow.yChanged.connect(function() {
|
||||
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
|
||||
interactiveWindowPosition = Qt.point(interactiveWindowPosition.x, nativeWindow.y);
|
||||
|
@ -181,6 +191,7 @@ Windows.Window {
|
|||
interactiveWindowSize = Qt.size(nativeWindow.width, interactiveWindowSize.height);
|
||||
}
|
||||
});
|
||||
|
||||
nativeWindow.heightChanged.connect(function() {
|
||||
if (presentationMode === Desktop.PresentationMode.NATIVE && nativeWindow.visible) {
|
||||
interactiveWindowSize = Qt.size(interactiveWindowSize.width, nativeWindow.height);
|
||||
|
@ -194,15 +205,11 @@ Windows.Window {
|
|||
|
||||
// finally set the initial window mode:
|
||||
setupPresentationMode();
|
||||
updateContentParent();
|
||||
|
||||
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
|
||||
function fromScript(message) {
|
||||
if (root.dynamicContent && root.dynamicContent.fromScript) {
|
||||
|
@ -232,8 +239,8 @@ Windows.Window {
|
|||
interactiveWindowSize.width = newWidth;
|
||||
updateInteractiveWindowSizeForMode();
|
||||
}
|
||||
function onRequestNewHeight(newWidth) {
|
||||
interactiveWindowSize.width = newWidth;
|
||||
function onRequestNewHeight(newHeight) {
|
||||
interactiveWindowSize.height = newHeight;
|
||||
updateInteractiveWindowSizeForMode();
|
||||
}
|
||||
|
||||
|
@ -308,6 +315,7 @@ Windows.Window {
|
|||
onPresentationModeChanged: {
|
||||
if (initialized) {
|
||||
setupPresentationMode();
|
||||
updateContentParent();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -375,14 +375,14 @@ Rectangle {
|
|||
x: margins.paddings
|
||||
interactive: false;
|
||||
height: contentHeight;
|
||||
spacing: 4;
|
||||
|
||||
clip: true;
|
||||
model: AudioScriptingInterface.devices.input;
|
||||
delegate: Item {
|
||||
width: rightMostInputLevelPos - margins.paddings*2
|
||||
height: margins.sizeCheckBox > checkBoxInput.implicitHeight ?
|
||||
margins.sizeCheckBox : checkBoxInput.implicitHeight
|
||||
|
||||
height: ((type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)) ?
|
||||
(margins.sizeCheckBox > checkBoxInput.implicitHeight ? margins.sizeCheckBox + 4 : checkBoxInput.implicitHeight + 4) : 0
|
||||
visible: (type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)
|
||||
AudioControls.CheckBox {
|
||||
id: checkBoxInput
|
||||
anchors.left: parent.left
|
||||
|
@ -470,13 +470,13 @@ Rectangle {
|
|||
height: contentHeight;
|
||||
anchors.top: outputDeviceHeader.bottom;
|
||||
anchors.topMargin: 10;
|
||||
spacing: 4;
|
||||
clip: true;
|
||||
model: AudioScriptingInterface.devices.output;
|
||||
delegate: Item {
|
||||
width: rightMostInputLevelPos
|
||||
height: margins.sizeCheckBox > checkBoxOutput.implicitHeight ?
|
||||
margins.sizeCheckBox : checkBoxOutput.implicitHeight
|
||||
height: ((type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)) ?
|
||||
(margins.sizeCheckBox > checkBoxOutput.implicitHeight ? margins.sizeCheckBox + 4 : checkBoxOutput.implicitHeight + 4) : 0
|
||||
visible: (type != "hmd" && bar.currentIndex === 0) || (type != "desktop" && bar.currentIndex === 1)
|
||||
|
||||
AudioControls.CheckBox {
|
||||
id: checkBoxOutput
|
||||
|
|
|
@ -133,15 +133,12 @@ Item {
|
|||
|
||||
ListElement {
|
||||
text: "Low World Detail"
|
||||
worldDetailQualityValue: 0.25
|
||||
}
|
||||
ListElement {
|
||||
text: "Medium World Detail"
|
||||
worldDetailQualityValue: 0.5
|
||||
}
|
||||
ListElement {
|
||||
text: "Full World Detail"
|
||||
worldDetailQualityValue: 0.75
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -158,14 +155,7 @@ Item {
|
|||
currentIndex: -1
|
||||
|
||||
function refreshWorldDetailDropdown() {
|
||||
var currentWorldDetailQuality = LODManager.worldDetailQuality;
|
||||
if (currentWorldDetailQuality <= 0.25) {
|
||||
worldDetailDropdown.currentIndex = 0;
|
||||
} else if (currentWorldDetailQuality <= 0.5) {
|
||||
worldDetailDropdown.currentIndex = 1;
|
||||
} else {
|
||||
worldDetailDropdown.currentIndex = 2;
|
||||
}
|
||||
worldDetailDropdown.currentIndex = LODManager.worldDetailQuality;
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
|
@ -173,7 +163,7 @@ Item {
|
|||
}
|
||||
|
||||
onCurrentIndexChanged: {
|
||||
LODManager.worldDetailQuality = model.get(currentIndex).worldDetailQualityValue;
|
||||
LODManager.worldDetailQuality = currentIndex;
|
||||
worldDetailDropdown.displayText = model.get(currentIndex).text;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -258,13 +258,12 @@ Flickable {
|
|||
Layout.preferredHeight: contentItem.height
|
||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||
interactive: false
|
||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
||||
clip: true
|
||||
model: AudioScriptingInterface.devices.input
|
||||
delegate: Item {
|
||||
width: parent.width
|
||||
height: inputDeviceCheckbox.height
|
||||
|
||||
width: parent.width
|
||||
height: model.type != "hmd" ? inputDeviceCheckbox.height + simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0
|
||||
visible: model.type != "hmd"
|
||||
SimplifiedControls.RadioButton {
|
||||
id: inputDeviceCheckbox
|
||||
anchors.left: parent.left
|
||||
|
@ -354,13 +353,12 @@ Flickable {
|
|||
Layout.preferredHeight: contentItem.height
|
||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||
interactive: false
|
||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
||||
clip: true
|
||||
model: AudioScriptingInterface.devices.output
|
||||
delegate: Item {
|
||||
width: parent.width
|
||||
height: outputDeviceCheckbox.height
|
||||
|
||||
height: model.type != "hmd" ? outputDeviceCheckbox.height +simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0
|
||||
visible: model.type != "hmd"
|
||||
SimplifiedControls.RadioButton {
|
||||
id: outputDeviceCheckbox
|
||||
anchors.left: parent.left
|
||||
|
|
|
@ -259,13 +259,13 @@ Flickable {
|
|||
Layout.preferredHeight: contentItem.height
|
||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||
interactive: false
|
||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
||||
clip: true
|
||||
model: AudioScriptingInterface.devices.input
|
||||
delegate: Item {
|
||||
width: parent.width
|
||||
height: inputDeviceCheckbox.height
|
||||
|
||||
width: parent.width
|
||||
height: model.type != "desktop" ? inputDeviceCheckbox.height + simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0
|
||||
visible: model.type != "desktop"
|
||||
|
||||
SimplifiedControls.RadioButton {
|
||||
id: inputDeviceCheckbox
|
||||
anchors.left: parent.left
|
||||
|
@ -355,13 +355,12 @@ Flickable {
|
|||
Layout.preferredHeight: contentItem.height
|
||||
Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin
|
||||
interactive: false
|
||||
spacing: simplifiedUI.margins.settings.spacingBetweenRadiobuttons
|
||||
clip: true
|
||||
model: AudioScriptingInterface.devices.output
|
||||
delegate: Item {
|
||||
width: parent.width
|
||||
height: outputDeviceCheckbox.height
|
||||
|
||||
height: model.type != "desktop" ? outputDeviceCheckbox.height + simplifiedUI.margins.settings.spacingBetweenRadiobuttons : 0
|
||||
visible: model.type != "desktop"
|
||||
SimplifiedControls.RadioButton {
|
||||
id: outputDeviceCheckbox
|
||||
anchors.left: parent.left
|
||||
|
|
|
@ -171,7 +171,6 @@
|
|||
#include "avatar/MyCharacterController.h"
|
||||
#include "CrashRecoveryHandler.h"
|
||||
#include "CrashHandler.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "DiscoverabilityManager.h"
|
||||
#include "GLCanvas.h"
|
||||
#include "InterfaceDynamicFactory.h"
|
||||
|
@ -889,11 +888,6 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
|
|||
DependencyManager::set<ScriptCache>();
|
||||
DependencyManager::set<SoundCache>();
|
||||
DependencyManager::set<SoundCacheScriptingInterface>();
|
||||
|
||||
#ifdef HAVE_DDE
|
||||
DependencyManager::set<DdeFaceTracker>();
|
||||
#endif
|
||||
|
||||
DependencyManager::set<AudioClient>();
|
||||
DependencyManager::set<AudioScope>();
|
||||
DependencyManager::set<DeferredLightingEffect>();
|
||||
|
@ -1070,7 +1064,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
_lastSendDownstreamAudioStats(usecTimestampNow()),
|
||||
_notifiedPacketVersionMismatchThisDomain(false),
|
||||
_maxOctreePPS(maxOctreePacketsPerSecond.get()),
|
||||
_lastFaceTrackerUpdate(0),
|
||||
_snapshotSound(nullptr),
|
||||
_sampleSound(nullptr)
|
||||
{
|
||||
|
@ -2020,13 +2013,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
|
|||
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
|
||||
const QString HIFI_NO_UPDATER_COMMAND_LINE_KEY = "--no-updater";
|
||||
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.
|
||||
#ifdef HAVE_DDE
|
||||
DependencyManager::get<DdeFaceTracker>()->setEnabled(false);
|
||||
#endif
|
||||
AnimDebugDraw::getInstance().shutdown();
|
||||
|
||||
// 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();
|
||||
|
||||
// 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
|
||||
|
||||
// stop QML
|
||||
|
@ -3481,9 +3460,6 @@ void Application::onDesktopRootContextCreated(QQmlContext* surfaceContext) {
|
|||
surfaceContext->setContextProperty("AccountServices", AccountServicesScriptingInterface::getInstance());
|
||||
|
||||
surfaceContext->setContextProperty("DialogsManager", _dialogsManagerScriptingInterface);
|
||||
#ifdef HAVE_DDE
|
||||
surfaceContext->setContextProperty("FaceTracker", DependencyManager::get<DdeFaceTracker>().data());
|
||||
#endif
|
||||
surfaceContext->setContextProperty("AvatarManager", DependencyManager::get<AvatarManager>().data());
|
||||
surfaceContext->setContextProperty("LODManager", DependencyManager::get<LODManager>().data());
|
||||
surfaceContext->setContextProperty("HMD", DependencyManager::get<HMDScriptingInterface>().data());
|
||||
|
@ -3750,16 +3726,6 @@ void Application::runTests() {
|
|||
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) {
|
||||
if (fov != _fieldOfView.get()) {
|
||||
_fieldOfView.set(fov);
|
||||
|
@ -5334,43 +5300,6 @@ ivec2 Application::getMouse() const {
|
|||
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,
|
||||
const QVector<QUuid>& entityIDs,
|
||||
const glm::vec3* givenOffset) {
|
||||
|
@ -5848,14 +5777,13 @@ void Application::pushPostUpdateLambda(void* key, const std::function<void()>& f
|
|||
// to everyone.
|
||||
// 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.
|
||||
void Application::updateMyAvatarLookAtPosition() {
|
||||
void Application::updateMyAvatarLookAtPosition(float deltaTime) {
|
||||
PerformanceTimer perfTimer("lookAt");
|
||||
bool showWarnings = Menu::getInstance()->isOptionChecked(MenuOption::PipelineWarnings);
|
||||
PerformanceWarning warn(showWarnings, "Application::updateMyAvatarLookAtPosition()");
|
||||
|
||||
auto myAvatar = getMyAvatar();
|
||||
FaceTracker* faceTracker = getActiveFaceTracker();
|
||||
myAvatar->updateLookAtPosition(faceTracker, _myCamera);
|
||||
myAvatar->updateEyesLookAtPosition(deltaTime);
|
||||
}
|
||||
|
||||
void Application::updateThreads(float deltaTime) {
|
||||
|
@ -6281,37 +6209,6 @@ void Application::update(float deltaTime) {
|
|||
auto myAvatar = getMyAvatar();
|
||||
{
|
||||
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>();
|
||||
|
||||
controller::HmdAvatarAlignmentType hmdAvatarAlignmentType;
|
||||
|
@ -6639,7 +6536,7 @@ void Application::update(float deltaTime) {
|
|||
{
|
||||
PROFILE_RANGE(simulation, "MyAvatar");
|
||||
PerformanceTimer perfTimer("MyAvatar");
|
||||
qApp->updateMyAvatarLookAtPosition();
|
||||
qApp->updateMyAvatarLookAtPosition(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
|
||||
// rig and animations from scratch.
|
||||
void Application::resetSensors(bool andReload) {
|
||||
#ifdef HAVE_DDE
|
||||
DependencyManager::get<DdeFaceTracker>()->reset();
|
||||
#endif
|
||||
|
||||
_overlayConductor.centerUI();
|
||||
getActiveDisplayPlugin()->resetSensors();
|
||||
getMyAvatar()->reset(true, andReload);
|
||||
|
@ -7508,13 +7401,10 @@ void Application::registerScriptEngineWithApplicationServices(const ScriptEngine
|
|||
scriptEngine->registerGlobalObject("AccountServices", AccountServicesScriptingInterface::getInstance());
|
||||
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("LODManager", DependencyManager::get<LODManager>().data());
|
||||
qScriptRegisterMetaType(scriptEngine.data(), worldDetailQualityToScriptValue, worldDetailQualityFromScriptValue);
|
||||
|
||||
scriptEngine->registerGlobalObject("Keyboard", DependencyManager::get<KeyboardScriptingInterface>().data());
|
||||
scriptEngine->registerGlobalObject("Performance", new PerformanceScriptingInterface());
|
||||
|
|
|
@ -81,7 +81,6 @@
|
|||
#include "VisionSqueeze.h"
|
||||
|
||||
class GLCanvas;
|
||||
class FaceTracker;
|
||||
class MainWindow;
|
||||
class AssetUpload;
|
||||
class CompositorHelper;
|
||||
|
@ -191,9 +190,6 @@ public:
|
|||
|
||||
ivec2 getMouse() const;
|
||||
|
||||
FaceTracker* getActiveFaceTracker();
|
||||
FaceTracker* getSelectedFaceTracker();
|
||||
|
||||
ApplicationOverlay& getApplicationOverlay() { return _applicationOverlay; }
|
||||
const ApplicationOverlay& getApplicationOverlay() const { return _applicationOverlay; }
|
||||
CompositorHelper& getApplicationCompositor() const;
|
||||
|
@ -292,7 +288,7 @@ public:
|
|||
|
||||
virtual void pushPostUpdateLambda(void* key, const std::function<void()>& func) override;
|
||||
|
||||
void updateMyAvatarLookAtPosition();
|
||||
void updateMyAvatarLookAtPosition(float deltaTime);
|
||||
|
||||
float getGameLoopRate() const { return _gameLoopCounter.rate(); }
|
||||
|
||||
|
@ -423,7 +419,6 @@ public slots:
|
|||
static void packageModel();
|
||||
|
||||
void resetSensors(bool andReload = false);
|
||||
void setActiveFaceTracker() const;
|
||||
|
||||
void hmdVisibleChanged(bool visible);
|
||||
|
||||
|
@ -497,8 +492,6 @@ private slots:
|
|||
|
||||
void resettingDomain();
|
||||
|
||||
void faceTrackerMuteToggled();
|
||||
|
||||
void activeChanged(Qt::ApplicationState state);
|
||||
void windowMinimizedChanged(bool minimized);
|
||||
|
||||
|
@ -736,8 +729,6 @@ private:
|
|||
PerformanceManager _performanceManager;
|
||||
RefreshRateManager _refreshRateManager;
|
||||
|
||||
quint64 _lastFaceTrackerUpdate;
|
||||
|
||||
GameWorkload _gameWorkload;
|
||||
|
||||
GraphicsEngine _graphicsEngine;
|
||||
|
|
|
@ -212,22 +212,36 @@ void AvatarBookmarks::loadBookmark(const QString& bookmarkName) {
|
|||
auto myAvatar = DependencyManager::get<AvatarManager>()->getMyAvatar();
|
||||
auto treeRenderer = DependencyManager::get<EntityTreeRenderer>();
|
||||
EntityTreePointer entityTree = treeRenderer ? treeRenderer->getTree() : nullptr;
|
||||
myAvatar->clearWornAvatarEntities();
|
||||
|
||||
// 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();
|
||||
const float& qScale = bookmark.value(ENTRY_AVATAR_SCALE, 1.0f).toFloat();
|
||||
myAvatar->setAvatarScale(qScale);
|
||||
QList<QVariant> attachments = bookmark.value(ENTRY_AVATAR_ATTACHMENTS, QList<QVariant>()).toList();
|
||||
myAvatar->setAttachmentsVariant(attachments);
|
||||
QVariantList avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
|
||||
addAvatarEntities(avatarEntities);
|
||||
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);
|
||||
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();
|
||||
myAvatar->setAvatarScale(qScale);
|
||||
|
||||
const QVariantList& avatarEntities = bookmark.value(ENTRY_AVATAR_ENTITIES, QVariantList()).toList();
|
||||
addAvatarEntities(avatarEntities);
|
||||
|
||||
emit bookmarkLoaded(bookmarkName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,11 +19,8 @@
|
|||
#include "ui/DialogsManager.h"
|
||||
#include "InterfaceLogging.h"
|
||||
|
||||
const float LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS;
|
||||
const float LODManager::DEFAULT_HMD_LOD_DOWN_FPS = LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS;
|
||||
|
||||
Setting::Handle<float> desktopLODDecreaseFPS("desktopLODDecreaseFPS", LODManager::DEFAULT_DESKTOP_LOD_DOWN_FPS);
|
||||
Setting::Handle<float> hmdLODDecreaseFPS("hmdLODDecreaseFPS", LODManager::DEFAULT_HMD_LOD_DOWN_FPS);
|
||||
Setting::Handle<int> desktopWorldDetailQuality("desktopWorldDetailQuality", (int)DEFAULT_WORLD_DETAIL_QUALITY);
|
||||
Setting::Handle<int> hmdWorldDetailQuality("hmdWorldDetailQuality", (int)DEFAULT_WORLD_DETAIL_QUALITY);
|
||||
|
||||
LODManager::LODManager() {
|
||||
}
|
||||
|
@ -326,19 +323,21 @@ QString LODManager::getLODFeedbackText() {
|
|||
}
|
||||
|
||||
void LODManager::loadSettings() {
|
||||
setDesktopLODTargetFPS(desktopLODDecreaseFPS.get());
|
||||
Setting::Handle<bool> firstRun { Settings::firstRun, true };
|
||||
auto desktopQuality = static_cast<WorldDetailQuality>(desktopWorldDetailQuality.get());
|
||||
auto hmdQuality = static_cast<WorldDetailQuality>(hmdWorldDetailQuality.get());
|
||||
|
||||
Setting::Handle<bool> firstRun{ Settings::firstRun, true };
|
||||
if (qApp->property(hifi::properties::OCULUS_STORE).toBool() && firstRun.get()) {
|
||||
const float LOD_HIGH_QUALITY_LEVEL = 0.75f;
|
||||
setHMDLODTargetFPS(LOD_HIGH_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS);
|
||||
} else {
|
||||
setHMDLODTargetFPS(hmdLODDecreaseFPS.get());
|
||||
hmdQuality = WORLD_DETAIL_HIGH;
|
||||
}
|
||||
|
||||
setWorldDetailQuality(desktopQuality, false);
|
||||
setWorldDetailQuality(hmdQuality, true);
|
||||
}
|
||||
|
||||
void LODManager::saveSettings() {
|
||||
desktopLODDecreaseFPS.set(getDesktopLODTargetFPS());
|
||||
hmdLODDecreaseFPS.set(getHMDLODTargetFPS());
|
||||
desktopWorldDetailQuality.set((int)_desktopWorldDetailQuality);
|
||||
hmdWorldDetailQuality.set((int)_hmdWorldDetailQuality);
|
||||
}
|
||||
|
||||
const float MIN_DECREASE_FPS = 0.5f;
|
||||
|
@ -393,54 +392,33 @@ float LODManager::getLODTargetFPS() const {
|
|||
}
|
||||
}
|
||||
|
||||
void LODManager::setWorldDetailQuality(float quality) {
|
||||
static const float MIN_FPS = 10;
|
||||
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;
|
||||
}
|
||||
|
||||
void LODManager::setWorldDetailQuality(WorldDetailQuality quality, bool isHMDMode) {
|
||||
float desiredFPS = isHMDMode ? QUALITY_TO_FPS_HMD[quality] : QUALITY_TO_FPS_DESKTOP[quality];
|
||||
if (isHMDMode) {
|
||||
_hmdWorldDetailQuality = quality;
|
||||
setHMDLODTargetFPS(desiredFPS);
|
||||
} else {
|
||||
_desktopWorldDetailQuality = quality;
|
||||
setDesktopLODTargetFPS(desiredFPS);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void LODManager::setWorldDetailQuality(WorldDetailQuality quality) {
|
||||
setWorldDetailQuality(quality, qApp->isHMDMode());
|
||||
emit worldDetailQualityChanged();
|
||||
}
|
||||
|
||||
float LODManager::getWorldDetailQuality() const {
|
||||
WorldDetailQuality LODManager::getWorldDetailQuality() const {
|
||||
return qApp->isHMDMode() ? _hmdWorldDetailQuality : _desktopWorldDetailQuality;
|
||||
}
|
||||
|
||||
static const float LOW = 0.25f;
|
||||
static const float MEDIUM = 0.5f;
|
||||
static const float HIGH = 0.75f;
|
||||
QScriptValue worldDetailQualityToScriptValue(QScriptEngine* engine, const WorldDetailQuality& worldDetailQuality) {
|
||||
return worldDetailQuality;
|
||||
}
|
||||
|
||||
bool inHMD = qApp->isHMDMode();
|
||||
|
||||
float targetFPS = 0.0f;
|
||||
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 worldDetailQualityFromScriptValue(const QScriptValue& object, WorldDetailQuality& worldDetailQuality) {
|
||||
worldDetailQuality =
|
||||
static_cast<WorldDetailQuality>(std::min(std::max(object.toInt32(), (int)WORLD_DETAIL_LOW), (int)WORLD_DETAIL_HIGH));
|
||||
}
|
||||
|
||||
void LODManager::setLODQualityLevel(float quality) {
|
||||
|
|
|
@ -23,26 +23,52 @@
|
|||
#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
|
||||
const float LOD_DEFAULT_QUALITY_LEVEL = 0.2f; // default quality level setting is High (lower framerate)
|
||||
#else
|
||||
const float LOD_DEFAULT_QUALITY_LEVEL = 0.5f; // default quality level setting is Mid
|
||||
#endif
|
||||
const float LOD_MAX_LIKELY_DESKTOP_FPS = 60.0f; // this is essentially, V-synch fps
|
||||
|
||||
#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
|
||||
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
|
||||
|
||||
const float LOD_OFFSET_FPS = 5.0f; // offset of FPS to add for computing the target framerate
|
||||
|
||||
class AABox;
|
||||
|
||||
|
||||
/**jsdoc
|
||||
* The LOD class manages your Level of Detail functions within Interface.
|
||||
* @namespace LODManager
|
||||
*
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
|
@ -51,12 +77,12 @@ class AABox;
|
|||
* @property {number} engineRunTime <em>Read-only.</em>
|
||||
* @property {number} gpuTime <em>Read-only.</em>
|
||||
*/
|
||||
|
||||
class LODManager : public QObject, public Dependency {
|
||||
Q_OBJECT
|
||||
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)
|
||||
|
||||
|
@ -193,8 +219,8 @@ public:
|
|||
float getSmoothRenderTime() const { return _smoothRenderTime; };
|
||||
float getSmoothRenderFPS() const { return (_smoothRenderTime > 0.0f ? (float)MSECS_PER_SECOND / _smoothRenderTime : 0.0f); };
|
||||
|
||||
void setWorldDetailQuality(float quality);
|
||||
float getWorldDetailQuality() const;
|
||||
void setWorldDetailQuality(WorldDetailQuality quality);
|
||||
WorldDetailQuality getWorldDetailQuality() const;
|
||||
|
||||
void setLODQualityLevel(float quality);
|
||||
float getLODQualityLevel() const;
|
||||
|
@ -220,9 +246,6 @@ public:
|
|||
float getPidOd() const;
|
||||
float getPidO() const;
|
||||
|
||||
static const float DEFAULT_DESKTOP_LOD_DOWN_FPS;
|
||||
static const float DEFAULT_HMD_LOD_DOWN_FPS;
|
||||
|
||||
signals:
|
||||
|
||||
/**jsdoc
|
||||
|
@ -244,6 +267,8 @@ signals:
|
|||
private:
|
||||
LODManager();
|
||||
|
||||
void setWorldDetailQuality(WorldDetailQuality quality, bool isHMDMode);
|
||||
|
||||
std::mutex _automaticLODLock;
|
||||
bool _automaticLODAdjust = true;
|
||||
|
||||
|
@ -258,8 +283,11 @@ private:
|
|||
|
||||
float _lodQualityLevel{ LOD_DEFAULT_QUALITY_LEVEL };
|
||||
|
||||
float _desktopTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_DESKTOP_FPS };
|
||||
float _hmdTargetFPS { LOD_OFFSET_FPS + LOD_DEFAULT_QUALITY_LEVEL * LOD_MAX_LIKELY_HMD_FPS };
|
||||
WorldDetailQuality _desktopWorldDetailQuality { DEFAULT_WORLD_DETAIL_QUALITY };
|
||||
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);
|
||||
int _boundaryLevelAdjust = 0;
|
||||
|
@ -269,4 +297,7 @@ private:
|
|||
glm::vec4 _pidOutputs{ 0.0f };
|
||||
};
|
||||
|
||||
QScriptValue worldDetailQualityToScriptValue(QScriptEngine* engine, const WorldDetailQuality& worldDetailQuality);
|
||||
void worldDetailQualityFromScriptValue(const QScriptValue& object, WorldDetailQuality& worldDetailQuality);
|
||||
|
||||
#endif // hifi_LODManager_h
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
#include "avatar/AvatarManager.h"
|
||||
#include "avatar/AvatarPackager.h"
|
||||
#include "AvatarBookmarks.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "MainWindow.h"
|
||||
#include "render/DrawStatus.h"
|
||||
#include "scripting/MenuScriptingInterface.h"
|
||||
|
@ -499,47 +498,6 @@ Menu::Menu() {
|
|||
// Developer > 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);
|
||||
connect(action, &QAction::triggered, [this]{ Avatar::setShowReceiveStats(isOptionChecked(MenuOption::AvatarReceiveStats)); });
|
||||
action = addCheckableActionToQMenuAndActionHash(avatarDebugMenu, MenuOption::ShowBoundingCollisionShapes, 0, false);
|
||||
|
|
|
@ -92,7 +92,7 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP
|
|||
RenderScriptingInterface::getInstance()->setShadowsEnabled(true);
|
||||
qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::REALTIME);
|
||||
|
||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(0.75f);
|
||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(WORLD_DETAIL_HIGH);
|
||||
|
||||
break;
|
||||
case PerformancePreset::MID:
|
||||
|
@ -104,7 +104,7 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP
|
|||
|
||||
RenderScriptingInterface::getInstance()->setShadowsEnabled(false);
|
||||
qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::INTERACTIVE);
|
||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(0.5f);
|
||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(WORLD_DETAIL_MEDIUM);
|
||||
|
||||
break;
|
||||
case PerformancePreset::LOW:
|
||||
|
@ -114,7 +114,7 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP
|
|||
|
||||
RenderScriptingInterface::getInstance()->setViewportResolutionScale(recommandedPpiScale);
|
||||
|
||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(0.25f);
|
||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(WORLD_DETAIL_LOW);
|
||||
|
||||
break;
|
||||
case PerformancePreset::UNKNOWN:
|
||||
|
|
|
@ -26,19 +26,22 @@ class AudioScope : public QObject, public Dependency {
|
|||
SINGLETON_DEPENDENCY
|
||||
|
||||
/**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
|
||||
*
|
||||
* @deprecated This API doesn't work properly. It is deprecated and will be removed.
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
*
|
||||
* @property {number} scopeInput <em>Read-only.</em>
|
||||
* @property {number} scopeOutputLeft <em>Read-only.</em>
|
||||
* @property {number} scopeOutputRight <em>Read-only.</em>
|
||||
* @property {number} triggerInput <em>Read-only.</em>
|
||||
* @property {number} triggerOutputLeft <em>Read-only.</em>
|
||||
* @property {number} triggerOutputRight <em>Read-only.</em>
|
||||
* @property {number[]} scopeInput - Scope input. <em>Read-only.</em>
|
||||
* @property {number[]} scopeOutputLeft - Scope left output. <em>Read-only.</em>
|
||||
* @property {number[]} scopeOutputRight - Scope right output. <em>Read-only.</em>
|
||||
* @property {number[]} triggerInput - Trigger input. <em>Read-only.</em>
|
||||
* @property {number[]} triggerOutputLeft - Trigger left output. <em>Read-only.</em>
|
||||
* @property {number[]} triggerOutputRight - Trigger right output. <em>Read-only.</em>
|
||||
*/
|
||||
|
||||
Q_PROPERTY(QVector<int> scopeInput READ getScopeInput)
|
||||
|
@ -58,159 +61,186 @@ public:
|
|||
public slots:
|
||||
|
||||
/**jsdoc
|
||||
* Toggle.
|
||||
* @function AudioScope.toggle
|
||||
*/
|
||||
void toggle() { setVisible(!_isEnabled); }
|
||||
|
||||
/**jsdoc
|
||||
* Set visible.
|
||||
* @function AudioScope.setVisible
|
||||
* @param {boolean} visible
|
||||
* @param {boolean} visible - Visible.
|
||||
*/
|
||||
void setVisible(bool visible);
|
||||
|
||||
/**jsdoc
|
||||
* Get visible.
|
||||
* @function AudioScope.getVisible
|
||||
* @returns {boolean}
|
||||
* @returns {boolean} Visible.
|
||||
*/
|
||||
bool getVisible() const { return _isEnabled; }
|
||||
|
||||
/**jsdoc
|
||||
* Toggle pause.
|
||||
* @function AudioScope.togglePause
|
||||
*/
|
||||
void togglePause() { setPause(!_isPaused); }
|
||||
|
||||
/**jsdoc
|
||||
* Set pause.
|
||||
* @function AudioScope.setPause
|
||||
* @param {boolean} paused
|
||||
* @param {boolean} pause - Pause.
|
||||
*/
|
||||
void setPause(bool paused) { _isPaused = paused; emit pauseChanged(); }
|
||||
|
||||
/**jsdoc
|
||||
* Get pause.
|
||||
* @function AudioScope.getPause
|
||||
* @returns {boolean}
|
||||
* @returns {boolean} Pause.
|
||||
*/
|
||||
bool getPause() { return _isPaused; }
|
||||
|
||||
/**jsdoc
|
||||
* Toggle trigger.
|
||||
* @function AudioScope.toggleTrigger
|
||||
*/
|
||||
void toggleTrigger() { _autoTrigger = !_autoTrigger; }
|
||||
|
||||
/**jsdoc
|
||||
* Get auto trigger.
|
||||
* @function AudioScope.getAutoTrigger
|
||||
* @returns {boolean}
|
||||
* @returns {boolean} Auto trigger.
|
||||
*/
|
||||
bool getAutoTrigger() { return _autoTrigger; }
|
||||
|
||||
/**jsdoc
|
||||
* Set auto trigger.
|
||||
* @function AudioScope.setAutoTrigger
|
||||
* @param {boolean} autoTrigger
|
||||
* @param {boolean} autoTrigger - Auto trigger.
|
||||
*/
|
||||
void setAutoTrigger(bool autoTrigger) { _isTriggered = false; _autoTrigger = autoTrigger; }
|
||||
|
||||
/**jsdoc
|
||||
* Set trigger values.
|
||||
* @function AudioScope.setTriggerValues
|
||||
* @param {number} x
|
||||
* @param {number} y
|
||||
* @param {number} x - X.
|
||||
* @param {number} y - Y.
|
||||
*/
|
||||
void setTriggerValues(int x, int y) { _triggerValues.x = x; _triggerValues.y = y; }
|
||||
|
||||
/**jsdoc
|
||||
* Set triggered.
|
||||
* @function AudioScope.setTriggered
|
||||
* @param {boolean} triggered
|
||||
* @param {boolean} triggered - Triggered.
|
||||
*/
|
||||
void setTriggered(bool triggered) { _isTriggered = triggered; }
|
||||
|
||||
/**jsdoc
|
||||
* Get triggered.
|
||||
* @function AudioScope.getTriggered
|
||||
* @returns {boolean}
|
||||
* @returns {boolean} Triggered.
|
||||
*/
|
||||
bool getTriggered() { return _isTriggered; }
|
||||
|
||||
/**jsdoc
|
||||
* Get frames per second.
|
||||
* @function AudioScope.getFramesPerSecond
|
||||
* @returns {number}
|
||||
* @returns {number} Frames per second.
|
||||
*/
|
||||
float getFramesPerSecond();
|
||||
|
||||
/**jsdoc
|
||||
* Get frames per scope.
|
||||
* @function AudioScope.getFramesPerScope
|
||||
* @returns {number}
|
||||
* @returns {number} Frames per scope.
|
||||
*/
|
||||
int getFramesPerScope() { return _framesPerScope; }
|
||||
|
||||
/**jsdoc
|
||||
* Select five frames audio scope.
|
||||
* @function AudioScope.selectAudioScopeFiveFrames
|
||||
*/
|
||||
void selectAudioScopeFiveFrames();
|
||||
|
||||
/**jsdoc
|
||||
* Select twenty frames audio scope.
|
||||
* @function AudioScope.selectAudioScopeTwentyFrames
|
||||
*/
|
||||
void selectAudioScopeTwentyFrames();
|
||||
|
||||
/**jsdoc
|
||||
* Select fifty frames audio scope.
|
||||
* @function AudioScope.selectAudioScopeFiftyFrames
|
||||
*/
|
||||
void selectAudioScopeFiftyFrames();
|
||||
|
||||
/**jsdoc
|
||||
* Get scope input.
|
||||
* @function AudioScope.getScopeInput
|
||||
* @returns {number[]}
|
||||
* @returns {number[]} Scope input.
|
||||
*/
|
||||
QVector<int> getScopeInput() { return _scopeInputData; };
|
||||
|
||||
/**jsdoc
|
||||
* Get scope left output.
|
||||
* @function AudioScope.getScopeOutputLeft
|
||||
* @returns {number[]}
|
||||
* @returns {number[]} Scope left output.
|
||||
*/
|
||||
QVector<int> getScopeOutputLeft() { return _scopeOutputLeftData; };
|
||||
|
||||
/**jsdoc
|
||||
* Get scope right output.
|
||||
* @function AudioScope.getScopeOutputRight
|
||||
* @returns {number[]}
|
||||
* @returns {number[]} Scope right output.
|
||||
*/
|
||||
QVector<int> getScopeOutputRight() { return _scopeOutputRightData; };
|
||||
|
||||
/**jsdoc
|
||||
* Get trigger input.
|
||||
* @function AudioScope.getTriggerInput
|
||||
* @returns {number[]}
|
||||
* @returns {number[]} Trigger input.
|
||||
*/
|
||||
QVector<int> getTriggerInput() { return _triggerInputData; };
|
||||
|
||||
/**jsdoc
|
||||
* Get left trigger output.
|
||||
* @function AudioScope.getTriggerOutputLeft
|
||||
* @returns {number[]}
|
||||
* @returns {number[]} Left trigger output.
|
||||
*/
|
||||
QVector<int> getTriggerOutputLeft() { return _triggerOutputLeftData; };
|
||||
|
||||
/**jsdoc
|
||||
* Get right trigger output.
|
||||
* @function AudioScope.getTriggerOutputRight
|
||||
* @returns {number[]}
|
||||
* @returns {number[]} Right trigger output.
|
||||
*/
|
||||
QVector<int> getTriggerOutputRight() { return _triggerOutputRightData; };
|
||||
|
||||
/**jsdoc
|
||||
* Set local echo.
|
||||
* @function AudioScope.setLocalEcho
|
||||
* @parm {boolean} localEcho
|
||||
* @parm {boolean} localEcho - Local echo.
|
||||
*/
|
||||
void setLocalEcho(bool localEcho);
|
||||
|
||||
/**jsdoc
|
||||
* Set server echo.
|
||||
* @function AudioScope.setServerEcho
|
||||
* @parm {boolean} serverEcho
|
||||
* @parm {boolean} serverEcho - Server echo.
|
||||
*/
|
||||
void setServerEcho(bool serverEcho);
|
||||
|
||||
signals:
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when pause changes.
|
||||
* @function AudioScope.pauseChanged
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void pauseChanged();
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when scope is triggered.
|
||||
* @function AudioScope.triggered
|
||||
* @returns {Signal}
|
||||
*/
|
||||
|
|
|
@ -48,7 +48,6 @@
|
|||
#include <recording/Clip.h>
|
||||
#include <recording/Frame.h>
|
||||
#include <RecordingScriptingInterface.h>
|
||||
#include <trackers/FaceTracker.h>
|
||||
#include <RenderableModelEntityItem.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_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_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);
|
||||
}
|
||||
|
@ -733,7 +733,7 @@ void MyAvatar::update(float deltaTime) {
|
|||
_physicsSafetyPending = getCollisionsEnabled();
|
||||
_characterController.recomputeFlying(); // In case we've gone to into the sky.
|
||||
}
|
||||
if (_goToFeetAjustment && _skeletonModelLoaded) {
|
||||
if (_goToFeetAjustment && _skeletonModel->isLoaded()) {
|
||||
auto feetAjustment = getWorldPosition() - getWorldFeetPosition();
|
||||
_goToPosition = getWorldPosition() + feetAjustment;
|
||||
setWorldPosition(_goToPosition);
|
||||
|
@ -749,7 +749,6 @@ void MyAvatar::update(float deltaTime) {
|
|||
|
||||
Head* head = getHead();
|
||||
head->relax(deltaTime);
|
||||
updateFromTrackers(deltaTime);
|
||||
|
||||
if (getIsInWalkingState() && glm::length(getControllerPoseInAvatarFrame(controller::Action::HEAD).getVelocity()) < DEFAULT_AVATAR_WALK_SPEED_THRESHOLD) {
|
||||
setIsInWalkingState(false);
|
||||
|
@ -782,18 +781,6 @@ void MyAvatar::update(float deltaTime) {
|
|||
emit energyChanged(currentEnergy);
|
||||
|
||||
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) {
|
||||
|
@ -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 {
|
||||
auto pose = getControllerPoseInAvatarFrame(controller::Action::LEFT_HAND);
|
||||
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 MY_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 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) {
|
||||
glm::vec3 myForward = _lookAtYaw * IDENTITY_FORWARD;
|
||||
if (_skeletonModel->isLoaded()) {
|
||||
myForward = getHeadJointFrontVector();
|
||||
}
|
||||
glm::vec3 myPosition = getHead()->getEyePosition();
|
||||
CameraMode mode = qApp->getCamera().getMode();
|
||||
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) {
|
||||
std::shared_ptr<Avatar> avatar = std::static_pointer_cast<Avatar>(avatarData);
|
||||
if (!avatar->isMyAvatar() && avatar->isInitialized()) {
|
||||
glm::vec3 otherForward = avatar->getHead()->getForwardDirection();
|
||||
glm::vec3 otherForward = avatar->getHeadJointFrontVector();
|
||||
glm::vec3 otherPosition = avatar->getHead()->getEyePosition();
|
||||
const float TIME_WITHOUT_TALKING_THRESHOLD = 1.0f;
|
||||
bool otherIsTalking = avatar->getHead()->getTimeWithoutTalking() <= TIME_WITHOUT_TALKING_THRESHOLD;
|
||||
|
@ -2284,7 +2220,9 @@ void MyAvatar::updateLookAtTargetAvatar() {
|
|||
AvatarHash hash = DependencyManager::get<AvatarManager>()->getHashCopy();
|
||||
|
||||
// determine what the best look at target for my avatar should be.
|
||||
computeMyLookAtTarget(hash);
|
||||
if (!_scriptControlsEyesLookAt) {
|
||||
computeMyLookAtTarget(hash);
|
||||
}
|
||||
|
||||
// snap look at position for avatars that are looking at me.
|
||||
snapOtherAvatarLookAtTargetsToMe(hash);
|
||||
|
@ -2496,7 +2434,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
|
||||
_headBoneSet.clear();
|
||||
_cauterizationNeedsUpdate = true;
|
||||
_skeletonModelLoaded = false;
|
||||
|
||||
std::shared_ptr<QMetaObject::Connection> skeletonConnection = std::make_shared<QMetaObject::Connection>();
|
||||
*skeletonConnection = QObject::connect(_skeletonModel.get(), &SkeletonModel::skeletonLoaded, [this, skeletonModelChangeCount, skeletonConnection]() {
|
||||
|
@ -2515,8 +2452,6 @@ void MyAvatar::setSkeletonModelURL(const QUrl& skeletonModelURL) {
|
|||
_fstAnimGraphOverrideUrl = _skeletonModel->getGeometry()->getAnimGraphOverrideUrl();
|
||||
initAnimGraph();
|
||||
initFlowFromFST();
|
||||
|
||||
_skeletonModelLoaded = true;
|
||||
}
|
||||
QObject::disconnect(*skeletonConnection);
|
||||
});
|
||||
|
@ -2633,6 +2568,8 @@ void MyAvatar::useFullAvatarURL(const QUrl& fullAvatarURL, const QString& modelN
|
|||
if (urlString.isEmpty() || (fullAvatarURL != getSkeletonModelURL())) {
|
||||
setSkeletonModelURL(fullAvatarURL);
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
const float MINIMUM_ROTATION_RECENTER_FILTER_LENGTH = 0.01f;
|
||||
_rotationRecenterFilterLength = std::max(MINIMUM_ROTATION_RECENTER_FILTER_LENGTH, length);
|
||||
|
@ -5212,10 +5124,9 @@ bool MyAvatar::isReadyForPhysics() const {
|
|||
|
||||
void MyAvatar::setSprintMode(bool sprint) {
|
||||
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;
|
||||
} 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);
|
||||
}
|
||||
|
||||
void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera) {
|
||||
void MyAvatar::updateEyesLookAtPosition(float deltaTime) {
|
||||
|
||||
updateLookAtTargetAvatar();
|
||||
|
||||
bool isLookingAtSomeone = false;
|
||||
glm::vec3 lookAtSpot;
|
||||
|
||||
const MyHead* myHead = getMyHead();
|
||||
|
@ -6649,6 +6559,13 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera)
|
|||
} else {
|
||||
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 {
|
||||
controller::Pose leftEyePose = getControllerPoseInAvatarFrame(controller::Action::LEFT_EYE);
|
||||
controller::Pose rightEyePose = getControllerPoseInAvatarFrame(controller::Action::RIGHT_EYE);
|
||||
|
@ -6677,7 +6594,6 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera)
|
|||
avatar && avatar->getLookAtSnappingEnabled() && getLookAtSnappingEnabled();
|
||||
if (haveLookAtCandidate && mutualLookAtSnappingEnabled) {
|
||||
// If I am looking at someone else, look directly at one of their eyes
|
||||
isLookingAtSomeone = true;
|
||||
auto lookingAtHead = avatar->getHead();
|
||||
|
||||
const float MAXIMUM_FACE_ANGLE = 65.0f * RADIANS_PER_DEGREE;
|
||||
|
@ -6711,28 +6627,14 @@ void MyAvatar::updateLookAtPosition(FaceTracker* faceTracker, Camera& myCamera)
|
|||
if (headPose.isValid()) {
|
||||
lookAtSpot = transformPoint(headPose.getMatrix(), glm::vec3(0.0f, 0.0f, TREE_SCALE));
|
||||
} else {
|
||||
lookAtSpot = myHead->getEyePosition() +
|
||||
(getHead()->getFinalOrientationInWorldFrame() * glm::vec3(0.0f, 0.0f, -TREE_SCALE));
|
||||
lookAtSpot = _shouldTurnToFaceCamera ?
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -6769,9 +6671,18 @@ glm::vec3 MyAvatar::aimToBlendValues(const glm::vec3& aimVector, const glm::quat
|
|||
}
|
||||
|
||||
void MyAvatar::resetHeadLookAt() {
|
||||
if (_skeletonModelLoaded) {
|
||||
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
|
||||
HEAD_BLEND_LINEAR_ALPHA_NAME, HEAD_ALPHA_BLENDING);
|
||||
if (_skeletonModel->isLoaded()) {
|
||||
if (isSeated()) {
|
||||
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6784,17 +6695,26 @@ void MyAvatar::resetLookAtRotation(const glm::vec3& avatarPosition, const glm::q
|
|||
resetHeadLookAt();
|
||||
}
|
||||
|
||||
void MyAvatar::updateHeadLookAt(float deltaTime) {
|
||||
if (_skeletonModelLoaded) {
|
||||
void MyAvatar::updateHeadLookAt(float deltaTime) {
|
||||
if (_skeletonModel->isLoaded()) {
|
||||
glm::vec3 lookAtTarget = _scriptControlsHeadLookAt ? _lookAtScriptTarget : _lookAtCameraTarget;
|
||||
glm::vec3 aimVector = lookAtTarget - getDefaultEyePosition();
|
||||
glm::vec3 lookAtBlend = MyAvatar::aimToBlendValues(aimVector, getWorldOrientation());
|
||||
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend,
|
||||
HEAD_BLEND_LINEAR_ALPHA_NAME, HEAD_ALPHA_BLENDING);
|
||||
if (isSeated()) {
|
||||
_skeletonModel->getRig().setDirectionalBlending(HEAD_BLEND_DIRECTIONAL_ALPHA_NAME, lookAtBlend,
|
||||
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) {
|
||||
_scriptHeadControlTimer += deltaTime;
|
||||
if (_scriptHeadControlTimer > MAX_LOOK_AT_TIME_SCRIPT_CONTROL) {
|
||||
if (_scriptHeadControlTimer >= MAX_LOOK_AT_TIME_SCRIPT_CONTROL) {
|
||||
_scriptHeadControlTimer = 0.0f;
|
||||
_scriptControlsHeadLookAt = false;
|
||||
_lookAtCameraTarget = _lookAtScriptTarget;
|
||||
|
@ -6815,6 +6735,25 @@ void MyAvatar::setHeadLookAt(const glm::vec3& 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 avatarUp = getWorldOrientation() * Vectors::UP;
|
||||
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));
|
||||
return result;
|
||||
}
|
||||
if (_skeletonModelLoaded && _pointAtActive) {
|
||||
if (_skeletonModel->isLoaded() && _pointAtActive) {
|
||||
glm::vec3 aimVector = pointAtTarget - getJointPosition(POINT_REF_JOINT_NAME);
|
||||
_isPointTargetValid = glm::dot(aimVector, getWorldOrientation() * Vectors::FRONT) > 0.0f;
|
||||
if (_isPointTargetValid) {
|
||||
|
@ -6902,9 +6841,8 @@ bool MyAvatar::setPointAt(const glm::vec3& pointAtTarget) {
|
|||
}
|
||||
|
||||
void MyAvatar::resetPointAt() {
|
||||
if (_skeletonModelLoaded) {
|
||||
if (_skeletonModel->isLoaded()) {
|
||||
_skeletonModel->getRig().setDirectionalBlending(POINT_BLEND_DIRECTIONAL_ALPHA_NAME, glm::vec3(),
|
||||
POINT_BLEND_LINEAR_ALPHA_NAME, POINT_ALPHA_BLENDING);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,7 +34,6 @@
|
|||
#include "AtRestDetector.h"
|
||||
#include "MyCharacterController.h"
|
||||
#include "RingBufferHistory.h"
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
|
||||
class AvatarActionHold;
|
||||
class ModelItemID;
|
||||
|
@ -184,12 +183,6 @@ class MyAvatar : public Avatar {
|
|||
* property value is <code>audioListenerModeCustom</code>.
|
||||
* @property {Quat} customListenOrientation=Quat.IDENTITY - The listening orientation used when the
|
||||
* <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
|
||||
* 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>.
|
||||
|
@ -275,7 +268,7 @@ class MyAvatar : public Avatar {
|
|||
* @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 {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).
|
||||
* 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
|
||||
|
@ -312,7 +305,10 @@ class MyAvatar : public Avatar {
|
|||
* @borrows Avatar.setAttachmentsVariant as setAttachmentsVariant
|
||||
* @borrows Avatar.updateAvatarEntity as updateAvatarEntity
|
||||
* @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.getAttachmentData as getAttachmentData
|
||||
* @borrows Avatar.setAttachmentData as setAttachmentData
|
||||
|
@ -359,10 +355,6 @@ class MyAvatar : public Avatar {
|
|||
Q_PROPERTY(AudioListenerMode audioListenerModeCustom READ getAudioListenerModeCustom)
|
||||
Q_PROPERTY(glm::vec3 customListenPosition READ getCustomListenPosition WRITE setCustomListenPosition)
|
||||
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 rotationThreshold READ getRotationThreshold WRITE setRotationThreshold)
|
||||
Q_PROPERTY(bool enableStepResetRotation READ getEnableStepResetRotation WRITE setEnableStepResetRotation)
|
||||
|
@ -1765,10 +1757,38 @@ public:
|
|||
/**jsdoc
|
||||
* Returns the current head look at target point in world coordinates.
|
||||
* @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; }
|
||||
|
||||
/**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
|
||||
* Aims the pointing directional blending towards the provided target point.
|
||||
* The "point" reaction should be triggered before using this method.
|
||||
|
@ -1906,7 +1926,7 @@ public:
|
|||
bool getFlowActive() 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
|
||||
// also clears internal reaction triggers
|
||||
|
@ -2435,10 +2455,17 @@ signals:
|
|||
/**jsdoc
|
||||
* Triggered when the avatar's model finishes loading.
|
||||
* @function MyAvatar.onLoadComplete
|
||||
* @returns {Signal}
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void onLoadComplete();
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the avatar's model has failed to load.
|
||||
* @function MyAvatar.onLoadFailed
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void onLoadFailed();
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when your avatar changes from being active to being away.
|
||||
* @function MyAvatar.wentAway
|
||||
|
@ -2551,20 +2578,11 @@ private:
|
|||
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking) override;
|
||||
|
||||
void simulate(float deltaTime, bool inView) override;
|
||||
void updateFromTrackers(float deltaTime);
|
||||
void saveAvatarUrl();
|
||||
virtual void render(RenderArgs* renderArgs) override;
|
||||
virtual bool shouldRenderHead(const RenderArgs* renderArgs) const override;
|
||||
void setShouldRenderLocally(bool shouldRender) { _shouldRender = shouldRender; setEnableMeshVisible(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);
|
||||
float getRotationRecenterFilterLength() const { return _rotationRecenterFilterLength; }
|
||||
void setRotationThreshold(float angleRadians);
|
||||
|
@ -2666,6 +2684,9 @@ private:
|
|||
|
||||
eyeContactTarget _eyeContactTarget;
|
||||
float _eyeContactTargetTimer { 0.0f };
|
||||
ThreadSafeValueCache<glm::vec3> _eyesLookAtTarget { glm::vec3() };
|
||||
bool _scriptControlsEyesLookAt{ false };
|
||||
float _scriptEyesControlTimer{ 0.0f };
|
||||
|
||||
glm::vec3 _trackedHeadPosition;
|
||||
|
||||
|
@ -2899,7 +2920,6 @@ private:
|
|||
|
||||
bool _haveReceivedHeightLimitsFromDomain { false };
|
||||
int _disableHandTouchCount { 0 };
|
||||
bool _skeletonModelLoaded { false };
|
||||
bool _reloadAvatarEntityDataFromSettings { true };
|
||||
|
||||
TimePoint _nextTraitsSendWindow;
|
||||
|
|
|
@ -14,15 +14,80 @@
|
|||
#include <NodeList.h>
|
||||
#include <recording/Deck.h>
|
||||
#include <Rig.h>
|
||||
#include <trackers/FaceTracker.h>
|
||||
#include <FaceshiftConstants.h>
|
||||
#include <BlendshapeConstants.h>
|
||||
|
||||
#include "devices/DdeFaceTracker.h"
|
||||
#include "Application.h"
|
||||
#include "MyAvatar.h"
|
||||
|
||||
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) {
|
||||
}
|
||||
|
||||
|
@ -46,36 +111,57 @@ void MyHead::simulate(float deltaTime) {
|
|||
auto player = DependencyManager::get<recording::Deck>();
|
||||
// Only use face trackers when not playing back a recording.
|
||||
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>();
|
||||
|
||||
// if input system has control over blink blendshapes
|
||||
bool eyeLidsTracked =
|
||||
userInputMapper->getActionStateValid(controller::Action::LEFT_EYE_BLINK) &&
|
||||
userInputMapper->getActionStateValid(controller::Action::RIGHT_EYE_BLINK);
|
||||
setFaceTrackerConnected(eyeLidsTracked);
|
||||
if (eyeLidsTracked) {
|
||||
float leftEyeBlink = userInputMapper->getActionState(controller::Action::LEFT_EYE_BLINK);
|
||||
float rightEyeBlink = userInputMapper->getActionState(controller::Action::RIGHT_EYE_BLINK);
|
||||
_blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2));
|
||||
_blendshapeCoefficients[EYE_BLINK_INDICES[0]] = leftEyeBlink;
|
||||
_blendshapeCoefficients[EYE_BLINK_INDICES[1]] = rightEyeBlink;
|
||||
} else {
|
||||
const float FULLY_OPEN = 0.0f;
|
||||
_blendshapeCoefficients.resize(std::max(_blendshapeCoefficients.size(), 2));
|
||||
_blendshapeCoefficients[EYE_BLINK_INDICES[0]] = FULLY_OPEN;
|
||||
_blendshapeCoefficients[EYE_BLINK_INDICES[1]] = FULLY_OPEN;
|
||||
userInputMapper->getActionStateValid(controller::Action::EYEBLINK_L) ||
|
||||
userInputMapper->getActionStateValid(controller::Action::EYEBLINK_R);
|
||||
|
||||
// if input system has control over the brows.
|
||||
bool browsTracked =
|
||||
userInputMapper->getActionStateValid(controller::Action::BROWSD_L) ||
|
||||
userInputMapper->getActionStateValid(controller::Action::BROWSD_R) ||
|
||||
userInputMapper->getActionStateValid(controller::Action::BROWSU_L) ||
|
||||
userInputMapper->getActionStateValid(controller::Action::BROWSU_R) ||
|
||||
userInputMapper->getActionStateValid(controller::Action::BROWSU_C);
|
||||
|
||||
// if input system has control of mouth
|
||||
bool mouthTracked =
|
||||
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);
|
||||
|
|
|
@ -112,9 +112,13 @@ static AnimPose computeHipsInSensorFrame(MyAvatar* myAvatar, bool isFlying) {
|
|||
void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) {
|
||||
const HFMModel& hfmModel = getHFMModel();
|
||||
|
||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||
assert(myAvatar);
|
||||
|
||||
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;
|
||||
if (eyePosesValid) {
|
||||
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());
|
||||
}
|
||||
|
||||
MyAvatar* myAvatar = static_cast<MyAvatar*>(_owningAvatar);
|
||||
assert(myAvatar);
|
||||
|
||||
Rig::ControllerParameters params;
|
||||
|
||||
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);
|
||||
_jointDataSimulationRate.increment();
|
||||
|
||||
head->simulate(deltaTime);
|
||||
_skeletonModel->simulate(deltaTime, true);
|
||||
|
||||
locationChanged(); // joints changed, so if there are any children, update them.
|
||||
|
@ -277,9 +278,11 @@ void OtherAvatar::simulate(float deltaTime, bool inView) {
|
|||
headPosition = getWorldPosition();
|
||||
}
|
||||
head->setPosition(headPosition);
|
||||
} else {
|
||||
head->simulate(deltaTime);
|
||||
_skeletonModel->simulate(deltaTime, false);
|
||||
}
|
||||
head->setScale(getModelScale());
|
||||
head->simulate(deltaTime);
|
||||
relayJointDataToChildren();
|
||||
} else {
|
||||
// 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
|
||||
* be configured to generate events on entities and overlays intersected.
|
||||
*
|
||||
* <p class="important">Deprecated: This API is deprecated. Use {@link Pointers} instead.
|
||||
*
|
||||
* @namespace LaserPointers
|
||||
*
|
||||
* @deprecated This API is deprecated and will be removed. Use {@link Pointers} instead.
|
||||
*
|
||||
* @hifi-interface
|
||||
* @hifi-client-entity
|
||||
* @hifi-avatar
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include <algorithm>
|
||||
|
||||
#include <shared/QtHelpers.h>
|
||||
#include <plugins/PluginManager.h>
|
||||
#include <plugins/DisplayPlugin.h>
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -44,7 +45,8 @@ enum AudioDeviceRole {
|
|||
SelectedDesktopRole,
|
||||
SelectedHMDRole,
|
||||
PeakRole,
|
||||
InfoRole
|
||||
InfoRole,
|
||||
TypeRole
|
||||
};
|
||||
|
||||
QHash<int, QByteArray> AudioDeviceList::_roles {
|
||||
|
@ -52,7 +54,8 @@ QHash<int, QByteArray> AudioDeviceList::_roles {
|
|||
{ SelectedDesktopRole, "selectedDesktop" },
|
||||
{ SelectedHMDRole, "selectedHMD" },
|
||||
{ PeakRole, "peak" },
|
||||
{ InfoRole, "info" }
|
||||
{ InfoRole, "info" },
|
||||
{ TypeRole, "type"}
|
||||
};
|
||||
|
||||
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);
|
||||
if (setting.isSet()) {
|
||||
deviceName = setting.get();
|
||||
} else if (hmd) {
|
||||
if (mode == QAudio::AudioInput) {
|
||||
deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioInDevice();
|
||||
} else { // if (_mode == QAudio::AudioOutput)
|
||||
deviceName = qApp->getActiveDisplayPlugin()->getPreferredAudioOutDevice();
|
||||
}
|
||||
} else {
|
||||
deviceName = HifiAudioDeviceInfo::DEFAULT_DEVICE_NAME;
|
||||
}
|
||||
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 };
|
||||
|
||||
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;
|
||||
} else if (role == InfoRole) {
|
||||
return QVariant::fromValue<HifiAudioDeviceInfo>(_devices.at(index.row())->info);
|
||||
} else if (role == TypeRole) {
|
||||
return _devices.at(index.row())->type;
|
||||
} else {
|
||||
return QVariant();
|
||||
}
|
||||
|
@ -166,8 +186,8 @@ void AudioDeviceList::resetDevice(bool contextIsHMD) {
|
|||
QString deviceName = getTargetDevice(contextIsHMD, _mode);
|
||||
// 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
|
||||
QMetaObject::invokeMethod(client, "switchAudioDevice",
|
||||
Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName));
|
||||
QMetaObject::invokeMethod(client, "switchAudioDevice",
|
||||
Q_ARG(QAudio::Mode, _mode), Q_ARG(QString, deviceName), Q_ARG(bool, contextIsHMD));
|
||||
|
||||
#if 0
|
||||
bool switchResult = false;
|
||||
|
@ -258,20 +278,28 @@ std::shared_ptr<scripting::AudioDevice> getSimilarDevice(const QString& deviceNa
|
|||
return devices[minDistanceIndex];
|
||||
}
|
||||
|
||||
void AudioDeviceList::onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices) {
|
||||
|
||||
void AudioDeviceList::onDevicesChanged(QAudio::Mode mode, const QList<HifiAudioDeviceInfo>& devices) {
|
||||
beginResetModel();
|
||||
|
||||
QList<std::shared_ptr<AudioDevice>> newDevices;
|
||||
bool hmdIsSelected = false;
|
||||
bool desktopIsSelected = false;
|
||||
|
||||
foreach(const HifiAudioDeviceInfo& deviceInfo, devices) {
|
||||
for (bool isHMD : {false, true}) {
|
||||
auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName;
|
||||
if (deviceInfo.deviceName() == backupSelectedDeviceName) {
|
||||
HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
|
||||
selectedDevice = deviceInfo;
|
||||
backupSelectedDeviceName.clear();
|
||||
|
||||
checkHmdDefaultsChange(mode);
|
||||
if (!_backupSelectedDesktopDeviceName.isEmpty() && !_backupSelectedHMDDeviceName.isEmpty()) {
|
||||
foreach(const HifiAudioDeviceInfo& deviceInfo, devices) {
|
||||
for (bool isHMD : {false, true}) {
|
||||
auto& backupSelectedDeviceName = isHMD ? _backupSelectedHMDDeviceName : _backupSelectedDesktopDeviceName;
|
||||
if (deviceInfo.deviceName() == backupSelectedDeviceName) {
|
||||
if (isHMD && deviceInfo.getDeviceType() != HifiAudioDeviceInfo::desktop) {
|
||||
_selectedHMDDevice= deviceInfo;
|
||||
backupSelectedDeviceName.clear();
|
||||
} else if (!isHMD && deviceInfo.getDeviceType() != HifiAudioDeviceInfo::hmd) {
|
||||
_selectedDesktopDevice = deviceInfo;
|
||||
backupSelectedDeviceName.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -281,10 +309,18 @@ void AudioDeviceList::onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices
|
|||
device.info = deviceInfo;
|
||||
|
||||
if (deviceInfo.isDefault()) {
|
||||
if (deviceInfo.getMode() == QAudio::AudioInput) {
|
||||
device.display = "Computer's default microphone (recommended)";
|
||||
} else {
|
||||
device.display = "Computer's default audio (recommended)";
|
||||
if (deviceInfo.getDeviceType() == HifiAudioDeviceInfo::desktop) {
|
||||
if (deviceInfo.getMode() == QAudio::AudioInput) {
|
||||
device.display = "Computer's default microphone (recommended)";
|
||||
} else {
|
||||
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 {
|
||||
device.display = device.info.deviceName()
|
||||
|
@ -292,6 +328,19 @@ void AudioDeviceList::onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices
|
|||
.remove("Device")
|
||||
.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}) {
|
||||
HifiAudioDeviceInfo& selectedDevice = isHMD ? _selectedHMDDevice : _selectedDesktopDevice;
|
||||
|
@ -302,8 +351,14 @@ void AudioDeviceList::onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices
|
|||
}
|
||||
else {
|
||||
//no selected device for context. fallback to saved
|
||||
const QString& savedDeviceName = isHMD ? _hmdSavedDeviceName : _desktopSavedDeviceName;
|
||||
isSelected = (device.info.deviceName() == savedDeviceName);
|
||||
QString& savedDeviceName = isHMD ? _hmdSavedDeviceName : _desktopSavedDeviceName;
|
||||
|
||||
if (device.info.deviceName() == savedDeviceName) {
|
||||
if ((isHMD && device.info.getDeviceType() != HifiAudioDeviceInfo::desktop) ||
|
||||
(!isHMD && device.info.getDeviceType() != HifiAudioDeviceInfo::hmd)) {
|
||||
isSelected = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSelected) {
|
||||
|
@ -385,6 +440,9 @@ AudioDevices::AudioDevices(bool& contextIsHMD) : _contextIsHMD(contextIsHMD) {
|
|||
connect(client, &AudioClient::deviceChanged, this, &AudioDevices::onDeviceChanged, Qt::QueuedConnection);
|
||||
connect(client, &AudioClient::devicesChanged, this, &AudioDevices::onDevicesChanged, Qt::QueuedConnection);
|
||||
connect(client, &AudioClient::peakValueListChanged, &_inputs, &AudioInputDeviceList::onPeakValueListChanged, Qt::QueuedConnection);
|
||||
|
||||
checkHmdDefaultsChange(QAudio::AudioInput);
|
||||
checkHmdDefaultsChange(QAudio::AudioOutput);
|
||||
|
||||
_inputs.onDeviceChanged(client->getActiveAudioDevice(QAudio::AudioInput), 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>& devicesOutput = client->getAudioDevices(QAudio::AudioOutput);
|
||||
|
||||
//setup devices
|
||||
_inputs.onDevicesChanged(devicesInput);
|
||||
_outputs.onDevicesChanged(devicesOutput);
|
||||
if (devicesInput.size() > 0 && devicesOutput.size() > 0) {
|
||||
//setup devices
|
||||
_inputs.onDevicesChanged(QAudio::AudioInput, devicesInput);
|
||||
_outputs.onDevicesChanged(QAudio::AudioOutput, devicesOutput);
|
||||
}
|
||||
}
|
||||
|
||||
AudioDevices::~AudioDevices() {}
|
||||
|
@ -494,14 +554,14 @@ void AudioDevices::onDevicesChanged(QAudio::Mode mode, const QList<HifiAudioDevi
|
|||
|
||||
//set devices for both contexts
|
||||
if (mode == QAudio::AudioInput) {
|
||||
_inputs.onDevicesChanged(devices);
|
||||
_inputs.onDevicesChanged(mode, devices);
|
||||
|
||||
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
|
||||
connect(&_inputs, &AudioDeviceList::selectedDevicePlugged, this, &AudioDevices::chooseInputDevice);
|
||||
});
|
||||
} else { // if (mode == QAudio::AudioOutput)
|
||||
_outputs.onDevicesChanged(devices);
|
||||
_outputs.onDevicesChanged(mode, devices);
|
||||
|
||||
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
|
||||
|
|
|
@ -29,6 +29,7 @@ public:
|
|||
QString display;
|
||||
bool selectedDesktop { false };
|
||||
bool selectedHMD { false };
|
||||
QString type;
|
||||
};
|
||||
|
||||
class AudioDeviceList : public QAbstractListModel {
|
||||
|
@ -57,7 +58,7 @@ signals:
|
|||
|
||||
protected slots:
|
||||
void onDeviceChanged(const HifiAudioDeviceInfo& device, bool isHMD);
|
||||
void onDevicesChanged(const QList<HifiAudioDeviceInfo>& devices);
|
||||
void onDevicesChanged(QAudio::Mode mode, const QList<HifiAudioDeviceInfo>& devices);
|
||||
|
||||
protected:
|
||||
friend class AudioDevices;
|
||||
|
|
|
@ -32,106 +32,187 @@ void MenuScriptingInterface::menuItemTriggered() {
|
|||
}
|
||||
|
||||
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) {
|
||||
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) {
|
||||
Menu* menuInstance = Menu::getInstance();
|
||||
if (!menuInstance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QThread::currentThread() == qApp->thread()) {
|
||||
Menu* menuInstance = Menu::getInstance();
|
||||
return menuInstance && menuInstance->menuExists(menu);
|
||||
}
|
||||
|
||||
bool result { false };
|
||||
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuExists",
|
||||
|
||||
BLOCKING_INVOKE_METHOD(menuInstance, "menuExists",
|
||||
Q_RETURN_ARG(bool, result),
|
||||
Q_ARG(const QString&, menu));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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&, 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&, separatorName));
|
||||
}
|
||||
|
||||
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) {
|
||||
Menu* menuInstance = Menu::getInstance();
|
||||
if (!menuInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
Menu* menuInstance = Menu::getInstance();
|
||||
if (!menuInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
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) {
|
||||
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&, menuitem));
|
||||
};
|
||||
|
||||
bool MenuScriptingInterface::menuItemExists(const QString& menu, const QString& menuitem) {
|
||||
Menu* menuInstance = Menu::getInstance();
|
||||
if (!menuInstance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QThread::currentThread() == qApp->thread()) {
|
||||
Menu* menuInstance = Menu::getInstance();
|
||||
return menuInstance && menuInstance->menuItemExists(menu, menuitem);
|
||||
}
|
||||
|
||||
bool result { false };
|
||||
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "menuItemExists",
|
||||
|
||||
BLOCKING_INVOKE_METHOD(menuInstance, "menuItemExists",
|
||||
Q_RETURN_ARG(bool, result),
|
||||
Q_ARG(const QString&, menu),
|
||||
Q_ARG(const QString&, menuitem));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
bool MenuScriptingInterface::isOptionChecked(const QString& menuOption) {
|
||||
Menu* menuInstance = Menu::getInstance();
|
||||
if (!menuInstance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QThread::currentThread() == qApp->thread()) {
|
||||
Menu* menuInstance = Menu::getInstance();
|
||||
return menuInstance && menuInstance->isOptionChecked(menuOption);
|
||||
}
|
||||
|
||||
bool result { false };
|
||||
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isOptionChecked",
|
||||
|
||||
BLOCKING_INVOKE_METHOD(menuInstance, "isOptionChecked",
|
||||
Q_RETURN_ARG(bool, result),
|
||||
Q_ARG(const QString&, menuOption));
|
||||
return result;
|
||||
}
|
||||
|
||||
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(bool, isChecked));
|
||||
}
|
||||
|
||||
bool MenuScriptingInterface::isMenuEnabled(const QString& menuOption) {
|
||||
Menu* menuInstance = Menu::getInstance();
|
||||
if (!menuInstance) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (QThread::currentThread() == qApp->thread()) {
|
||||
Menu* menuInstance = Menu::getInstance();
|
||||
return menuInstance && menuInstance->isMenuEnabled(menuOption);
|
||||
}
|
||||
|
||||
bool result { false };
|
||||
BLOCKING_INVOKE_METHOD(Menu::getInstance(), "isMenuEnabled",
|
||||
|
||||
BLOCKING_INVOKE_METHOD(menuInstance, "isMenuEnabled",
|
||||
Q_RETURN_ARG(bool, result),
|
||||
Q_ARG(const QString&, menuOption));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
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(bool, isChecked));
|
||||
}
|
||||
|
||||
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 <SettingHandle.h>
|
||||
#include <trackers/FaceTracker.h>
|
||||
#include <UsersScriptingInterface.h>
|
||||
|
||||
#include "Application.h"
|
||||
|
@ -76,8 +75,6 @@ void AvatarInputs::update() {
|
|||
return;
|
||||
}
|
||||
|
||||
AI_UPDATE(cameraEnabled, !Menu::getInstance()->isOptionChecked(MenuOption::NoFaceTracking));
|
||||
AI_UPDATE(cameraMuted, Menu::getInstance()->isOptionChecked(MenuOption::MuteFaceTracking));
|
||||
AI_UPDATE(isHMD, qApp->isHMDMode());
|
||||
}
|
||||
|
||||
|
@ -103,13 +100,6 @@ bool AvatarInputs::getIgnoreRadiusEnabled() const {
|
|||
return DependencyManager::get<NodeList>()->getIgnoreRadiusEnabled();
|
||||
}
|
||||
|
||||
void AvatarInputs::toggleCameraMute() {
|
||||
FaceTracker* faceTracker = qApp->getSelectedFaceTracker();
|
||||
if (faceTracker) {
|
||||
faceTracker->toggleMute();
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarInputs::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
|
||||
* disabled.
|
||||
* <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),
|
||||
* <code>false</code> it if isn't.
|
||||
* <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
|
||||
* is disabled.
|
||||
* <em>Read-only.</em>
|
||||
|
@ -51,8 +51,6 @@ class AvatarInputs : public QObject {
|
|||
* it is hidden.
|
||||
*/
|
||||
|
||||
AI_PROPERTY(bool, cameraEnabled, false)
|
||||
AI_PROPERTY(bool, cameraMuted, false)
|
||||
AI_PROPERTY(bool, isHMD, false)
|
||||
|
||||
Q_PROPERTY(bool showAudioTools READ showAudioTools WRITE setShowAudioTools NOTIFY showAudioToolsChanged)
|
||||
|
@ -99,19 +97,17 @@ signals:
|
|||
|
||||
/**jsdoc
|
||||
* 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
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void cameraEnabledChanged();
|
||||
|
||||
/**jsdoc
|
||||
* 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
|
||||
* @returns {Signal}
|
||||
*/
|
||||
void cameraMutedChanged();
|
||||
|
||||
/**jsdoc
|
||||
* Triggered when the display mode changes between desktop and HMD.
|
||||
|
@ -185,10 +181,9 @@ protected:
|
|||
|
||||
/**jsdoc
|
||||
* 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
|
||||
*/
|
||||
Q_INVOKABLE void toggleCameraMute();
|
||||
|
||||
private:
|
||||
void onAvatarEnteredIgnoreRadius();
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
|
||||
#include <AudioClient.h>
|
||||
#include <avatar/AvatarManager.h>
|
||||
#include <devices/DdeFaceTracker.h>
|
||||
#include <ScriptEngines.h>
|
||||
#include <OffscreenUi.h>
|
||||
#include <Preferences.h>
|
||||
|
@ -58,18 +57,19 @@ void setupPreferences() {
|
|||
static const QString GRAPHICS_QUALITY { "Graphics Quality" };
|
||||
{
|
||||
auto getter = []()->float {
|
||||
return DependencyManager::get<LODManager>()->getWorldDetailQuality();
|
||||
return (int)DependencyManager::get<LODManager>()->getWorldDetailQuality();
|
||||
};
|
||||
|
||||
auto setter = [](float value) {
|
||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(value);
|
||||
auto setter = [](int value) {
|
||||
DependencyManager::get<LODManager>()->setWorldDetailQuality(static_cast<WorldDetailQuality>(value));
|
||||
};
|
||||
|
||||
auto wodSlider = new SliderPreference(GRAPHICS_QUALITY, "World Detail", getter, setter);
|
||||
wodSlider->setMin(0.25f);
|
||||
wodSlider->setMax(0.75f);
|
||||
wodSlider->setStep(0.25f);
|
||||
preferences->addPreference(wodSlider);
|
||||
auto wodButtons = new RadioButtonsPreference(GRAPHICS_QUALITY, "World Detail", getter, setter);
|
||||
QStringList items;
|
||||
items << "Low World Detail" << "Medium World Detail" << "High World Detail";
|
||||
wodButtons->setHeading("World Detail");
|
||||
wodButtons->setItems(items);
|
||||
preferences->addPreference(wodButtons);
|
||||
|
||||
auto getterShadow = []()->bool {
|
||||
auto menu = Menu::getInstance();
|
||||
|
@ -295,22 +295,6 @@ void setupPreferences() {
|
|||
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" };
|
||||
{
|
||||
auto getter = [myAvatar]()->bool { return myAvatar->getAllowTeleporting(); };
|
||||
|
|
|
@ -227,6 +227,9 @@
|
|||
<pointsize>-1</pointsize>
|
||||
</font>
|
||||
</property>
|
||||
<property name="textColor">
|
||||
<color>black</color>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::NoFrame</enum>
|
||||
</property>
|
||||
|
|
|
@ -12,7 +12,23 @@
|
|||
}
|
||||
@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[]) {
|
||||
redirectLogToDocuments();
|
||||
if (argc < 3) {
|
||||
NSLog(@"Error: wrong number of arguments");
|
||||
return 0;
|
||||
|
@ -50,6 +66,7 @@ int main(int argc, char const* argv[]) {
|
|||
options:NSWorkspaceLaunchNewInstance
|
||||
configuration:configuration
|
||||
error:&error];
|
||||
|
||||
if (error != nil) {
|
||||
NSLog(@"couldn't start launcher: %@", error);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|