From 6f81dd53f2cdbc730fc84eae648f72bea3807d56 Mon Sep 17 00:00:00 2001 From: Gerard Maguire Date: Tue, 8 Aug 2017 05:08:20 +0100 Subject: [PATCH 01/46] Fixed avatar rotation upon teleportation using Pal Added "VisitUserFromPAL" to LookupTrigger enum and bool "shouldMatchOrientation" to goToUser() --- interface/resources/qml/hifi/NameCard.qml | 4 +++- interface/resources/qml/hifi/Pal.qml | 2 +- libraries/networking/src/AddressManager.cpp | 18 ++++++++++++------ libraries/networking/src/AddressManager.h | 5 +++-- 4 files changed, 19 insertions(+), 10 deletions(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 65517f5a73..937c67d8ba 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -433,7 +433,7 @@ Item { anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter x: 240 onClicked: { - AddressManager.goToUser(thisNameCard.userName); + AddressManager.goToUser(thisNameCard.userName, false); UserActivityLogger.palAction("go_to_user", thisNameCard.userName); } } @@ -595,7 +595,9 @@ Item { // the avatar goes into fly mode rather than falling. However, that is not exposed to Javascript right now. // FIXME: it would be nice if this used the same teleport steps and smoothing as in the teleport.js script. // Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target. + // Position avatar 2 metres from the target in the direction that target avatar was facing. MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2})); + // Rotate avatar on Y axis to face target avatar. MyAvatar.orientation = Quat.multiply(avatar.orientation, {y: 1}); } } diff --git a/interface/resources/qml/hifi/Pal.qml b/interface/resources/qml/hifi/Pal.qml index c98cfba1ba..134fea5170 100644 --- a/interface/resources/qml/hifi/Pal.qml +++ b/interface/resources/qml/hifi/Pal.qml @@ -830,7 +830,7 @@ Rectangle { hoverEnabled: enabled enabled: connectionsNameCard.selected && pal.activeTab == "connectionsTab" onClicked: { - AddressManager.goToUser(model.userName); + AddressManager.goToUser(model.userName, false); UserActivityLogger.palAction("go_to_user", model.userName); } onEntered: connectionsLocationData.color = hifi.colors.blueHighlight; diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 99e1962387..588df71e41 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -665,6 +665,11 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should } } + if (trigger == LookupTrigger::VisitUserFromPAL) { + qCDebug(networking) << "trigger is VisitUserFromPAL -- applying Quat.cancelOutRollAndPitch"; + newOrientation = cancelOutRollAndPitch(newOrientation); + } + emit locationChangeRequired(newPosition, orientationChanged, newOrientation, shouldFace); } else { @@ -729,13 +734,14 @@ bool AddressManager::setDomainInfo(const QString& hostname, quint16 port, Lookup return hostChanged; } -void AddressManager::goToUser(const QString& username) { +void AddressManager::goToUser(const QString& username, bool shouldMatchOrientation) { QString formattedUsername = QUrl::toPercentEncoding(username); - // for history storage handling we remember how this lookup was trigged - for a username it's always user input + // for history storage handling we remember how this lookup was triggered - for a username it's always user input QVariantMap requestParams; - requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast(LookupTrigger::UserInput)); - + requestParams.insert(LOOKUP_TRIGGER_KEY, static_cast( + shouldMatchOrientation ? LookupTrigger::UserInput : LookupTrigger::VisitUserFromPAL + )); // this is a username - pull the captured name and lookup that user's location DependencyManager::get()->sendRequest(GET_USER_LOCATION.arg(formattedUsername), AccountManagerAuth::Optional, @@ -841,8 +847,8 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { // and do not but it into the back stack _forwardStack.push(currentAddress()); } else { - if (trigger == LookupTrigger::UserInput) { - // anyime the user has manually looked up an address we know we should clear the forward stack + if (trigger == LookupTrigger::UserInput || trigger == LookupTrigger::VisitUserFromPAL) { + // anyime the user has actively triggered an address we know we should clear the forward stack _forwardStack.clear(); emit goForwardPossible(false); diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index 83eedfc82f..98b2048601 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -55,7 +55,8 @@ public: DomainPathResponse, Internal, AttemptedRefresh, - Suggestions + Suggestions, + VisitUserFromPAL }; bool isConnected(); @@ -95,7 +96,7 @@ public slots: void goToLocalSandbox(QString path = "", LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(SANDBOX_HIFI_ADDRESS + path, trigger); } void goToEntry(LookupTrigger trigger = LookupTrigger::StartupFromSettings) { handleUrl(DEFAULT_HIFI_ADDRESS, trigger); } - void goToUser(const QString& username); + void goToUser(const QString& username, bool shouldMatchOrientation = true); void refreshPreviousLookup(); From 356f055b53284ba22b0a7c0763452e9b89fee9ac Mon Sep 17 00:00:00 2001 From: Gerard Maguire Date: Tue, 8 Aug 2017 15:26:21 +0100 Subject: [PATCH 02/46] fixed missing bool --- interface/resources/qml/hifi/NameCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 937c67d8ba..78dff425c7 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -244,7 +244,7 @@ Item { enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "" && isPresent; hoverEnabled: enabled onClicked: { - goToUserInDomain(thisNameCard.uuid); + goToUserInDomain(thisNameCard.uuid, false); UserActivityLogger.palAction("go_to_user_in_domain", thisNameCard.uuid); } onEntered: { From 47b37ac04ef315bdf2d2cadec595fadaf4db9d0a Mon Sep 17 00:00:00 2001 From: Gerard Maguire Date: Tue, 8 Aug 2017 15:35:14 +0100 Subject: [PATCH 03/46] Bool --- interface/resources/qml/hifi/NameCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 78dff425c7..7e93d2c397 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -339,7 +339,7 @@ Item { enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "" && isPresent; hoverEnabled: enabled onClicked: { - goToUserInDomain(thisNameCard.uuid); + goToUserInDomain(thisNameCard.uuid, false); UserActivityLogger.palAction("go_to_user_in_domain", thisNameCard.uuid); } onEntered: { From afb03b3d2d8ae66fa112f16d75aa7422828a9115 Mon Sep 17 00:00:00 2001 From: Gerard Maguire Date: Tue, 10 Oct 2017 16:19:31 +0100 Subject: [PATCH 04/46] Avatar pitch and roll cleared --- interface/resources/qml/hifi/NameCard.qml | 10 ++++++---- libraries/networking/src/AddressManager.cpp | 12 +++++------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index 7e93d2c397..b122f71760 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -244,7 +244,7 @@ Item { enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "" && isPresent; hoverEnabled: enabled onClicked: { - goToUserInDomain(thisNameCard.uuid, false); + goToUserInDomain(thisNameCard.uuid); UserActivityLogger.palAction("go_to_user_in_domain", thisNameCard.uuid); } onEntered: { @@ -339,7 +339,7 @@ Item { enabled: selected && pal.activeTab == "nearbyTab" && thisNameCard.userName !== "" && isPresent; hoverEnabled: enabled onClicked: { - goToUserInDomain(thisNameCard.uuid, false); + goToUserInDomain(thisNameCard.uuid); UserActivityLogger.palAction("go_to_user_in_domain", thisNameCard.uuid); } onEntered: { @@ -433,6 +433,7 @@ Item { anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter x: 240 onClicked: { + console.log("Vist user button clicked."); // Remove after debugging. AddressManager.goToUser(thisNameCard.userName, false); UserActivityLogger.palAction("go_to_user", thisNameCard.userName); } @@ -597,7 +598,8 @@ Item { // Note, however, that this script allows teleporting to a person in the air, while teleport.js is going to a grounded target. // Position avatar 2 metres from the target in the direction that target avatar was facing. MyAvatar.position = Vec3.sum(avatar.position, Vec3.multiplyQbyV(avatar.orientation, {x: 0, y: 0, z: -2})); - // Rotate avatar on Y axis to face target avatar. - MyAvatar.orientation = Quat.multiply(avatar.orientation, {y: 1}); + + // Rotate avatar on Y axis to face target avatar and cancel out any inherited roll and pitch. + MyAvatar.orientation = Quat.cancelOutRollAndPitch(Quat.multiply(avatar.orientation, {y: 1})); } } diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index 4030e2101b..772f9df2e1 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -668,13 +668,11 @@ bool AddressManager::handleViewpoint(const QString& viewpointString, bool should qCDebug(networking) << "Orientation parsed from lookup string is invalid. Will not use for location change."; } } - - if (trigger == LookupTrigger::VisitUserFromPAL) { - qCDebug(networking) << "trigger is VisitUserFromPAL -- applying Quat.cancelOutRollAndPitch"; - newOrientation = cancelOutRollAndPitch(newOrientation); - } - - emit locationChangeRequired(newPosition, orientationChanged, newOrientation, shouldFace); + + emit locationChangeRequired(newPosition, orientationChanged, + LookupTrigger::VisitUserFromPAL ? cancelOutRollAndPitch(newOrientation): newOrientation, + shouldFace + ); } else { qCDebug(networking) << "Could not jump to position from lookup string because it has an invalid value."; From 9345f4545f29cb24112bd76a012142a479769391 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 25 Oct 2017 13:35:42 -0700 Subject: [PATCH 05/46] sanitize the ice-server addresses before using --- domain-server/src/DomainServer.cpp | 37 +++++++++++------------------- domain-server/src/DomainServer.h | 2 -- 2 files changed, 14 insertions(+), 25 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 436f49c7ca..46eda163d8 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -754,26 +754,6 @@ void DomainServer::setupICEHeartbeatForFullNetworking() { void DomainServer::updateICEServerAddresses() { if (_iceAddressLookupID == INVALID_ICE_LOOKUP_ID) { _iceAddressLookupID = QHostInfo::lookupHost(_iceServerAddr, this, SLOT(handleICEHostInfo(QHostInfo))); - - // there seems to be a 5.9 bug where lookupHost never calls our slot - // so we add a single shot manual "timeout" to fire it off again if it hasn't called back yet - static const int ICE_ADDRESS_LOOKUP_TIMEOUT_MS = 5000; - QTimer::singleShot(ICE_ADDRESS_LOOKUP_TIMEOUT_MS, this, &DomainServer::timeoutICEAddressLookup); - } -} - -void DomainServer::timeoutICEAddressLookup() { - if (_iceAddressLookupID != INVALID_ICE_LOOKUP_ID) { - // we waited 5s and didn't hear back for our ICE DNS lookup - // so time that one out and kick off another - - qDebug() << "IP address lookup timed out for" << _iceServerAddr << "- retrying"; - - QHostInfo::abortHostLookup(_iceAddressLookupID); - - _iceAddressLookupID = INVALID_ICE_LOOKUP_ID; - - updateICEServerAddresses(); } } @@ -2799,9 +2779,20 @@ void DomainServer::handleKeypairChange() { void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) { // clear the ICE address lookup ID so that it can fire again - _iceAddressLookupID = -1; + _iceAddressLookupID = INVALID_ICE_LOOKUP_ID; - if (hostInfo.error() != QHostInfo::NoError) { + // enumerate the returned addresses and collect only valid IPv4 addresses + QList sanitizedAddresses = hostInfo.addresses(); + auto it = sanitizedAddresses.begin(); + while (it != sanitizedAddresses.end()) { + if (!it->isNull() && it->protocol() == QAbstractSocket::IPv4Protocol) { + ++it; + } else { + it = sanitizedAddresses.erase(it); + } + } + + if (hostInfo.error() != QHostInfo::NoError || sanitizedAddresses.empty()) { qWarning() << "IP address lookup failed for" << _iceServerAddr << ":" << hostInfo.errorString(); // if we don't have an ICE server to use yet, trigger a retry @@ -2814,7 +2805,7 @@ void DomainServer::handleICEHostInfo(const QHostInfo& hostInfo) { } else { int countBefore = _iceServerAddresses.count(); - _iceServerAddresses = hostInfo.addresses(); + _iceServerAddresses = sanitizedAddresses; if (countBefore == 0) { qInfo() << "Found" << _iceServerAddresses.count() << "ice-server IP addresses for" << _iceServerAddr; diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 52ac435517..982d39d046 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -116,8 +116,6 @@ private slots: void tokenGrantFinished(); void profileRequestFinished(); - void timeoutICEAddressLookup(); - signals: void iceServerChanged(); void userConnected(); From 60fbbe4bc0e4f725353202650b93d7f44675a6c1 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 25 Oct 2017 13:12:53 -0700 Subject: [PATCH 06/46] disable baking of JS files --- assignment-client/src/assets/AssetServer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/assignment-client/src/assets/AssetServer.cpp b/assignment-client/src/assets/AssetServer.cpp index 9c03bdd3bb..d13e031c03 100644 --- a/assignment-client/src/assets/AssetServer.cpp +++ b/assignment-client/src/assets/AssetServer.cpp @@ -50,9 +50,9 @@ static const int INTERFACE_RUNNING_CHECK_FREQUENCY_MS = 1000; const QString ASSET_SERVER_LOGGING_TARGET_NAME = "asset-server"; -static const QStringList BAKEABLE_MODEL_EXTENSIONS = {"fbx"}; +static const QStringList BAKEABLE_MODEL_EXTENSIONS = { "fbx" }; static QStringList BAKEABLE_TEXTURE_EXTENSIONS; -static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {"js"}; +static const QStringList BAKEABLE_SCRIPT_EXTENSIONS = {}; static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx"; static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx"; static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js"; From e450ac5bd3b5fd13357b85a1d31ded73aa9dbe7a Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 25 Oct 2017 15:28:30 -0700 Subject: [PATCH 07/46] don't use COW-less SpecialAddress assignment --- libraries/networking/src/HifiSockAddr.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/networking/src/HifiSockAddr.h b/libraries/networking/src/HifiSockAddr.h index b582198139..3c753f0434 100644 --- a/libraries/networking/src/HifiSockAddr.h +++ b/libraries/networking/src/HifiSockAddr.h @@ -34,7 +34,7 @@ public: HifiSockAddr(const sockaddr* sockaddr); bool isNull() const { return _address.isNull() && _port == 0; } - void clear() { _address = QHostAddress::Null; _port = 0;} + void clear() { _address.clear(); _port = 0;} HifiSockAddr& operator=(const HifiSockAddr& rhsSockAddr); void swap(HifiSockAddr& otherSockAddr); From 19420a0ff26b691ee7e0eeb2e1a734302c244be8 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Mon, 30 Oct 2017 17:01:55 -0700 Subject: [PATCH 08/46] Move static cert verify to properties --- .../src/entities/EntityServer.cpp | 2 +- libraries/entities/src/EntityItem.cpp | 114 ----------------- libraries/entities/src/EntityItem.h | 3 - .../entities/src/EntityItemProperties.cpp | 116 ++++++++++++++++++ libraries/entities/src/EntityItemProperties.h | 4 + .../entities/src/EntityScriptingInterface.cpp | 2 +- libraries/entities/src/EntityTree.cpp | 2 +- 7 files changed, 123 insertions(+), 120 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index fa9c73b12d..702ca88787 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -459,7 +459,7 @@ void EntityServer::startDynamicDomainVerification() { EntityItemPointer entity = tree->findEntityByEntityItemID(i.value()); if (entity) { - if (!entity->verifyStaticCertificateProperties()) { + if (!entity->getProperties().verifyStaticCertificateProperties()) { qCDebug(entities) << "During Dynamic Domain Verification, a certified entity with ID" << i.value() << "failed" << "static certificate verification."; // Delete the entity if it doesn't pass static certificate verification diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index 4f5db991c8..c355ce90fc 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -14,10 +14,6 @@ #include #include #include -#include -#include -#include -#include #include #include #include @@ -1575,116 +1571,6 @@ float EntityItem::getRadius() const { return 0.5f * glm::length(getDimensions()); } -// Checking Certifiable Properties -#define ADD_STRING_PROPERTY(n, N) if (!propertySet.get##N().isEmpty()) json[#n] = propertySet.get##N() -#define ADD_ENUM_PROPERTY(n, N) json[#n] = propertySet.get##N##AsString() -#define ADD_INT_PROPERTY(n, N) if (propertySet.get##N() != 0) json[#n] = (propertySet.get##N() == (quint32) -1) ? -1.0 : ((double) propertySet.get##N()) -QByteArray EntityItem::getStaticCertificateJSON() const { - // Produce a compact json of every non-default static certificate property, with the property names in alphabetical order. - // The static certificate properties include all an only those properties that cannot be changed without altering the identity - // of the entity as reviewed during the certification submission. - - QJsonObject json; - EntityItemProperties propertySet = getProperties(); // Note: neither EntityItem nor EntityitemProperties "properties" are QObject "properties"! - // It is important that this be reproducible in the same order each time. Since we also generate these on the server, we do it alphabetically - // to help maintainence in two different code bases. - if (!propertySet.getAnimation().getURL().isEmpty()) { - json["animationURL"] = propertySet.getAnimation().getURL(); - } - ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL); - ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL); - ADD_INT_PROPERTY(editionNumber, EditionNumber); - ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber); - ADD_STRING_PROPERTY(itemArtist, ItemArtist); - ADD_STRING_PROPERTY(itemCategories, ItemCategories); - ADD_STRING_PROPERTY(itemDescription, ItemDescription); - ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense); - ADD_STRING_PROPERTY(itemName, ItemName); - ADD_INT_PROPERTY(limitedRun, LimitedRun); - ADD_STRING_PROPERTY(marketplaceID, MarketplaceID); - ADD_STRING_PROPERTY(modelURL, ModelURL); - ADD_STRING_PROPERTY(script, Script); - ADD_ENUM_PROPERTY(shapeType, ShapeType); - json["type"] = EntityTypes::getEntityTypeName(propertySet.getType()); - - return QJsonDocument(json).toJson(QJsonDocument::Compact); -} -QByteArray EntityItem::getStaticCertificateHash() const { - return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256); -} - -bool EntityItem::verifyStaticCertificateProperties() { - // True IIF a non-empty certificateID matches the static certificate json. - // I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash. - - if (getCertificateID().isEmpty()) { - return false; - } - - const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8(); - const unsigned char* marketplacePublicKey = reinterpret_cast(marketplacePublicKeyByteArray.constData()); - int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length(); - - BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength); - EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); - if (evp_key) { - RSA* rsa = EVP_PKEY_get1_RSA(evp_key); - if (rsa) { - const QByteArray digestByteArray = getStaticCertificateHash(); - const unsigned char* digest = reinterpret_cast(digestByteArray.constData()); - int digestLength = digestByteArray.length(); - - const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8()); - const unsigned char* signature = reinterpret_cast(signatureByteArray.constData()); - int signatureLength = signatureByteArray.length(); - - ERR_clear_error(); - bool answer = RSA_verify(NID_sha256, - digest, - digestLength, - signature, - signatureLength, - rsa); - long error = ERR_get_error(); - if (error != 0) { - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str - << "\nStatic Cert JSON:" << getStaticCertificateJSON() - << "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength - << "\nDigest:" << digest << "\nDigest Length:" << digestLength - << "\nSignature:" << signature << "\nSignature Length:" << signatureLength; - } - RSA_free(rsa); - if (bio) { - BIO_free(bio); - } - if (evp_key) { - EVP_PKEY_free(evp_key); - } - return answer; - } else { - if (bio) { - BIO_free(bio); - } - if (evp_key) { - EVP_PKEY_free(evp_key); - } - long error = ERR_get_error(); - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; - return false; - } - } else { - if (bio) { - BIO_free(bio); - } - long error = ERR_get_error(); - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; - return false; - } -} - void EntityItem::adjustShapeInfoByRegistration(ShapeInfo& info) const { if (_registrationPoint != ENTITY_ITEM_DEFAULT_REGISTRATION_POINT) { glm::mat4 scale = glm::scale(getDimensions()); diff --git a/libraries/entities/src/EntityItem.h b/libraries/entities/src/EntityItem.h index 79862b9bd2..76a5205960 100644 --- a/libraries/entities/src/EntityItem.h +++ b/libraries/entities/src/EntityItem.h @@ -328,9 +328,6 @@ public: void setEntityInstanceNumber(const quint32&); QString getCertificateID() const; void setCertificateID(const QString& value); - QByteArray getStaticCertificateJSON() const; - QByteArray getStaticCertificateHash() const; - bool verifyStaticCertificateProperties(); // TODO: get rid of users of getRadius()... float getRadius() const; diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index 3bbd6ce8e6..f774b208c4 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -14,6 +14,15 @@ #include #include +#include +#include +#include +#include +#include +#include +#include +#include + #include #include #include @@ -2471,3 +2480,110 @@ bool EntityItemProperties::parentRelatedPropertyChanged() const { bool EntityItemProperties::queryAACubeRelatedPropertyChanged() const { return parentRelatedPropertyChanged() || dimensionsChanged(); } + +// Checking Certifiable Properties +#define ADD_STRING_PROPERTY(n, N) if (!get##N().isEmpty()) json[#n] = get##N() +#define ADD_ENUM_PROPERTY(n, N) json[#n] = get##N##AsString() +#define ADD_INT_PROPERTY(n, N) if (get##N() != 0) json[#n] = (get##N() == (quint32) -1) ? -1.0 : ((double) get##N()) +QByteArray EntityItemProperties::getStaticCertificateJSON() const { + // Produce a compact json of every non-default static certificate property, with the property names in alphabetical order. + // The static certificate properties include all an only those properties that cannot be changed without altering the identity + // of the entity as reviewed during the certification submission. + + QJsonObject json; + if (!getAnimation().getURL().isEmpty()) { + json["animationURL"] = getAnimation().getURL(); + } + ADD_STRING_PROPERTY(collisionSoundURL, CollisionSoundURL); + ADD_STRING_PROPERTY(compoundShapeURL, CompoundShapeURL); + ADD_INT_PROPERTY(editionNumber, EditionNumber); + ADD_INT_PROPERTY(instanceNumber, EntityInstanceNumber); + ADD_STRING_PROPERTY(itemArtist, ItemArtist); + ADD_STRING_PROPERTY(itemCategories, ItemCategories); + ADD_STRING_PROPERTY(itemDescription, ItemDescription); + ADD_STRING_PROPERTY(itemLicenseUrl, ItemLicense); + ADD_STRING_PROPERTY(itemName, ItemName); + ADD_INT_PROPERTY(limitedRun, LimitedRun); + ADD_STRING_PROPERTY(marketplaceID, MarketplaceID); + ADD_STRING_PROPERTY(modelURL, ModelURL); + ADD_STRING_PROPERTY(script, Script); + ADD_ENUM_PROPERTY(shapeType, ShapeType); + json["type"] = EntityTypes::getEntityTypeName(getType()); + + return QJsonDocument(json).toJson(QJsonDocument::Compact); +} +QByteArray EntityItemProperties::getStaticCertificateHash() const { + return QCryptographicHash::hash(getStaticCertificateJSON(), QCryptographicHash::Sha256); +} + +bool EntityItemProperties::verifyStaticCertificateProperties() { + // True IIF a non-empty certificateID matches the static certificate json. + // I.e., if we can verify that the certificateID was produced by High Fidelity signing the static certificate hash. + + if (getCertificateID().isEmpty()) { + return false; + } + + const QByteArray marketplacePublicKeyByteArray = EntityItem::_marketplacePublicKey.toUtf8(); + const unsigned char* marketplacePublicKey = reinterpret_cast(marketplacePublicKeyByteArray.constData()); + int marketplacePublicKeyLength = marketplacePublicKeyByteArray.length(); + + BIO *bio = BIO_new_mem_buf((void*)marketplacePublicKey, marketplacePublicKeyLength); + EVP_PKEY* evp_key = PEM_read_bio_PUBKEY(bio, NULL, NULL, NULL); + if (evp_key) { + RSA* rsa = EVP_PKEY_get1_RSA(evp_key); + if (rsa) { + const QByteArray digestByteArray = getStaticCertificateHash(); + const unsigned char* digest = reinterpret_cast(digestByteArray.constData()); + int digestLength = digestByteArray.length(); + + const QByteArray signatureByteArray = QByteArray::fromBase64(getCertificateID().toUtf8()); + const unsigned char* signature = reinterpret_cast(signatureByteArray.constData()); + int signatureLength = signatureByteArray.length(); + + ERR_clear_error(); + bool answer = RSA_verify(NID_sha256, + digest, + digestLength, + signature, + signatureLength, + rsa); + long error = ERR_get_error(); + if (error != 0) { + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "ERROR while verifying static certificate properties! RSA error:" << error_str + << "\nStatic Cert JSON:" << getStaticCertificateJSON() + << "\nKey:" << EntityItem::_marketplacePublicKey << "\nKey Length:" << marketplacePublicKeyLength + << "\nDigest:" << digest << "\nDigest Length:" << digestLength + << "\nSignature:" << signature << "\nSignature Length:" << signatureLength; + } + RSA_free(rsa); + if (bio) { + BIO_free(bio); + } + if (evp_key) { + EVP_PKEY_free(evp_key); + } + return answer; + } else { + if (bio) { + BIO_free(bio); + } + if (evp_key) { + EVP_PKEY_free(evp_key); + } + long error = ERR_get_error(); + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; + return false; + } + } else { + if (bio) { + BIO_free(bio); + } + long error = ERR_get_error(); + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "Failed to verify static certificate properties! RSA error:" << error_str; + return false; + } +} diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index 6547026e5c..a8bb063934 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -336,6 +336,10 @@ public: QByteArray getPackedStrokeColors() const; QByteArray packStrokeColors(const QVector& strokeColors) const; + QByteArray getStaticCertificateJSON() const; + QByteArray getStaticCertificateHash() const; + bool verifyStaticCertificateProperties(); + protected: QString getCollisionMaskAsString() const; void setCollisionMaskFromString(const QString& maskString); diff --git a/libraries/entities/src/EntityScriptingInterface.cpp b/libraries/entities/src/EntityScriptingInterface.cpp index c83a5f60a1..998a2d4dfe 100644 --- a/libraries/entities/src/EntityScriptingInterface.cpp +++ b/libraries/entities/src/EntityScriptingInterface.cpp @@ -1827,7 +1827,7 @@ bool EntityScriptingInterface::verifyStaticCertificateProperties(const QUuid& en _entityTree->withReadLock([&] { EntityItemPointer entity = _entityTree->findEntityByEntityItemID(EntityItemID(entityID)); if (entity) { - result = entity->verifyStaticCertificateProperties(); + result = entity->getProperties().verifyStaticCertificateProperties(); } }); } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 9397f38cdd..22348360ed 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1528,7 +1528,7 @@ int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned c _totalCreates++; if (newEntity && isCertified && getIsServer()) { - if (!newEntity->verifyStaticCertificateProperties()) { + if (!properties.verifyStaticCertificateProperties()) { qCDebug(entities) << "User" << senderNode->getUUID() << "attempted to add a certified entity with ID" << entityItemID << "which failed" << "static certificate verification."; From 90e92511767a5bbe3f248afd4d474e423bac5791 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 31 Oct 2017 11:08:02 -0700 Subject: [PATCH 09/46] It's a start --- .../octree/OctreeInboundPacketProcessor.cpp | 4 + .../ui/overlays/ContextOverlayInterface.cpp | 37 +++++++ .../src/ui/overlays/ContextOverlayInterface.h | 2 +- libraries/entities/src/EntityTree.cpp | 101 +++++++++++++----- libraries/entities/src/EntityTree.h | 2 + libraries/networking/src/udt/PacketHeaders.h | 2 + libraries/octree/src/Octree.h | 1 + 7 files changed, 119 insertions(+), 30 deletions(-) diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index 3f835678ac..50c95ffc5d 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -96,6 +96,10 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer _myServer->getOctree()->withWriteLock([&] { _myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode); }); + } else if (packetType == PacketType::ChallengeOwnershipRequest) { + _myServer->getOctree()->withWriteLock([&] { + _myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode); + }); } else if (_myServer->getOctree()->handlesEditPacketType(packetType)) { PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket); _receivedPacketCount++; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 70b75a0b17..402e72f2cf 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -42,6 +42,8 @@ ContextOverlayInterface::ContextOverlayInterface() { _entityPropertyFlags += PROP_DIMENSIONS; _entityPropertyFlags += PROP_REGISTRATION_POINT; _entityPropertyFlags += PROP_CERTIFICATE_ID; + _entityPropertyFlags += PROP_CLIENT_ONLY; + _entityPropertyFlags += PROP_OWNING_AVATAR_ID; auto entityScriptingInterface = DependencyManager::get().data(); connect(entityScriptingInterface, &EntityScriptingInterface::mousePressOnEntity, this, &ContextOverlayInterface::createOrDestroyContextOverlay); @@ -260,6 +262,41 @@ void ContextOverlayInterface::openInspectionCertificate() { auto tablet = dynamic_cast(_tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system")); tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH); _hmdScriptingInterface->openTablet(); + + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_currentEntityWithContextOverlay, _entityPropertyFlags); + + QUuid nodeToChallenge = entityProperties.getOwningAvatarID(); + auto nodeList = DependencyManager::get(); + + qDebug() << "ZRF FIXME" << entityProperties.getClientOnly() << nodeToChallenge << nodeList->getSessionUUID(); + + // Don't challenge ownership of avatar entities that I own + if (entityProperties.getClientOnly() && nodeToChallenge != nodeList->getSessionUUID()) { + SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); + + if (entityServer) { + QByteArray certID = entityProperties.getCertificateID().toUtf8(); + QByteArray ownerKey; // ZRF FIXME! + QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); + + int certIDByteArraySize = certID.length(); + int ownerKeyByteArraySize = ownerKey.length(); + int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); + + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + ownerKeyByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(ownerKeyByteArraySize); + challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); + challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(ownerKey); + challengeOwnershipPacket->write(nodeToChallengeByteArray); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); + } else { + qCWarning(context_overlay) << "Couldn't get Entity Server!"; + } + } } } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index b4d3ddc0c2..a063fdde23 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -73,7 +73,7 @@ public slots: private: bool _verboseLogging { true }; bool _enabled { true }; - QUuid _currentEntityWithContextOverlay{}; + EntityItemID _currentEntityWithContextOverlay{}; QString _entityMarketplaceID; bool _contextOverlayJustClicked { false }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 22348360ed..90ee1190d5 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1153,8 +1153,8 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) _challengeOwnershipTimeoutTimer->deleteLater(); } }); - _challengeOwnershipTimeoutTimer->setSingleShot(true); - _challengeOwnershipTimeoutTimer->start(5000); +_challengeOwnershipTimeoutTimer->setSingleShot(true); +_challengeOwnershipTimeoutTimer->start(5000); } void EntityTree::startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) { @@ -1225,7 +1225,7 @@ bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decr if (!id.isNull()) { qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed; deleting entity" << id << "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce; - deleteEntity(id, true); + deleteEntity(id, true); } } else { qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded; keeping entity" << id; @@ -1234,6 +1234,71 @@ bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decr return verificationSuccess; } +void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { + int certIDByteArraySize; + int ownerKeyByteArraySize; + int nodeToChallengeByteArraySize; + + message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&ownerKeyByteArraySize); + message.readPrimitive(&nodeToChallengeByteArraySize); + + QString certID(message.read(certIDByteArraySize)); + QString ownerKey(message.read(ownerKeyByteArraySize)); + QUuid nodeToChallenge = QUuid::fromRfc4122(message.read(nodeToChallengeByteArraySize)); + + sendChallengeOwnershipPacket(certID, ownerKey, EntityItemID(), sourceNode, nodeToChallenge); +} + +void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge) { + // 1. Encrypt a nonce with the owner's public key + auto nodeList = DependencyManager::get(); + QByteArray encryptedText = computeEncryptedNonce(certID, ownerKey); + + if (encryptedText == "") { + qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce."; + } else { + // In this case, the server is challenging a client. That client has just rezzed a new certified entity. + if (nodeToChallenge.isNull()) { + // 2. Send the encrypted text to the rezzing avatar's node + QByteArray certIDByteArray = certID.toUtf8(); + int certIDByteArraySize = certIDByteArray.size(); + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, + certIDByteArraySize + encryptedText.length() + 2 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedText.length()); + challengeOwnershipPacket->write(certIDByteArray); + challengeOwnershipPacket->write(encryptedText); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); + + // 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); + return; + } else { + startChallengeOwnershipTimer(entityItemID); + } + // In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants + // to make sure belongs to Avatar B. + } else { + QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122(); + QByteArray certIDByteArray = certID.toUtf8(); + int certIDByteArraySize = certIDByteArray.size(); + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + encryptedText.length() + senderNodeUUID.length() + 3 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedText.length()); + challengeOwnershipPacket->writePrimitive(senderNodeUUID.length()); + challengeOwnershipPacket->write(certIDByteArray); + challengeOwnershipPacket->write(encryptedText); + challengeOwnershipPacket->write(senderNodeUUID); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(nodeToChallenge))); + } + } +} + void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) { // Start owner verification. auto nodeList = DependencyManager::get(); @@ -1279,33 +1344,11 @@ void EntityTree::validatePop(const QString& certID, const EntityItemID& entityIt } } else { // Second, challenge ownership of the PoP cert - // 1. Encrypt a nonce with the owner's public key - QByteArray encryptedText = computeEncryptedNonce(certID, jsonObject["transfer_recipient_key"].toString()); + sendChallengeOwnershipPacket(certID, + jsonObject["transfer_recipient_key"].toString(), + entityItemID, + senderNode); - if (encryptedText == "") { - qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity..."; - deleteEntity(entityItemID, true); - } else { - // 2. Send the encrypted text to the rezzing avatar's node - QByteArray certIDByteArray = certID.toUtf8(); - int certIDByteArraySize = certIDByteArray.size(); - auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, - certIDByteArraySize + encryptedText.length() + 2 * sizeof(int), - true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(encryptedText.length()); - challengeOwnershipPacket->write(certIDByteArray); - challengeOwnershipPacket->write(encryptedText); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); - - // 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); - return; - } else { - startChallengeOwnershipTimer(entityItemID); - } - } } } else { qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << "; deleting entity" << entityItemID diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 518cde9a59..3c11557dac 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -93,6 +93,7 @@ public: void fixupTerseEditLogging(EntityItemProperties& properties, QList& changedProperties); virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) override; + virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -377,6 +378,7 @@ protected: private: QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey); bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce); + void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge = NULL); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); }; diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 049cb0f1a8..8d48b106e4 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -124,6 +124,8 @@ public: OctreeFileReplacementFromUrl, ChallengeOwnership, EntityScriptCallMethod, + ChallengeOwnershipRequest, + ChallengeOwnershipReply, NUM_PACKET_TYPE }; diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index 2761dffb1b..fe8868b342 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -212,6 +212,7 @@ public: virtual bool handlesEditPacketType(PacketType packetType) const { return false; } virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; } + virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual bool recurseChildrenWithData() const { return true; } From 6f96e0c7bd30623c33add2fbaebfad6e8f4ea6e3 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 31 Oct 2017 12:14:12 -0700 Subject: [PATCH 10/46] More progress --- .../octree/OctreeInboundPacketProcessor.cpp | 6 +- interface/src/commerce/Wallet.cpp | 41 +++++-- interface/src/commerce/Wallet.h | 1 + .../ui/overlays/ContextOverlayInterface.cpp | 85 +++++++++---- libraries/entities/src/EntityTree.cpp | 112 ++++++++++++------ libraries/entities/src/EntityTree.h | 9 +- libraries/octree/src/Octree.h | 1 + 7 files changed, 186 insertions(+), 69 deletions(-) diff --git a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp index 50c95ffc5d..bce6e7fe44 100644 --- a/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp +++ b/assignment-client/src/octree/OctreeInboundPacketProcessor.cpp @@ -98,7 +98,11 @@ void OctreeInboundPacketProcessor::processPacket(QSharedPointer }); } else if (packetType == PacketType::ChallengeOwnershipRequest) { _myServer->getOctree()->withWriteLock([&] { - _myServer->getOctree()->processChallengeOwnershipPacket(*message, sendingNode); + _myServer->getOctree()->processChallengeOwnershipRequestPacket(*message, sendingNode); + }); + } else if (packetType == PacketType::ChallengeOwnershipReply) { + _myServer->getOctree()->withWriteLock([&] { + _myServer->getOctree()->processChallengeOwnershipReplyPacket(*message, sendingNode); }); } else if (_myServer->getOctree()->handlesEditPacketType(packetType)) { PerformanceWarning warn(debugProcessPacket, "processPacket KNOWN TYPE", debugProcessPacket); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index c7c09d8b03..2389e8e39d 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -319,6 +319,7 @@ Wallet::Wallet() { auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnershipPacket"); + packetReceiver.registerListener(PacketType::ChallengeOwnershipRequest, this, "handleChallengeOwnershipPacket"); connect(ledger.data(), &Ledger::accountResult, this, [&]() { auto wallet = DependencyManager::get(); @@ -717,15 +718,24 @@ bool Wallet::changePassphrase(const QString& newPassphrase) { } void Wallet::handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest; unsigned char decryptedText[64]; int certIDByteArraySize; int encryptedTextByteArraySize; + int senderNodeUUIDByteArraySize; packet->readPrimitive(&certIDByteArraySize); packet->readPrimitive(&encryptedTextByteArraySize); + if (challengeOriginatedFromClient) { + packet->readPrimitive(&senderNodeUUIDByteArraySize); + } QByteArray certID = packet->read(certIDByteArraySize); QByteArray encryptedText = packet->read(encryptedTextByteArraySize); + QByteArray senderNodeUUID; + if (challengeOriginatedFromClient) { + packet->readPrimitive(&senderNodeUUID); + } RSA* rsa = readKeys(keyFilePath().toStdString().c_str()); @@ -745,16 +755,33 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack int decryptedTextByteArraySize = decryptedTextByteArray.size(); int certIDSize = certID.size(); // setup the packet - auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true); + if (challengeOriginatedFromClient) { + auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, + certIDSize + decryptedTextByteArraySize + senderNodeUUIDByteArraySize + 3 * sizeof(int), + true); - decryptedTextPacket->writePrimitive(certIDSize); - decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); - decryptedTextPacket->write(certID); - decryptedTextPacket->write(decryptedTextByteArray); + decryptedTextPacket->writePrimitive(certIDSize); + decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); + decryptedTextPacket->writePrimitive(senderNodeUUIDByteArraySize); + decryptedTextPacket->write(certID); + decryptedTextPacket->write(decryptedTextByteArray); + decryptedTextPacket->write(senderNodeUUID); - qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; + qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; - nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); + nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); + } else { + auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true); + + decryptedTextPacket->writePrimitive(certIDSize); + decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); + decryptedTextPacket->write(certID); + decryptedTextPacket->write(decryptedTextByteArray); + + qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; + + nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); + } } else { qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed."; } diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index ed145df451..fcc508b4e9 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -67,6 +67,7 @@ signals: private slots: void handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode); + void handleChallengeOwnershipRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); private: QStringList _publicKeys{}; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 402e72f2cf..cfc2156a0f 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -14,6 +14,9 @@ #include #include +#include +#include +#include #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) @@ -268,31 +271,73 @@ void ContextOverlayInterface::openInspectionCertificate() { QUuid nodeToChallenge = entityProperties.getOwningAvatarID(); auto nodeList = DependencyManager::get(); - qDebug() << "ZRF FIXME" << entityProperties.getClientOnly() << nodeToChallenge << nodeList->getSessionUUID(); - - // Don't challenge ownership of avatar entities that I own - if (entityProperties.getClientOnly() && nodeToChallenge != nodeList->getSessionUUID()) { + // ZRF FIXME: Don't challenge ownership of avatar entities that I own + if (entityProperties.getClientOnly()/* && nodeToChallenge != nodeList->getSessionUUID()*/) { SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); if (entityServer) { - QByteArray certID = entityProperties.getCertificateID().toUtf8(); - QByteArray ownerKey; // ZRF FIXME! - QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; + requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); + QJsonObject request; + request["certificate_id"] = entityProperties.getCertificateID(); + networkRequest.setUrl(requestURL); - int certIDByteArraySize = certID.length(); - int ownerKeyByteArraySize = ownerKey.length(); - int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); + QNetworkReply* networkReply = NULL; + networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); - auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, - certIDByteArraySize + ownerKeyByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), - true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(ownerKeyByteArraySize); - challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); - challengeOwnershipPacket->write(certID); - challengeOwnershipPacket->write(ownerKey); - challengeOwnershipPacket->write(nodeToChallengeByteArray); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); + connect(networkReply, &QNetworkReply::finished, [=]() { + QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); + jsonObject = jsonObject["data"].toObject(); + + if (networkReply->error() == QNetworkReply::NoError) { + if (!jsonObject["invalid_reason"].toString().isEmpty()) { + qCDebug(entities) << "invalid_reason not empty"; + } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { + qCDebug(entities) << "'transfer_status' is 'failed'";; + } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { + qCDebug(entities) << "'transfer_status' is 'pending'";; + } else { + QByteArray certID = entityProperties.getCertificateID().toUtf8(); + QByteArray ownerKey = jsonObject["transfer_recipient_key"].toString().toUtf8(); + QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); + QByteArray encryptedText = DependencyManager::get()->getTree()->computeEncryptedNonce(certID, ownerKey); + + int certIDByteArraySize = certID.length(); + int ownerKeyByteArraySize = ownerKey.length(); + int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); + int encryptedTextByteArraySize = encryptedText.length(); + + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + ownerKeyByteArraySize + nodeToChallengeByteArraySize + encryptedTextByteArraySize + 4 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(ownerKeyByteArraySize); + challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize); + challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(ownerKey); + challengeOwnershipPacket->write(nodeToChallengeByteArray); + challengeOwnershipPacket->write(encryptedText); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); + + // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time + //if (thread() != QThread::currentThread()) { + // QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); + // return; + //} else { + // startChallengeOwnershipTimer(entityItemID); + //} + } + } else { + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error(); + } + + networkReply->deleteLater(); + }); } else { qCWarning(context_overlay) << "Couldn't get Entity Server!"; } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 90ee1190d5..71c6864b4f 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1237,68 +1237,104 @@ bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decr void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { int certIDByteArraySize; int ownerKeyByteArraySize; + int encryptedTextByteArraySize; int nodeToChallengeByteArraySize; message.readPrimitive(&certIDByteArraySize); message.readPrimitive(&ownerKeyByteArraySize); + message.readPrimitive(&encryptedTextByteArraySize); message.readPrimitive(&nodeToChallengeByteArraySize); QString certID(message.read(certIDByteArraySize)); QString ownerKey(message.read(ownerKeyByteArraySize)); + QString encryptedText(message.read(encryptedTextByteArraySize)); QUuid nodeToChallenge = QUuid::fromRfc4122(message.read(nodeToChallengeByteArraySize)); - sendChallengeOwnershipPacket(certID, ownerKey, EntityItemID(), sourceNode, nodeToChallenge); + sendChallengeOwnershipRequestPacket(certID, ownerKey, encryptedText, sourceNode, nodeToChallenge); } -void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge) { +void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { + auto nodeList = DependencyManager::get(); + + int certIDByteArraySize; + int decryptedTextByteArraySize; + int senderNodeUUIDByteArraySize; + + message.readPrimitive(&certIDByteArraySize); + message.readPrimitive(&decryptedTextByteArraySize); + message.readPrimitive(&senderNodeUUIDByteArraySize); + + QByteArray certID(message.read(certIDByteArraySize)); + QByteArray decryptedText(message.read(decryptedTextByteArraySize)); + QUuid challengingNode = QUuid::fromRfc4122(message.read(senderNodeUUIDByteArraySize)); + + + + auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, + certIDByteArraySize + decryptedText.length() + 2 * sizeof(int), + true); + challengeOwnershipReplyPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipReplyPacket->writePrimitive(decryptedText.length()); + challengeOwnershipReplyPacket->write(certID); + challengeOwnershipReplyPacket->write(decryptedText); + + nodeList->sendPacket(std::move(challengeOwnershipReplyPacket), *(nodeList->nodeWithUUID(challengingNode))); +} + +void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) { // 1. Encrypt a nonce with the owner's public key auto nodeList = DependencyManager::get(); + QByteArray encryptedText = computeEncryptedNonce(certID, ownerKey); if (encryptedText == "") { - qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce."; + qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity..."; + deleteEntity(entityItemID, true); } else { - // In this case, the server is challenging a client. That client has just rezzed a new certified entity. - if (nodeToChallenge.isNull()) { - // 2. Send the encrypted text to the rezzing avatar's node - QByteArray certIDByteArray = certID.toUtf8(); - int certIDByteArraySize = certIDByteArray.size(); - auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, - certIDByteArraySize + encryptedText.length() + 2 * sizeof(int), - true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(encryptedText.length()); - challengeOwnershipPacket->write(certIDByteArray); - challengeOwnershipPacket->write(encryptedText); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); + // 2. Send the encrypted text to the rezzing avatar's node + QByteArray certIDByteArray = certID.toUtf8(); + int certIDByteArraySize = certIDByteArray.size(); + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnership, + certIDByteArraySize + encryptedText.length() + 2 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedText.length()); + challengeOwnershipPacket->write(certIDByteArray); + challengeOwnershipPacket->write(encryptedText); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *senderNode); - // 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time - if (thread() != QThread::currentThread()) { - QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); - return; - } else { - startChallengeOwnershipTimer(entityItemID); - } - // In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants - // to make sure belongs to Avatar B. + // 3. Kickoff a 10-second timeout timer that deletes the entity if we don't get an ownership response in time + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); + return; } else { - QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122(); - QByteArray certIDByteArray = certID.toUtf8(); - int certIDByteArraySize = certIDByteArray.size(); - auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, - certIDByteArraySize + encryptedText.length() + senderNodeUUID.length() + 3 * sizeof(int), - true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(encryptedText.length()); - challengeOwnershipPacket->writePrimitive(senderNodeUUID.length()); - challengeOwnershipPacket->write(certIDByteArray); - challengeOwnershipPacket->write(encryptedText); - challengeOwnershipPacket->write(senderNodeUUID); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(nodeToChallenge))); + startChallengeOwnershipTimer(entityItemID); } } } +void EntityTree::sendChallengeOwnershipRequestPacket(const QString& certID, const QString& ownerKey, const QString& encryptedText, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge) { + // 1. Encrypt a nonce with the owner's public key + auto nodeList = DependencyManager::get(); + + // In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants + // to make sure belongs to Avatar B. + QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122(); + QByteArray encryptedTextByteArray = encryptedText.toUtf8(); + QByteArray certIDByteArray = certID.toUtf8(); + int certIDByteArraySize = certIDByteArray.size(); + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + encryptedTextByteArray.length() + senderNodeUUID.length() + 3 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedTextByteArray.length()); + challengeOwnershipPacket->writePrimitive(senderNodeUUID.length()); + challengeOwnershipPacket->write(certIDByteArray); + challengeOwnershipPacket->write(encryptedTextByteArray); + challengeOwnershipPacket->write(senderNodeUUID); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(nodeToChallenge))); +} + void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) { // Start owner verification. auto nodeList = DependencyManager::get(); diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 3c11557dac..05b32676ed 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -94,6 +94,7 @@ public: virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& senderNode) override; virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; + virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) override; virtual bool findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, @@ -274,6 +275,9 @@ public: static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; + QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey); + bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce); + signals: void deletingEntity(const EntityItemID& entityID); void deletingEntityPointer(EntityItem* entityID); @@ -376,9 +380,8 @@ protected: Q_INVOKABLE void startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); private: - QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey); - bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce); - void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge = NULL); + void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); + void sendChallengeOwnershipRequestPacket(const QString& certID, const QString& ownerKey, const QString& encryptedText, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); }; diff --git a/libraries/octree/src/Octree.h b/libraries/octree/src/Octree.h index fe8868b342..ec6a0e810d 100644 --- a/libraries/octree/src/Octree.h +++ b/libraries/octree/src/Octree.h @@ -213,6 +213,7 @@ public: virtual int processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, const SharedNodePointer& sourceNode) { return 0; } virtual void processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } + virtual void processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual void processChallengeOwnershipPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { return; } virtual bool recurseChildrenWithData() const { return true; } From f5ada4fe62b94bb1703c18c19a977a51e4ac0bf9 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 31 Oct 2017 12:31:42 -0700 Subject: [PATCH 11/46] Will it compile? --- interface/src/commerce/Wallet.h | 1 - .../ui/overlays/ContextOverlayInterface.cpp | 20 +++++++++ .../src/ui/overlays/ContextOverlayInterface.h | 3 ++ libraries/entities/src/EntityTree.cpp | 42 ++++++++++--------- libraries/entities/src/EntityTree.h | 2 +- 5 files changed, 47 insertions(+), 21 deletions(-) diff --git a/interface/src/commerce/Wallet.h b/interface/src/commerce/Wallet.h index fcc508b4e9..ed145df451 100644 --- a/interface/src/commerce/Wallet.h +++ b/interface/src/commerce/Wallet.h @@ -67,7 +67,6 @@ signals: private slots: void handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode); - void handleChallengeOwnershipRequestPacket(QSharedPointer packet, SharedNodePointer sendingNode); private: QStringList _publicKeys{}; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index cfc2156a0f..8f6a38ce09 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -71,6 +71,10 @@ ContextOverlayInterface::ContextOverlayInterface() { connect(&qApp->getOverlays(), &Overlays::hoverLeaveOverlay, this, &ContextOverlayInterface::contextOverlays_hoverLeaveOverlay); connect(_selectionScriptingInterface.data(), &SelectionScriptingInterface::selectedItemsListChanged, &_selectionToSceneHandler, &SelectionToSceneHandler::selectedItemsListChanged); + + auto nodeList = DependencyManager::get(); + auto& packetReceiver = nodeList->getPacketReceiver(); + packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, this, "handleChallengeOwnershipReplyPacket"); } static const uint32_t MOUSE_HW_ID = 0; @@ -375,3 +379,19 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) { destroyContextOverlay(_currentEntityWithContextOverlay, PointerEvent()); } } + +void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + int certIDByteArraySize; + int decryptedTextByteArraySize; + + packet->readPrimitive(&certIDByteArraySize); + packet->readPrimitive(&decryptedTextByteArraySize); + + QString certID(packet->read(certIDByteArraySize)); + QString decryptedText(packet->read(decryptedTextByteArraySize)); + + EntityItemID id; + bool verificationSuccess = DependencyManager::get()->getTree()->verifyDecryptedNonce(certID, decryptedText, id); + + qDebug() << "ZRF VERIFICATION STATUS:" << verificationSuccess; +} diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index a063fdde23..eedc1790d3 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -70,6 +70,9 @@ public slots: void contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event); bool contextOverlayFilterPassed(const EntityItemID& entityItemID); +private slots: + void handleChallengeOwnershipReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode); + private: bool _verboseLogging { true }; bool _enabled { true }; diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 71c6864b4f..dc99a7b6c9 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1206,9 +1206,7 @@ QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QStrin } } -bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce) { - - EntityItemID id; +bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id) { { QReadLocker certIdMapLocker(&_entityCertificateIDMapLock); id = _entityCertificateIDMap.value(certID); @@ -1221,14 +1219,12 @@ bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decr } bool verificationSuccess = (actualNonce == decryptedNonce); - if (!verificationSuccess) { - if (!id.isNull()) { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed; deleting entity" << id - << "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce; - deleteEntity(id, true); - } + + if (verificationSuccess) { + qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded for entity" << id; } else { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded; keeping entity" << id; + qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed for entity" << id + << "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce; } return verificationSuccess; @@ -1268,8 +1264,6 @@ void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, QByteArray decryptedText(message.read(decryptedTextByteArraySize)); QUuid challengingNode = QUuid::fromRfc4122(message.read(senderNodeUUIDByteArraySize)); - - auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, certIDByteArraySize + decryptedText.length() + 2 * sizeof(int), true); @@ -1319,19 +1313,24 @@ void EntityTree::sendChallengeOwnershipRequestPacket(const QString& certID, cons // In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants // to make sure belongs to Avatar B. - QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122(); - QByteArray encryptedTextByteArray = encryptedText.toUtf8(); QByteArray certIDByteArray = certID.toUtf8(); - int certIDByteArraySize = certIDByteArray.size(); + QByteArray encryptedTextByteArray = encryptedText.toUtf8(); + QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122(); + + int certIDByteArraySize = certIDByteArray.length(); + int encryptedTextByteArraySize = encryptedTextByteArray.length(); + int senderNodeUUIDSize = senderNodeUUID.length(); + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, - certIDByteArraySize + encryptedTextByteArray.length() + senderNodeUUID.length() + 3 * sizeof(int), + certIDByteArraySize + encryptedTextByteArraySize + senderNodeUUIDSize + 3 * sizeof(int), true); challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(encryptedTextByteArray.length()); - challengeOwnershipPacket->writePrimitive(senderNodeUUID.length()); + challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize); + challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize); challengeOwnershipPacket->write(certIDByteArray); challengeOwnershipPacket->write(encryptedTextByteArray); challengeOwnershipPacket->write(senderNodeUUID); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(nodeToChallenge))); } @@ -1408,7 +1407,12 @@ void EntityTree::processChallengeOwnershipPacket(ReceivedMessage& message, const emit killChallengeOwnershipTimeoutTimer(certID); - verifyDecryptedNonce(certID, decryptedText); + EntityItemID id; + if (!verifyDecryptedNonce(certID, decryptedText, id)) { + if (!id.isNull()) { + deleteEntity(id, true); + } + } } int EntityTree::processEditPacketData(ReceivedMessage& message, const unsigned char* editData, int maxLength, diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 05b32676ed..3048917a49 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -276,7 +276,7 @@ public: static const float DEFAULT_MAX_TMP_ENTITY_LIFETIME; QByteArray computeEncryptedNonce(const QString& certID, const QString ownerKey); - bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce); + bool verifyDecryptedNonce(const QString& certID, const QString& decryptedNonce, EntityItemID& id); signals: void deletingEntity(const EntityItemID& entityID); From e9c144892a6d1d3d7e9c17f799b3824097ece56d Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 31 Oct 2017 12:55:41 -0700 Subject: [PATCH 12/46] Fixes --- assignment-client/src/entities/EntityServer.cpp | 10 ++++++++-- interface/src/commerce/Wallet.cpp | 2 +- interface/src/ui/overlays/ContextOverlayInterface.cpp | 3 ++- libraries/entities/src/EntityTree.cpp | 4 ++-- 4 files changed, 13 insertions(+), 6 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 702ca88787..3a31b21f08 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -41,8 +41,14 @@ EntityServer::EntityServer(ReceivedMessage& message) : DependencyManager::set(); auto& packetReceiver = DependencyManager::get()->getPacketReceiver(); - packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, PacketType::EntityEdit, PacketType::EntityErase, PacketType::EntityPhysics, PacketType::ChallengeOwnership }, - this, "handleEntityPacket"); + packetReceiver.registerListenerForTypes({ PacketType::EntityAdd, + PacketType::EntityEdit, + PacketType::EntityErase, + PacketType::EntityPhysics, + PacketType::ChallengeOwnership, + PacketType::ChallengeOwnershipRequest }, + this, + "handleEntityPacket"); connect(&_dynamicDomainVerificationTimer, &QTimer::timeout, this, &EntityServer::startDynamicDomainVerification); _dynamicDomainVerificationTimer.setSingleShot(true); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 2389e8e39d..4414c7b206 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -734,7 +734,7 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack QByteArray encryptedText = packet->read(encryptedTextByteArraySize); QByteArray senderNodeUUID; if (challengeOriginatedFromClient) { - packet->readPrimitive(&senderNodeUUID); + senderNodeUUID = packet->read(senderNodeUUIDByteArraySize); } RSA* rsa = readKeys(keyFilePath().toStdString().c_str()); diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 8f6a38ce09..85bdef22ca 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -337,7 +337,8 @@ void ContextOverlayInterface::openInspectionCertificate() { //} } } else { - qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error(); + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << + "More info:" << networkReply->readAll(); } networkReply->deleteLater(); diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index dc99a7b6c9..ba84c45311 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1238,13 +1238,13 @@ void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message message.readPrimitive(&certIDByteArraySize); message.readPrimitive(&ownerKeyByteArraySize); - message.readPrimitive(&encryptedTextByteArraySize); message.readPrimitive(&nodeToChallengeByteArraySize); + message.readPrimitive(&encryptedTextByteArraySize); QString certID(message.read(certIDByteArraySize)); QString ownerKey(message.read(ownerKeyByteArraySize)); - QString encryptedText(message.read(encryptedTextByteArraySize)); QUuid nodeToChallenge = QUuid::fromRfc4122(message.read(nodeToChallengeByteArraySize)); + QString encryptedText(message.read(encryptedTextByteArraySize)); sendChallengeOwnershipRequestPacket(certID, ownerKey, encryptedText, sourceNode, nodeToChallenge); } From d49e281fabfffeb6fede7d7e0dff13d03f3981cf Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 31 Oct 2017 14:12:30 -0700 Subject: [PATCH 13/46] Add RSA error handling --- interface/src/commerce/Wallet.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 4414c7b206..acc0b08afb 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -740,6 +740,7 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack RSA* rsa = readKeys(keyFilePath().toStdString().c_str()); if (rsa) { + ERR_clear_error(); const int decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize, reinterpret_cast(encryptedText.constData()), decryptedText, @@ -784,6 +785,11 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack } } else { qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed."; + long error = ERR_get_error(); + if (error != 0) { + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "RSA error:" << error_str; + } } } else { qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed."; From f9a410de873679b5371ebe6d8f6039a6de6d62d9 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Tue, 31 Oct 2017 14:53:37 -0700 Subject: [PATCH 14/46] Change 'GET' to 'BUY' when hovering over costed items --- scripts/system/html/js/marketplacesInject.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/scripts/system/html/js/marketplacesInject.js b/scripts/system/html/js/marketplacesInject.js index 8deb5c0bbd..4b127baef8 100644 --- a/scripts/system/html/js/marketplacesInject.js +++ b/scripts/system/html/js/marketplacesInject.js @@ -290,11 +290,17 @@ } }); - // change pricing to GET on button hover + // change pricing to GET/BUY on button hover $('body').on('mouseenter', '#price-or-edit .price', function () { var $this = $(this); $this.data('initialHtml', $this.html()); - $this.text('GET'); + + var cost = $(this).parent().siblings().text(); + if (parseInt(cost) > 0) { + $this.text('BUY'); + } else { + $this.text('GET'); + } }); $('body').on('mouseleave', '#price-or-edit .price', function () { From b8370128ced4dea2e27a7f0ef7de6cdc7e9875f2 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Tue, 31 Oct 2017 14:34:17 -0700 Subject: [PATCH 15/46] remove master config from HifiConfigVariantMap/domain-server --- .../src/DomainServerSettingsManager.cpp | 28 +---------------- libraries/shared/src/HifiConfigVariantMap.cpp | 30 +------------------ libraries/shared/src/HifiConfigVariantMap.h | 6 ---- 3 files changed, 2 insertions(+), 62 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 43262a1a81..d3360d9ded 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -100,8 +100,7 @@ void DomainServerSettingsManager::processSettingsRequestPacket(QSharedPointer Date: Tue, 31 Oct 2017 15:59:18 -0700 Subject: [PATCH 16/46] Why did I think this would work before? --- interface/src/commerce/Wallet.cpp | 1 + .../ui/overlays/ContextOverlayInterface.cpp | 124 +++++++++--------- libraries/entities/src/EntityTree.cpp | 18 +-- libraries/entities/src/EntityTree.h | 2 +- 4 files changed, 73 insertions(+), 72 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index acc0b08afb..d0ef3fcaae 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -732,6 +732,7 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack QByteArray certID = packet->read(certIDByteArraySize); QByteArray encryptedText = packet->read(encryptedTextByteArraySize); + qDebug() << "ZRF encryptedText Inbound:" << QString(encryptedText); QByteArray senderNodeUUID; if (challengeOriginatedFromClient) { senderNodeUUID = packet->read(senderNodeUUIDByteArraySize); diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 85bdef22ca..eecf93745a 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -277,74 +277,78 @@ void ContextOverlayInterface::openInspectionCertificate() { // ZRF FIXME: Don't challenge ownership of avatar entities that I own if (entityProperties.getClientOnly()/* && nodeToChallenge != nodeList->getSessionUUID()*/) { - SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); + // ZRF FIXME! + //if (entityProperties.verifyStaticCertificateProperties()) { + if (true) { + SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); - if (entityServer) { - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest networkRequest; - networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); - QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; - requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); - QJsonObject request; - request["certificate_id"] = entityProperties.getCertificateID(); - networkRequest.setUrl(requestURL); + if (entityServer) { + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest networkRequest; + networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json"); + QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL; + requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/transfer"); + QJsonObject request; + request["certificate_id"] = entityProperties.getCertificateID(); + networkRequest.setUrl(requestURL); - QNetworkReply* networkReply = NULL; - networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); + QNetworkReply* networkReply = NULL; + networkReply = networkAccessManager.put(networkRequest, QJsonDocument(request).toJson()); - connect(networkReply, &QNetworkReply::finished, [=]() { - QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); - jsonObject = jsonObject["data"].toObject(); + connect(networkReply, &QNetworkReply::finished, [=]() { + QJsonObject jsonObject = QJsonDocument::fromJson(networkReply->readAll()).object(); + jsonObject = jsonObject["data"].toObject(); - if (networkReply->error() == QNetworkReply::NoError) { - if (!jsonObject["invalid_reason"].toString().isEmpty()) { - qCDebug(entities) << "invalid_reason not empty"; - } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { - qCDebug(entities) << "'transfer_status' is 'failed'";; - } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { - qCDebug(entities) << "'transfer_status' is 'pending'";; + if (networkReply->error() == QNetworkReply::NoError) { + if (!jsonObject["invalid_reason"].toString().isEmpty()) { + qCDebug(entities) << "invalid_reason not empty"; + } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { + qCDebug(entities) << "'transfer_status' is 'failed'";; + } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { + qCDebug(entities) << "'transfer_status' is 'pending'";; + } else { + QString ownerKey = jsonObject["transfer_recipient_key"].toString(); + + QByteArray certID = entityProperties.getCertificateID().toUtf8(); + QByteArray encryptedText = DependencyManager::get()->getTree()->computeEncryptedNonce(certID, ownerKey); + QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); + + int certIDByteArraySize = certID.length(); + int encryptedTextByteArraySize = encryptedText.length(); + int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); + + auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, + certIDByteArraySize + encryptedTextByteArraySize + nodeToChallengeByteArraySize + 3 * sizeof(int), + true); + challengeOwnershipPacket->writePrimitive(certIDByteArraySize); + challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize); + challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); + challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(encryptedText); + challengeOwnershipPacket->write(nodeToChallengeByteArray); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); + + // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time + //if (thread() != QThread::currentThread()) { + // QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); + // return; + //} else { + // startChallengeOwnershipTimer(entityItemID); + //} + } } else { - QByteArray certID = entityProperties.getCertificateID().toUtf8(); - QByteArray ownerKey = jsonObject["transfer_recipient_key"].toString().toUtf8(); - QByteArray nodeToChallengeByteArray = entityProperties.getOwningAvatarID().toRfc4122(); - QByteArray encryptedText = DependencyManager::get()->getTree()->computeEncryptedNonce(certID, ownerKey); - - int certIDByteArraySize = certID.length(); - int ownerKeyByteArraySize = ownerKey.length(); - int nodeToChallengeByteArraySize = nodeToChallengeByteArray.length(); - int encryptedTextByteArraySize = encryptedText.length(); - - auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, - certIDByteArraySize + ownerKeyByteArraySize + nodeToChallengeByteArraySize + encryptedTextByteArraySize + 4 * sizeof(int), - true); - challengeOwnershipPacket->writePrimitive(certIDByteArraySize); - challengeOwnershipPacket->writePrimitive(ownerKeyByteArraySize); - challengeOwnershipPacket->writePrimitive(nodeToChallengeByteArraySize); - challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize); - challengeOwnershipPacket->write(certID); - challengeOwnershipPacket->write(ownerKey); - challengeOwnershipPacket->write(nodeToChallengeByteArray); - challengeOwnershipPacket->write(encryptedText); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); - - // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time - //if (thread() != QThread::currentThread()) { - // QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); - // return; - //} else { - // startChallengeOwnershipTimer(entityItemID); - //} + qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << + "More info:" << networkReply->readAll(); } - } else { - qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << - "More info:" << networkReply->readAll(); - } - networkReply->deleteLater(); - }); + networkReply->deleteLater(); + }); + } else { + qCWarning(context_overlay) << "Couldn't get Entity Server!"; + } } else { - qCWarning(context_overlay) << "Couldn't get Entity Server!"; + qCDebug(context_overlay) << "Entity" << _currentEntityWithContextOverlay << "failed static certificate verification!"; } } } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index ba84c45311..1cf9fdfb26 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1153,8 +1153,8 @@ void EntityTree::startChallengeOwnershipTimer(const EntityItemID& entityItemID) _challengeOwnershipTimeoutTimer->deleteLater(); } }); -_challengeOwnershipTimeoutTimer->setSingleShot(true); -_challengeOwnershipTimeoutTimer->start(5000); + _challengeOwnershipTimeoutTimer->setSingleShot(true); + _challengeOwnershipTimeoutTimer->start(5000); } void EntityTree::startPendingTransferStatusTimer(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode) { @@ -1195,7 +1195,6 @@ QByteArray EntityTree::computeEncryptedNonce(const QString& certID, const QStrin QWriteLocker locker(&_certNonceMapLock); _certNonceMap.insert(certID, nonce); - qCDebug(entities) << "Challenging ownership of Cert ID" << certID << "by encrypting and sending nonce" << nonce << "to owner."; return encryptedText; } else { @@ -1232,21 +1231,18 @@ bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decr void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { int certIDByteArraySize; - int ownerKeyByteArraySize; int encryptedTextByteArraySize; int nodeToChallengeByteArraySize; message.readPrimitive(&certIDByteArraySize); - message.readPrimitive(&ownerKeyByteArraySize); - message.readPrimitive(&nodeToChallengeByteArraySize); message.readPrimitive(&encryptedTextByteArraySize); + message.readPrimitive(&nodeToChallengeByteArraySize); QString certID(message.read(certIDByteArraySize)); - QString ownerKey(message.read(ownerKeyByteArraySize)); - QUuid nodeToChallenge = QUuid::fromRfc4122(message.read(nodeToChallengeByteArraySize)); QString encryptedText(message.read(encryptedTextByteArraySize)); + QUuid nodeToChallenge = QUuid::fromRfc4122(message.read(nodeToChallengeByteArraySize)); - sendChallengeOwnershipRequestPacket(certID, ownerKey, encryptedText, sourceNode, nodeToChallenge); + sendChallengeOwnershipRequestPacket(certID, encryptedText, sourceNode, nodeToChallenge); } void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { @@ -1285,6 +1281,7 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri qCDebug(entities) << "CRITICAL ERROR: Couldn't compute encrypted nonce. Deleting entity..."; deleteEntity(entityItemID, true); } else { + qCDebug(entities) << "Challenging ownership of Cert ID" << certID; // 2. Send the encrypted text to the rezzing avatar's node QByteArray certIDByteArray = certID.toUtf8(); int certIDByteArraySize = certIDByteArray.size(); @@ -1307,8 +1304,7 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri } } -void EntityTree::sendChallengeOwnershipRequestPacket(const QString& certID, const QString& ownerKey, const QString& encryptedText, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge) { - // 1. Encrypt a nonce with the owner's public key +void EntityTree::sendChallengeOwnershipRequestPacket(const QString& certID, const QString& encryptedText, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge) { auto nodeList = DependencyManager::get(); // In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 3048917a49..5eb5d5b569 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -381,7 +381,7 @@ protected: private: void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); - void sendChallengeOwnershipRequestPacket(const QString& certID, const QString& ownerKey, const QString& encryptedText, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge); + void sendChallengeOwnershipRequestPacket(const QString& certID, const QString& encryptedText, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); }; From 7616fe193cff920299d44b39cc98c7e97efe8029 Mon Sep 17 00:00:00 2001 From: Atlante45 Date: Tue, 31 Oct 2017 16:09:26 -0700 Subject: [PATCH 17/46] Migrate the DS config version to the config file --- .../src/DomainServerSettingsManager.cpp | 54 +++++++++++-------- libraries/shared/src/HifiConfigVariantMap.cpp | 6 --- libraries/shared/src/HifiConfigVariantMap.h | 1 - libraries/shared/src/SettingHelpers.cpp | 6 +++ libraries/shared/src/SettingHelpers.h | 7 +-- 5 files changed, 42 insertions(+), 32 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index d3360d9ded..6c50e5245d 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include //for KillAvatarReason #include #include "DomainServerNodeData.h" @@ -43,12 +44,7 @@ const QString DESCRIPTION_COLUMNS_KEY = "columns"; const QString SETTINGS_VIEWPOINT_KEY = "viewpoint"; -static Setting::Handle JSON_SETTING_VERSION("json-settings/version", 0.0); - -DomainServerSettingsManager::DomainServerSettingsManager() : - _descriptionArray(), - _configMap() -{ +DomainServerSettingsManager::DomainServerSettingsManager() { // load the description object from the settings description QFile descriptionFile(QCoreApplication::applicationDirPath() + SETTINGS_DESCRIPTION_RELATIVE_PATH); descriptionFile.open(QIODevice::ReadOnly); @@ -102,9 +98,32 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList _configMap.loadConfig(_argumentList); + static const auto VERSION_SETTINGS_KEYPATH = "version"; + QVariant* versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH); + + if (!versionVariant) { + versionVariant = _configMap.valueForKeyPath(VERSION_SETTINGS_KEYPATH, true); + *versionVariant = _descriptionVersion; + persistToFile(); + qDebug() << "No version in config file, setting to current version" << _descriptionVersion; + } + + { + // Backward compatibility migration code + // The config version used to be stored in a different file + // This moves it to the actual config file. + Setting::Handle JSON_SETTING_VERSION("json-settings/version", 0.0); + if (JSON_SETTING_VERSION.isSet()) { + auto version = JSON_SETTING_VERSION.get(); + *versionVariant = version; + persistToFile(); + QFile::remove(settingsFilename()); + } + } + // What settings version were we before and what are we using now? // Do we need to do any re-mapping? - double oldVersion = JSON_SETTING_VERSION.get(); + double oldVersion = versionVariant->toDouble(); if (oldVersion != _descriptionVersion) { const QString ALLOWED_USERS_SETTINGS_KEYPATH = "security.allowed_users"; @@ -136,9 +155,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList QVariant* restrictedAccess = _configMap.valueForKeyPath(RESTRICTED_ACCESS_SETTINGS_KEYPATH, true); *restrictedAccess = QVariant(true); - - // write the new settings to the json file - persistToFile(); } } @@ -168,9 +184,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList *entityServerVariant = entityServerMap; } - - // write the new settings to the json file - persistToFile(); } } @@ -188,9 +201,6 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList qDebug() << "Migrating plaintext password to SHA256 hash in domain-server settings."; *passwordVariant = QCryptographicHash::hash(plaintextPassword.toUtf8(), QCryptographicHash::Sha256).toHex(); - - // write the new settings to file - persistToFile(); } } @@ -293,16 +303,16 @@ void DomainServerSettingsManager::setupConfigMap(const QStringList& argumentList QVariant* wizardCompletedOnce = _configMap.valueForKeyPath(WIZARD_COMPLETED_ONCE, true); *wizardCompletedOnce = QVariant(true); - - // write the new settings to the json file - persistToFile(); } + + // write the current description version to our settings + *versionVariant = _descriptionVersion; + + // write the new settings to the json file + persistToFile(); } unpackPermissions(); - - // write the current description version to our settings - JSON_SETTING_VERSION.set(_descriptionVersion); } QVariantMap& DomainServerSettingsManager::getDescriptorsMap() { diff --git a/libraries/shared/src/HifiConfigVariantMap.cpp b/libraries/shared/src/HifiConfigVariantMap.cpp index 8730906afc..59f660feee 100644 --- a/libraries/shared/src/HifiConfigVariantMap.cpp +++ b/libraries/shared/src/HifiConfigVariantMap.cpp @@ -91,12 +91,6 @@ QVariantMap HifiConfigVariantMap::mergeCLParametersWithJSONConfig(const QStringL return mergedMap; } -HifiConfigVariantMap::HifiConfigVariantMap() : - _userConfigFilename() -{ - -} - void HifiConfigVariantMap::loadConfig(const QStringList& argumentList) { // load the user config const QString USER_CONFIG_FILE_OPTION = "--user-config"; diff --git a/libraries/shared/src/HifiConfigVariantMap.h b/libraries/shared/src/HifiConfigVariantMap.h index e9db93173a..ee248ec3d2 100644 --- a/libraries/shared/src/HifiConfigVariantMap.h +++ b/libraries/shared/src/HifiConfigVariantMap.h @@ -21,7 +21,6 @@ class HifiConfigVariantMap { public: static QVariantMap mergeCLParametersWithJSONConfig(const QStringList& argumentList); - HifiConfigVariantMap(); void loadConfig(const QStringList& argumentList); const QVariant value(const QString& key) const { return _userConfig.value(key); } diff --git a/libraries/shared/src/SettingHelpers.cpp b/libraries/shared/src/SettingHelpers.cpp index dd301aa5aa..c6b8ac8f83 100644 --- a/libraries/shared/src/SettingHelpers.cpp +++ b/libraries/shared/src/SettingHelpers.cpp @@ -23,9 +23,15 @@ #include "SharedLogging.h" +const QSettings::Format JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile); + QSettings::SettingsMap jsonDocumentToVariantMap(const QJsonDocument& document); QJsonDocument variantMapToJsonDocument(const QSettings::SettingsMap& map); +QString settingsFilename() { + return QSettings().fileName(); +} + bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map) { QJsonParseError jsonParseError; diff --git a/libraries/shared/src/SettingHelpers.h b/libraries/shared/src/SettingHelpers.h index 122e56957c..ad61897c65 100644 --- a/libraries/shared/src/SettingHelpers.h +++ b/libraries/shared/src/SettingHelpers.h @@ -14,12 +14,13 @@ #include +extern const QSettings::Format JSON_FORMAT; + +QString settingsFilename(); + bool readJSONFile(QIODevice& device, QSettings::SettingsMap& map); bool writeJSONFile(QIODevice& device, const QSettings::SettingsMap& map); -static const auto JSON_FORMAT = QSettings::registerFormat("json", readJSONFile, writeJSONFile); - void loadOldINIFile(QSettings& settings); - #endif // hifi_SettingHelpers_h From 8a6a744099ed41a56afd89f2628a6f2e4aa45300 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 1 Nov 2017 10:46:13 -0700 Subject: [PATCH 18/46] It's working --- .../src/entities/EntityServer.cpp | 3 +- interface/src/commerce/Ledger.h | 8 ++++++ interface/src/commerce/Wallet.cpp | 15 +++++----- .../ui/overlays/ContextOverlayInterface.cpp | 11 +++++++- libraries/entities/src/EntityTree.cpp | 28 +++++++++---------- libraries/entities/src/EntityTree.h | 2 +- 6 files changed, 41 insertions(+), 26 deletions(-) diff --git a/assignment-client/src/entities/EntityServer.cpp b/assignment-client/src/entities/EntityServer.cpp index 3a31b21f08..995a5bad27 100644 --- a/assignment-client/src/entities/EntityServer.cpp +++ b/assignment-client/src/entities/EntityServer.cpp @@ -46,7 +46,8 @@ EntityServer::EntityServer(ReceivedMessage& message) : PacketType::EntityErase, PacketType::EntityPhysics, PacketType::ChallengeOwnership, - PacketType::ChallengeOwnershipRequest }, + PacketType::ChallengeOwnershipRequest, + PacketType::ChallengeOwnershipReply }, this, "handleEntityPacket"); diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index ae001010f0..75f6fb1ba8 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -65,6 +65,14 @@ public slots: void certificateInfoSuccess(QNetworkReply& reply); void certificateInfoFailure(QNetworkReply& reply); + void updateCertificateStatus(const QString& certID, uint certStatus); + enum CertificateStatus { + CERTIFICATE_STATUS_UNKNOWN = 0, + CERTIFICATE_STATUS_VERIFICATION_SUCCESS, + CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED, + CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED, + }; + private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); QJsonObject failResponse(const QString& label, QNetworkReply& reply); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index d0ef3fcaae..c31184eb56 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -722,20 +722,19 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack unsigned char decryptedText[64]; int certIDByteArraySize; int encryptedTextByteArraySize; - int senderNodeUUIDByteArraySize; + int challengingNodeUUIDByteArraySize; packet->readPrimitive(&certIDByteArraySize); packet->readPrimitive(&encryptedTextByteArraySize); if (challengeOriginatedFromClient) { - packet->readPrimitive(&senderNodeUUIDByteArraySize); + packet->readPrimitive(&challengingNodeUUIDByteArraySize); } QByteArray certID = packet->read(certIDByteArraySize); QByteArray encryptedText = packet->read(encryptedTextByteArraySize); - qDebug() << "ZRF encryptedText Inbound:" << QString(encryptedText); - QByteArray senderNodeUUID; + QByteArray challengingNodeUUID; if (challengeOriginatedFromClient) { - senderNodeUUID = packet->read(senderNodeUUIDByteArraySize); + challengingNodeUUID = packet->read(challengingNodeUUIDByteArraySize); } RSA* rsa = readKeys(keyFilePath().toStdString().c_str()); @@ -759,15 +758,15 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack // setup the packet if (challengeOriginatedFromClient) { auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, - certIDSize + decryptedTextByteArraySize + senderNodeUUIDByteArraySize + 3 * sizeof(int), + certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int), true); decryptedTextPacket->writePrimitive(certIDSize); decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); - decryptedTextPacket->writePrimitive(senderNodeUUIDByteArraySize); + decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize); decryptedTextPacket->write(certID); decryptedTextPacket->write(decryptedTextByteArray); - decryptedTextPacket->write(senderNodeUUID); + decryptedTextPacket->write(challengingNodeUUID); qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index eecf93745a..48f2394ca3 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -17,6 +17,7 @@ #include #include #include +#include #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) @@ -348,6 +349,8 @@ void ContextOverlayInterface::openInspectionCertificate() { qCWarning(context_overlay) << "Couldn't get Entity Server!"; } } else { + auto ledger = DependencyManager::get(); + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); qCDebug(context_overlay) << "Entity" << _currentEntityWithContextOverlay << "failed static certificate verification!"; } } @@ -386,6 +389,8 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) { } void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + auto ledger = DependencyManager::get(); + int certIDByteArraySize; int decryptedTextByteArraySize; @@ -398,5 +403,9 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer EntityItemID id; bool verificationSuccess = DependencyManager::get()->getTree()->verifyDecryptedNonce(certID, decryptedText, id); - qDebug() << "ZRF VERIFICATION STATUS:" << verificationSuccess; + if (verificationSuccess) { + emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); + } else { + emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED)); + } } diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index 1cf9fdfb26..f748291077 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1238,11 +1238,11 @@ void EntityTree::processChallengeOwnershipRequestPacket(ReceivedMessage& message message.readPrimitive(&encryptedTextByteArraySize); message.readPrimitive(&nodeToChallengeByteArraySize); - QString certID(message.read(certIDByteArraySize)); - QString encryptedText(message.read(encryptedTextByteArraySize)); - QUuid nodeToChallenge = QUuid::fromRfc4122(message.read(nodeToChallengeByteArraySize)); + QByteArray certID(message.read(certIDByteArraySize)); + QByteArray encryptedText(message.read(encryptedTextByteArraySize)); + QByteArray nodeToChallenge(message.read(nodeToChallengeByteArraySize)); - sendChallengeOwnershipRequestPacket(certID, encryptedText, sourceNode, nodeToChallenge); + sendChallengeOwnershipRequestPacket(certID, encryptedText, nodeToChallenge, sourceNode); } void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, const SharedNodePointer& sourceNode) { @@ -1250,15 +1250,15 @@ void EntityTree::processChallengeOwnershipReplyPacket(ReceivedMessage& message, int certIDByteArraySize; int decryptedTextByteArraySize; - int senderNodeUUIDByteArraySize; + int challengingNodeUUIDByteArraySize; message.readPrimitive(&certIDByteArraySize); message.readPrimitive(&decryptedTextByteArraySize); - message.readPrimitive(&senderNodeUUIDByteArraySize); + message.readPrimitive(&challengingNodeUUIDByteArraySize); QByteArray certID(message.read(certIDByteArraySize)); QByteArray decryptedText(message.read(decryptedTextByteArraySize)); - QUuid challengingNode = QUuid::fromRfc4122(message.read(senderNodeUUIDByteArraySize)); + QUuid challengingNode = QUuid::fromRfc4122(message.read(challengingNodeUUIDByteArraySize)); auto challengeOwnershipReplyPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, certIDByteArraySize + decryptedText.length() + 2 * sizeof(int), @@ -1304,17 +1304,15 @@ void EntityTree::sendChallengeOwnershipPacket(const QString& certID, const QStri } } -void EntityTree::sendChallengeOwnershipRequestPacket(const QString& certID, const QString& encryptedText, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge) { +void EntityTree::sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode) { auto nodeList = DependencyManager::get(); // In this case, Client A is challenging Client B. Client A is inspecting a certified entity that it wants // to make sure belongs to Avatar B. - QByteArray certIDByteArray = certID.toUtf8(); - QByteArray encryptedTextByteArray = encryptedText.toUtf8(); QByteArray senderNodeUUID = senderNode->getUUID().toRfc4122(); - int certIDByteArraySize = certIDByteArray.length(); - int encryptedTextByteArraySize = encryptedTextByteArray.length(); + int certIDByteArraySize = certID.length(); + int encryptedTextByteArraySize = encryptedText.length(); int senderNodeUUIDSize = senderNodeUUID.length(); auto challengeOwnershipPacket = NLPacket::create(PacketType::ChallengeOwnershipRequest, @@ -1323,11 +1321,11 @@ void EntityTree::sendChallengeOwnershipRequestPacket(const QString& certID, cons challengeOwnershipPacket->writePrimitive(certIDByteArraySize); challengeOwnershipPacket->writePrimitive(encryptedTextByteArraySize); challengeOwnershipPacket->writePrimitive(senderNodeUUIDSize); - challengeOwnershipPacket->write(certIDByteArray); - challengeOwnershipPacket->write(encryptedTextByteArray); + challengeOwnershipPacket->write(certID); + challengeOwnershipPacket->write(encryptedText); challengeOwnershipPacket->write(senderNodeUUID); - nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(nodeToChallenge))); + nodeList->sendPacket(std::move(challengeOwnershipPacket), *(nodeList->nodeWithUUID(QUuid::fromRfc4122(nodeToChallenge)))); } void EntityTree::validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation) { diff --git a/libraries/entities/src/EntityTree.h b/libraries/entities/src/EntityTree.h index 5eb5d5b569..86bfe984f6 100644 --- a/libraries/entities/src/EntityTree.h +++ b/libraries/entities/src/EntityTree.h @@ -381,7 +381,7 @@ protected: private: void sendChallengeOwnershipPacket(const QString& certID, const QString& ownerKey, const EntityItemID& entityItemID, const SharedNodePointer& senderNode); - void sendChallengeOwnershipRequestPacket(const QString& certID, const QString& encryptedText, const SharedNodePointer& senderNode, const QUuid& nodeToChallenge); + void sendChallengeOwnershipRequestPacket(const QByteArray& certID, const QByteArray& encryptedText, const QByteArray& nodeToChallenge, const SharedNodePointer& senderNode); void validatePop(const QString& certID, const EntityItemID& entityItemID, const SharedNodePointer& senderNode, bool isRetryingValidation); }; From 3d3c93f1a581a769001f58307e328a80d889f44f Mon Sep 17 00:00:00 2001 From: Nissim Hadar Date: Wed, 1 Nov 2017 11:23:18 -0700 Subject: [PATCH 19/46] Fixes Bug 8802 --- libraries/entities/src/EntityPropertyFlags.h | 36 +++++++++---------- .../networking/src/udt/PacketHeaders.cpp | 2 +- libraries/networking/src/udt/PacketHeaders.h | 3 +- 3 files changed, 21 insertions(+), 20 deletions(-) diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index c20b362b04..f0f22b0091 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -202,6 +202,24 @@ enum EntityPropertyList { PROP_ENTITY_INSTANCE_NUMBER, PROP_CERTIFICATE_ID, + PROP_HAZE_MODE, + + PROP_HAZE_RANGE, + PROP_HAZE_COLOR, + PROP_HAZE_GLARE_COLOR, + PROP_HAZE_ENABLE_GLARE, + PROP_HAZE_GLARE_ANGLE, + + PROP_HAZE_ALTITUDE_EFFECT, + PROP_HAZE_CEILING, + PROP_HAZE_BASE_REF, + + PROP_HAZE_BACKGROUND_BLEND, + + PROP_HAZE_ATTENUATE_KEYLIGHT, + PROP_HAZE_KEYLIGHT_RANGE, + PROP_HAZE_KEYLIGHT_ALTITUDE, + //////////////////////////////////////////////////////////////////////////////////////////////////// // ATTENTION: add new properties to end of list just ABOVE this line PROP_AFTER_LAST_ITEM, @@ -234,24 +252,6 @@ enum EntityPropertyList { PROP_STAGE_AUTOMATIC_HOURDAY = PROP_ANIMATION_FRAME_INDEX, PROP_BACKGROUND_MODE = PROP_MODEL_URL, - PROP_HAZE_MODE = PROP_COLOR, - - PROP_HAZE_RANGE = PROP_INTENSITY, - PROP_HAZE_COLOR = PROP_CUTOFF, - PROP_HAZE_GLARE_COLOR = PROP_EXPONENT, - PROP_HAZE_ENABLE_GLARE = PROP_IS_SPOTLIGHT, - PROP_HAZE_GLARE_ANGLE = PROP_DIFFUSE_COLOR, - - PROP_HAZE_ALTITUDE_EFFECT = PROP_AMBIENT_COLOR_UNUSED, - PROP_HAZE_CEILING = PROP_SPECULAR_COLOR_UNUSED, - PROP_HAZE_BASE_REF = PROP_LINEAR_ATTENUATION_UNUSED, - - PROP_HAZE_BACKGROUND_BLEND = PROP_QUADRATIC_ATTENUATION_UNUSED, - - PROP_HAZE_ATTENUATE_KEYLIGHT = PROP_ANIMATION_FRAME_INDEX, - PROP_HAZE_KEYLIGHT_RANGE = PROP_MODEL_URL, - PROP_HAZE_KEYLIGHT_ALTITUDE = PROP_ANIMATION_URL, - PROP_SKYBOX_COLOR = PROP_ANIMATION_URL, PROP_SKYBOX_URL = PROP_ANIMATION_FPS, PROP_KEYLIGHT_AMBIENT_URL = PROP_ANIMATION_PLAYING, diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 09ee41b31d..9a98393fa1 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -30,7 +30,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::EntityEdit: case PacketType::EntityData: case PacketType::EntityPhysics: - return static_cast(EntityVersion::HasDynamicOwnershipTests); + return static_cast(EntityVersion::HazeEffect); case PacketType::EntityQuery: return static_cast(EntityQueryPacketVersion::JSONFilterWithFamilyTree); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 049cb0f1a8..e00b4139c3 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -196,7 +196,8 @@ QDebug operator<<(QDebug debug, const PacketType& type); enum class EntityVersion : PacketVersion { StrokeColorProperty = 77, - HasDynamicOwnershipTests + HasDynamicOwnershipTests, + HazeEffect }; enum class EntityScriptCallMethodVersion : PacketVersion { From f061b9c2da92d9a8f85376b8c272644ab63c8209 Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Wed, 25 Oct 2017 15:11:18 -0700 Subject: [PATCH 20/46] Add stylus functionality to pointer manager --- interface/src/Application.cpp | 16 +- interface/src/raypick/JointRayPick.cpp | 2 +- interface/src/raypick/JointRayPick.h | 2 +- interface/src/raypick/LaserPointer.cpp | 44 +- interface/src/raypick/LaserPointer.h | 6 +- .../LaserPointerScriptingInterface.cpp | 6 +- .../raypick/LaserPointerScriptingInterface.h | 2 +- interface/src/raypick/MouseRayPick.cpp | 2 +- interface/src/raypick/MouseRayPick.h | 2 +- .../src/raypick/PickScriptingInterface.cpp | 7 +- .../src/raypick/PointerScriptingInterface.cpp | 41 +- .../src/raypick/PointerScriptingInterface.h | 5 +- interface/src/raypick/RayPick.h | 2 +- .../src/raypick/RayPickScriptingInterface.cpp | 7 +- interface/src/raypick/StaticRayPick.cpp | 2 +- interface/src/raypick/StaticRayPick.h | 2 +- interface/src/raypick/StylusPointer.cpp | 627 ++++++++++++++++++ interface/src/raypick/StylusPointer.h | 145 ++++ libraries/pointers/src/pointers/Pick.h | 4 +- .../src/pointers/PickCacheOptimizer.h | 16 +- .../pointers/src/pointers/PickManager.cpp | 9 +- libraries/pointers/src/pointers/PickManager.h | 8 +- libraries/pointers/src/pointers/Pointer.cpp | 10 +- libraries/pointers/src/pointers/Pointer.h | 16 +- .../pointers/src/pointers/PointerManager.cpp | 11 +- .../pointers/src/pointers/PointerManager.h | 4 +- libraries/script-engine/src/ScriptCache.cpp | 1 + libraries/shared/src/PointerEvent.h | 6 +- libraries/shared/src/RegisteredMetaTypes.h | 67 +- libraries/shared/src/shared/Bilateral.h | 21 +- scripts/defaultScripts.js | 2 +- .../controllerModules/tabletStylusInput.js | 454 +------------ 32 files changed, 1019 insertions(+), 530 deletions(-) create mode 100644 interface/src/raypick/StylusPointer.cpp create mode 100644 interface/src/raypick/StylusPointer.h diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 07962d838b..420c4e7ce6 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1819,14 +1819,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo DependencyManager::get()->setMouseRayPickResultOperator([&](QUuid rayPickID) { RayToEntityIntersectionResult entityResult; entityResult.intersects = false; - QVariantMap result = DependencyManager::get()->getPrevPickResult(rayPickID); - if (result["type"].isValid()) { - entityResult.intersects = result["type"] != PickScriptingInterface::INTERSECTED_NONE(); + auto pickResult = DependencyManager::get()->getPrevPickResultTyped(rayPickID); + if (pickResult) { + entityResult.intersects = pickResult->type != IntersectionType::NONE; if (entityResult.intersects) { - entityResult.intersection = vec3FromVariant(result["intersection"]); - entityResult.distance = result["distance"].toFloat(); - entityResult.surfaceNormal = vec3FromVariant(result["surfaceNormal"]); - entityResult.entityID = result["objectID"].toUuid(); + entityResult.intersection = pickResult->intersection; + entityResult.distance = pickResult->distance; + entityResult.surfaceNormal = pickResult->surfaceNormal; + entityResult.entityID = pickResult->objectID; entityResult.entity = DependencyManager::get()->getTree()->findEntityByID(entityResult.entityID); } } @@ -4957,7 +4957,7 @@ void Application::update(float deltaTime) { { PROFILE_RANGE(app, "PointerManager"); - DependencyManager::get()->update(); + DependencyManager::get()->update(deltaTime); } { diff --git a/interface/src/raypick/JointRayPick.cpp b/interface/src/raypick/JointRayPick.cpp index c2a7fb11e5..fdffb9796d 100644 --- a/interface/src/raypick/JointRayPick.cpp +++ b/interface/src/raypick/JointRayPick.cpp @@ -20,7 +20,7 @@ JointRayPick::JointRayPick(const std::string& jointName, const glm::vec3& posOff { } -const PickRay JointRayPick::getMathematicalPick() const { +PickRay JointRayPick::getMathematicalPick() const { auto myAvatar = DependencyManager::get()->getMyAvatar(); int jointIndex = myAvatar->getJointIndex(QString::fromStdString(_jointName)); bool useAvatarHead = _jointName == "Avatar"; diff --git a/interface/src/raypick/JointRayPick.h b/interface/src/raypick/JointRayPick.h index ab44bf67c8..0ef39d6336 100644 --- a/interface/src/raypick/JointRayPick.h +++ b/interface/src/raypick/JointRayPick.h @@ -18,7 +18,7 @@ class JointRayPick : public RayPick { public: JointRayPick(const std::string& jointName, const glm::vec3& posOffset, const glm::vec3& dirOffset, const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); - const PickRay getMathematicalPick() const override; + PickRay getMathematicalPick() const override; private: std::string _jointName; diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 83e3757514..bf1847e3f6 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -16,6 +16,7 @@ #include #include #include "PickScriptingInterface.h" +#include "RayPick.h" LaserPointer::LaserPointer(const QVariant& rayProps, const RenderStateMap& renderStates, const DefaultRenderStateMap& defaultRenderStates, bool hover, const PointerTriggers& triggers, bool faceAvatar, bool centerEndY, bool lockEnd, bool distanceScaleEnd, bool enabled) : @@ -177,17 +178,20 @@ void LaserPointer::disableRenderState(const RenderState& renderState) { } } -void LaserPointer::updateVisuals(const QVariantMap& prevRayPickResult) { - IntersectionType type = IntersectionType(prevRayPickResult["type"].toInt()); - PickRay pickRay = PickRay(prevRayPickResult["searchRay"].toMap()); - QUuid uid = prevRayPickResult["objectID"].toUuid(); +void LaserPointer::updateVisuals(const PickResultPointer& pickResult) { + auto rayPickResult = std::static_pointer_cast(pickResult); + + IntersectionType type = rayPickResult ? rayPickResult->type : IntersectionType::NONE; if (_enabled && !_currentRenderState.empty() && _renderStates.find(_currentRenderState) != _renderStates.end() && (type != IntersectionType::NONE || _laserLength > 0.0f || !_objectLockEnd.first.isNull())) { - float distance = _laserLength > 0.0f ? _laserLength : prevRayPickResult["distance"].toFloat(); + PickRay pickRay{ rayPickResult->pickVariant }; + QUuid uid = rayPickResult->objectID; + float distance = _laserLength > 0.0f ? _laserLength : rayPickResult->distance; updateRenderState(_renderStates[_currentRenderState], type, distance, uid, pickRay, false); disableRenderState(_defaultRenderStates[_currentRenderState].second); } else if (_enabled && !_currentRenderState.empty() && _defaultRenderStates.find(_currentRenderState) != _defaultRenderStates.end()) { disableRenderState(_renderStates[_currentRenderState]); + PickRay pickRay = rayPickResult ? PickRay(rayPickResult->pickVariant) : PickRay(); updateRenderState(_defaultRenderStates[_currentRenderState].second, IntersectionType::NONE, _defaultRenderStates[_currentRenderState].first, QUuid(), pickRay, true); } else if (!_currentRenderState.empty()) { disableRenderState(_renderStates[_currentRenderState]); @@ -195,8 +199,12 @@ void LaserPointer::updateVisuals(const QVariantMap& prevRayPickResult) { } } -Pointer::PickedObject LaserPointer::getHoveredObject(const QVariantMap& pickResult) { - return Pointer::PickedObject(pickResult["objectID"].toUuid(), IntersectionType(pickResult["type"].toUInt())); +Pointer::PickedObject LaserPointer::getHoveredObject(const PickResultPointer& pickResult) { + auto rayPickResult = std::static_pointer_cast(pickResult); + if (!rayPickResult) { + return PickedObject(); + } + return PickedObject(rayPickResult->objectID, rayPickResult->type); } Pointer::Buttons LaserPointer::getPressedButtons() { @@ -281,16 +289,22 @@ RenderState LaserPointer::buildRenderState(const QVariantMap& propMap) { return RenderState(startID, pathID, endID); } -PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const { +PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const { uint32_t id = 0; - glm::vec3 intersection = vec3FromVariant(pickResult["intersection"]); - glm::vec3 surfaceNormal = vec3FromVariant(pickResult["surfaceNormal"]); - QVariantMap searchRay = pickResult["searchRay"].toMap(); - glm::vec3 direction = vec3FromVariant(searchRay["direction"]); - QUuid pickedID = pickResult["objectID"].toUuid(); + QUuid pickedID; + glm::vec3 intersection, surfaceNormal, direction, origin; + if (target.type != NONE) { + auto rayPickResult = std::static_pointer_cast(pickResult); + intersection = rayPickResult->intersection; + surfaceNormal = rayPickResult->surfaceNormal; + const QVariantMap& searchRay = rayPickResult->pickVariant; + direction = vec3FromVariant(searchRay["direction"]); + origin = vec3FromVariant(searchRay["origin"]); + pickedID = rayPickResult->objectID;; + } + glm::vec2 pos2D; if (pickedID != target.objectID) { - glm::vec3 origin = vec3FromVariant(searchRay["origin"]); if (target.type == ENTITY) { intersection = intersectRayWithEntityXYPlane(target.objectID, origin, direction); } else if (target.type == OVERLAY) { @@ -355,4 +369,4 @@ glm::vec2 LaserPointer::projectOntoOverlayXYPlane(const QUuid& overlayID, const glm::vec2 LaserPointer::projectOntoEntityXYPlane(const QUuid& entityID, const glm::vec3& worldPos) const { auto props = DependencyManager::get()->getEntityProperties(entityID); return projectOntoXYPlane(worldPos, props.getPosition(), props.getRotation(), props.getDimensions(), props.getRegistrationPoint()); -} \ No newline at end of file +} diff --git a/interface/src/raypick/LaserPointer.h b/interface/src/raypick/LaserPointer.h index bdd3f2ffa0..9fbbadb475 100644 --- a/interface/src/raypick/LaserPointer.h +++ b/interface/src/raypick/LaserPointer.h @@ -65,15 +65,15 @@ public: void setLength(float length) override; void setLockEndUUID(const QUuid& objectID, bool isOverlay) override; - void updateVisuals(const QVariantMap& prevRayPickResult) override; + void updateVisuals(const PickResultPointer& prevRayPickResult) override; - PickedObject getHoveredObject(const QVariantMap& pickResult) override; + PickedObject getHoveredObject(const PickResultPointer& pickResult) override; Pointer::Buttons getPressedButtons() override; static RenderState buildRenderState(const QVariantMap& propMap); protected: - PointerEvent buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const override; + PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const override; private: PointerTriggers _triggers; diff --git a/interface/src/raypick/LaserPointerScriptingInterface.cpp b/interface/src/raypick/LaserPointerScriptingInterface.cpp index 533dffafb9..92ad837e7a 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.cpp +++ b/interface/src/raypick/LaserPointerScriptingInterface.cpp @@ -27,4 +27,8 @@ QUuid LaserPointerScriptingInterface::createLaserPointer(const QVariant& propert void LaserPointerScriptingInterface::editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const { DependencyManager::get()->editRenderState(uid, renderState, properties); -} \ No newline at end of file +} + +QVariantMap LaserPointerScriptingInterface::getPrevRayPickResult(const QUuid& uid) const { + return DependencyManager::get()->getPrevPickResult(uid); +} diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h index 1116da1528..d1dd5499f4 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.h +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -27,7 +27,7 @@ public slots: Q_INVOKABLE void removeLaserPointer(const QUuid& uid) const { DependencyManager::get()->removePointer(uid); } Q_INVOKABLE void editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const; Q_INVOKABLE void setRenderState(const QUuid& uid, const QString& renderState) const { DependencyManager::get()->setRenderState(uid, renderState.toStdString()); } - Q_INVOKABLE QVariantMap getPrevRayPickResult(QUuid uid) const { return DependencyManager::get()->getPrevPickResult(uid); } + Q_INVOKABLE QVariantMap getPrevRayPickResult(const QUuid& uid) const; Q_INVOKABLE void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const { DependencyManager::get()->setPrecisionPicking(uid, precisionPicking); } Q_INVOKABLE void setLaserLength(const QUuid& uid, float laserLength) const { DependencyManager::get()->setLength(uid, laserLength); } diff --git a/interface/src/raypick/MouseRayPick.cpp b/interface/src/raypick/MouseRayPick.cpp index f691dafc01..1f39b61614 100644 --- a/interface/src/raypick/MouseRayPick.cpp +++ b/interface/src/raypick/MouseRayPick.cpp @@ -18,7 +18,7 @@ MouseRayPick::MouseRayPick(const PickFilter& filter, const float maxDistance, co { } -const PickRay MouseRayPick::getMathematicalPick() const { +PickRay MouseRayPick::getMathematicalPick() const { QVariant position = qApp->getApplicationCompositor().getReticleInterface()->getPosition(); if (position.isValid()) { QVariantMap posMap = position.toMap(); diff --git a/interface/src/raypick/MouseRayPick.h b/interface/src/raypick/MouseRayPick.h index e9eb3ccabf..81561d7459 100644 --- a/interface/src/raypick/MouseRayPick.h +++ b/interface/src/raypick/MouseRayPick.h @@ -18,7 +18,7 @@ class MouseRayPick : public RayPick { public: MouseRayPick(const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); - const PickRay getMathematicalPick() const override; + PickRay getMathematicalPick() const override; }; #endif // hifi_MouseRayPick_h diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 40f898e65d..ac81bcf72f 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -94,7 +94,12 @@ void PickScriptingInterface::removePick(const QUuid& uid) { } QVariantMap PickScriptingInterface::getPrevPickResult(const QUuid& uid) { - return DependencyManager::get()->getPrevPickResult(uid); + QVariantMap result; + auto pickResult = DependencyManager::get()->getPrevPickResult(uid); + if (pickResult) { + result = pickResult->toVariantMap(); + } + return result; } void PickScriptingInterface::setPrecisionPicking(const QUuid& uid, const bool precisionPicking) { diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index c3a4ac164a..74f207f793 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -10,9 +10,11 @@ #include #include +#include #include "Application.h" #include "LaserPointer.h" +#include "StylusPointer.h" void PointerScriptingInterface::setIgnoreItems(const QUuid& uid, const QScriptValue& ignoreItems) const { DependencyManager::get()->setIgnoreItems(uid, qVectorQUuidFromScriptValue(ignoreItems)); @@ -21,15 +23,40 @@ void PointerScriptingInterface::setIncludeItems(const QUuid& uid, const QScriptV DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } -QUuid PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) const { +QUuid PointerScriptingInterface::createPointer(const PickQuery::PickType& type, const QVariant& properties) { + // Interaction with managers should always happen ont he main thread + if (QThread::currentThread() != qApp->thread()) { + QUuid result; + BLOCKING_INVOKE_METHOD(this, "createPointer", Q_RETURN_ARG(QUuid, result), Q_ARG(PickQuery::PickType, type), Q_ARG(QVariant, properties)); + return result; + } + switch (type) { case PickQuery::PickType::Ray: return createLaserPointer(properties); + case PickQuery::PickType::Stylus: + return createStylus(properties); default: return QUuid(); } } +QUuid PointerScriptingInterface::createStylus(const QVariant& properties) const { + bilateral::Side side = bilateral::Side::Invalid; + { + QVariant handVar = properties.toMap()["hand"]; + if (handVar.isValid()) { + side = bilateral::side(handVar.toInt()); + } + } + + if (bilateral::Side::Invalid == side) { + return QUuid(); + } + + return DependencyManager::get()->addPointer(std::make_shared(side)); +} + QUuid PointerScriptingInterface::createLaserPointer(const QVariant& properties) const { QVariantMap propertyMap = properties.toMap(); @@ -133,4 +160,14 @@ void PointerScriptingInterface::editRenderState(const QUuid& uid, const QString& } DependencyManager::get()->editRenderState(uid, renderState.toStdString(), startProps, pathProps, endProps); -} \ No newline at end of file +} + +QVariantMap PointerScriptingInterface::getPrevPickResult(const QUuid& uid) const { + QVariantMap result; + auto pickResult = DependencyManager::get()->getPrevPickResult(uid); + if (pickResult) { + result = pickResult->toVariantMap(); + } + return result; +} + diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index cc2ffbc3cc..0cba7e2a6d 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -20,15 +20,16 @@ class PointerScriptingInterface : public QObject, public Dependency { public: QUuid createLaserPointer(const QVariant& properties) const; + QUuid createStylus(const QVariant& properties) const; public slots: - Q_INVOKABLE QUuid createPointer(const PickQuery::PickType& type, const QVariant& properties) const; + Q_INVOKABLE QUuid createPointer(const PickQuery::PickType& type, const QVariant& properties); Q_INVOKABLE void enablePointer(const QUuid& uid) const { DependencyManager::get()->enablePointer(uid); } Q_INVOKABLE void disablePointer(const QUuid& uid) const { DependencyManager::get()->disablePointer(uid); } Q_INVOKABLE void removePointer(const QUuid& uid) const { DependencyManager::get()->removePointer(uid); } Q_INVOKABLE void editRenderState(const QUuid& uid, const QString& renderState, const QVariant& properties) const; Q_INVOKABLE void setRenderState(const QUuid& uid, const QString& renderState) const { DependencyManager::get()->setRenderState(uid, renderState.toStdString()); } - Q_INVOKABLE QVariantMap getPrevPickResult(const QUuid& uid) const { return DependencyManager::get()->getPrevPickResult(uid); } + Q_INVOKABLE QVariantMap getPrevPickResult(const QUuid& uid) const; Q_INVOKABLE void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const { DependencyManager::get()->setPrecisionPicking(uid, precisionPicking); } Q_INVOKABLE void setLaserLength(const QUuid& uid, float laserLength) const { DependencyManager::get()->setLength(uid, laserLength); } diff --git a/interface/src/raypick/RayPick.h b/interface/src/raypick/RayPick.h index b5d7ea7c3e..1c2b3fbb80 100644 --- a/interface/src/raypick/RayPick.h +++ b/interface/src/raypick/RayPick.h @@ -53,7 +53,7 @@ public: bool doesIntersect() const override { return intersects; } bool checkOrFilterAgainstMaxDistance(float maxDistance) override { return distance < maxDistance; } - PickResultPointer compareAndProcessNewResult(const PickResultPointer newRes) override { + PickResultPointer compareAndProcessNewResult(const PickResultPointer& newRes) override { auto newRayRes = std::static_pointer_cast(newRes); if (newRayRes->distance < distance) { return std::make_shared(*newRayRes); diff --git a/interface/src/raypick/RayPickScriptingInterface.cpp b/interface/src/raypick/RayPickScriptingInterface.cpp index 92bf3ec521..8cee02270d 100644 --- a/interface/src/raypick/RayPickScriptingInterface.cpp +++ b/interface/src/raypick/RayPickScriptingInterface.cpp @@ -37,7 +37,12 @@ void RayPickScriptingInterface::removeRayPick(const QUuid& uid) { } QVariantMap RayPickScriptingInterface::getPrevRayPickResult(const QUuid& uid) { - return DependencyManager::get()->getPrevPickResult(uid); + QVariantMap result; + auto pickResult = DependencyManager::get()->getPrevPickResult(uid); + if (pickResult) { + result = pickResult->toVariantMap(); + } + return result; } void RayPickScriptingInterface::setPrecisionPicking(const QUuid& uid, const bool precisionPicking) { diff --git a/interface/src/raypick/StaticRayPick.cpp b/interface/src/raypick/StaticRayPick.cpp index f7803aade6..c79f87ad0e 100644 --- a/interface/src/raypick/StaticRayPick.cpp +++ b/interface/src/raypick/StaticRayPick.cpp @@ -13,6 +13,6 @@ StaticRayPick::StaticRayPick(const glm::vec3& position, const glm::vec3& directi { } -const PickRay StaticRayPick::getMathematicalPick() const { +PickRay StaticRayPick::getMathematicalPick() const { return _pickRay; } \ No newline at end of file diff --git a/interface/src/raypick/StaticRayPick.h b/interface/src/raypick/StaticRayPick.h index 6dc0a809ae..ded57caf4e 100644 --- a/interface/src/raypick/StaticRayPick.h +++ b/interface/src/raypick/StaticRayPick.h @@ -15,7 +15,7 @@ class StaticRayPick : public RayPick { public: StaticRayPick(const glm::vec3& position, const glm::vec3& direction, const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); - const PickRay getMathematicalPick() const override; + PickRay getMathematicalPick() const override; private: PickRay _pickRay; diff --git a/interface/src/raypick/StylusPointer.cpp b/interface/src/raypick/StylusPointer.cpp new file mode 100644 index 0000000000..4cc34f36d9 --- /dev/null +++ b/interface/src/raypick/StylusPointer.cpp @@ -0,0 +1,627 @@ +// +// Created by Bradley Austin Davis on 2017/10/24 +// Copyright 2013-2017 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 "StylusPointer.h" + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Application.h" +#include "avatar/AvatarManager.h" +#include "avatar/MyAvatar.h" + +#include "scripting/HMDScriptingInterface.h" +#include "ui/overlays/Web3DOverlay.h" +#include "ui/overlays/Sphere3DOverlay.h" +#include "avatar/AvatarManager.h" +#include "InterfaceLogging.h" +#include "PickScriptingInterface.h" + +using namespace controller; +using namespace bilateral; + +static Setting::Handle USE_FINGER_AS_STYLUS("preferAvatarFingerOverStylus", false); +static const float WEB_STYLUS_LENGTH = 0.2f; +static const float WEB_TOUCH_Y_OFFSET = 0.105f; // how far forward (or back with a negative number) to slide stylus in hand +static const vec3 TIP_OFFSET{ 0.0f, WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, 0.0f }; +static const float TABLET_MIN_HOVER_DISTANCE = 0.01f; +static const float TABLET_MAX_HOVER_DISTANCE = 0.1f; +static const float TABLET_MIN_TOUCH_DISTANCE = -0.05f; +static const float TABLET_MAX_TOUCH_DISTANCE = TABLET_MIN_HOVER_DISTANCE; +static const float EDGE_BORDER = 0.075f; + +static const float HOVER_HYSTERESIS = 0.01f; +static const float NEAR_HYSTERESIS = 0.05f; +static const float TOUCH_HYSTERESIS = 0.002f; + +// triggered when stylus presses a web overlay/entity +static const float HAPTIC_STYLUS_STRENGTH = 1.0f; +static const float HAPTIC_STYLUS_DURATION = 20.0f; +static const float POINTER_PRESS_TO_MOVE_DELAY = 0.33f; // seconds + +static const float WEB_DISPLAY_STYLUS_DISTANCE = 0.5f; +static const float TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481f; +static const float TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED = TOUCH_PRESS_TO_MOVE_DEADSPOT * TOUCH_PRESS_TO_MOVE_DEADSPOT; + +std::array STYLUSES; + +static OverlayID getHomeButtonID() { + return DependencyManager::get()->getCurrentHomeButtonID(); +} + +static OverlayID getTabletScreenID() { + return DependencyManager::get()->getCurrentTabletScreenID(); +} + +struct SideData { + QString avatarJoint; + QString cameraJoint; + controller::StandardPoseChannel channel; + controller::Hand hand; + vec3 grabPointSphereOffset; + + int getJointIndex(bool finger) { + const auto& jointName = finger ? avatarJoint : cameraJoint; + return DependencyManager::get()->getMyAvatar()->getJointIndex(jointName); + } +}; + +static const std::array SIDES{ { { "LeftHandIndex4", + "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND", + StandardPoseChannel::LEFT_HAND, + Hand::LEFT, + { -0.04f, 0.13f, 0.039f } }, + { "RightHandIndex4", + "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND", + StandardPoseChannel::RIGHT_HAND, + Hand::RIGHT, + { 0.04f, 0.13f, 0.039f } } } }; + +static StylusTip getFingerWorldLocation(Side side) { + const auto& sideData = SIDES[index(side)]; + auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto fingerJointIndex = myAvatar->getJointIndex(sideData.avatarJoint); + + if (-1 == fingerJointIndex) { + return StylusTip(); + } + + auto fingerPosition = myAvatar->getAbsoluteJointTranslationInObjectFrame(fingerJointIndex); + auto fingerRotation = myAvatar->getAbsoluteJointRotationInObjectFrame(fingerJointIndex); + auto avatarOrientation = myAvatar->getOrientation(); + auto avatarPosition = myAvatar->getPosition(); + StylusTip result; + result.side = side; + result.orientation = avatarOrientation * fingerRotation; + result.position = avatarPosition + (avatarOrientation * fingerPosition); + return result; +} + +// controllerWorldLocation is where the controller would be, in-world, with an added offset +static StylusTip getControllerWorldLocation(Side side, float sensorToWorldScale) { + static const std::array INPUTS{ { UserInputMapper::makeStandardInput(SIDES[0].channel), + UserInputMapper::makeStandardInput(SIDES[1].channel) } }; + const auto sideIndex = index(side); + const auto& input = INPUTS[sideIndex]; + + const auto pose = DependencyManager::get()->getPose(input); + const auto& valid = pose.valid; + StylusTip result; + if (valid) { + result.side = side; + const auto& sideData = SIDES[sideIndex]; + auto myAvatar = DependencyManager::get()->getMyAvatar(); + auto controllerJointIndex = myAvatar->getJointIndex(sideData.cameraJoint); + + const auto avatarOrientation = myAvatar->getOrientation(); + const auto avatarPosition = myAvatar->getPosition(); + result.orientation = avatarOrientation * myAvatar->getAbsoluteJointRotationInObjectFrame(controllerJointIndex); + result.position = + avatarPosition + (avatarOrientation * myAvatar->getAbsoluteJointTranslationInObjectFrame(controllerJointIndex)); + // add to the real position so the grab-point is out in front of the hand, a bit + result.position += result.orientation * (sideData.grabPointSphereOffset * sensorToWorldScale); + auto worldControllerPos = avatarPosition + avatarOrientation * pose.translation; + // compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions. + auto worldControllerLinearVel = avatarOrientation * pose.velocity; + auto worldControllerAngularVel = avatarOrientation * pose.angularVelocity; + result.velocity = + worldControllerLinearVel + glm::cross(worldControllerAngularVel, result.position - worldControllerPos); + } + + return result; +} + +bool StylusPickResult::isNormalized() const { + return valid && (normalizedPosition == glm::clamp(normalizedPosition, vec3(0), vec3(1))); +} + +bool StylusPickResult::isNearNormal(float min, float max, float hystersis) const { + return valid && (distance == glm::clamp(distance, min - hystersis, max + hystersis)); +} + +bool StylusPickResult::isNear2D(float border, float hystersis) const { + return valid && position2D == glm::clamp(position2D, vec2(0) - border - hystersis, vec2(dimensions) + border + hystersis); +} + +bool StylusPickResult::isNear(float min, float max, float border, float hystersis) const { + // check to see if the projected stylusTip is within within the 2d border + return isNearNormal(min, max, hystersis) && isNear2D(border, hystersis); +} + +StylusPickResult::operator bool() const { + return valid; +} + +bool StylusPickResult::hasKeyboardFocus() const { + if (!overlayID.isNull()) { + return qApp->getOverlays().getKeyboardFocusOverlay() == overlayID; + } +#if 0 + if (!entityID.isNull()) { + return qApp->getKeyboardFocusEntity() == entityID; + } +#endif + return false; +} + +void StylusPickResult::setKeyboardFocus() const { + if (!overlayID.isNull()) { + qApp->getOverlays().setKeyboardFocusOverlay(overlayID); + qApp->setKeyboardFocusEntity(EntityItemID()); +#if 0 + } else if (!entityID.isNull()) { + qApp->getOverlays().setKeyboardFocusOverlay(OverlayID()); + qApp->setKeyboardFocusEntity(entityID); +#endif + } +} + +void StylusPickResult::sendHoverOverEvent() const { + if (!overlayID.isNull()) { + qApp->getOverlays().hoverOverOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position, + normal, -normal }); + } + // FIXME support entity +} + +void StylusPickResult::sendHoverEnterEvent() const { + if (!overlayID.isNull()) { + qApp->getOverlays().hoverEnterOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position, + normal, -normal }); + } + // FIXME support entity +} + +void StylusPickResult::sendTouchStartEvent() const { + if (!overlayID.isNull()) { + qApp->getOverlays().sendMousePressOnOverlay(overlayID, PointerEvent{ PointerEvent::Press, deviceId(), position2D, position, + normal, -normal, PointerEvent::PrimaryButton, + PointerEvent::PrimaryButton }); + } + // FIXME support entity +} + +void StylusPickResult::sendTouchEndEvent() const { + if (!overlayID.isNull()) { + qApp->getOverlays().sendMouseReleaseOnOverlay(overlayID, + PointerEvent{ PointerEvent::Release, deviceId(), position2D, position, normal, + -normal, PointerEvent::PrimaryButton }); + } + // FIXME support entity +} + +void StylusPickResult::sendTouchMoveEvent() const { + if (!overlayID.isNull()) { + qApp->getOverlays().sendMouseMoveOnOverlay(overlayID, PointerEvent{ PointerEvent::Move, deviceId(), position2D, position, + normal, -normal, PointerEvent::PrimaryButton, + PointerEvent::PrimaryButton }); + } + // FIXME support entity +} + +bool StylusPickResult::doesIntersect() const { + return true; +} + +// for example: if we want the closest result, compare based on distance +// if we want all results, combine them +// must return a new pointer +std::shared_ptr StylusPickResult::compareAndProcessNewResult(const std::shared_ptr& newRes) { + auto newStylusResult = std::static_pointer_cast(newRes); + if (newStylusResult && newStylusResult->distance < distance) { + return std::make_shared(*newStylusResult); + } else { + return std::make_shared(*this); + } +} + +// returns true if this result contains any valid results with distance < maxDistance +// can also filter out results with distance >= maxDistance +bool StylusPickResult::checkOrFilterAgainstMaxDistance(float maxDistance) { + return distance < maxDistance; +} + +uint32_t StylusPickResult::deviceId() const { + // 0 is reserved for hardware mouse + return index(tip.side) + 1; +} + +StylusPick::StylusPick(Side side) + : Pick(PickFilter(PickScriptingInterface::PICK_OVERLAYS()), FLT_MAX, true) + , _side(side) { +} + +StylusTip StylusPick::getMathematicalPick() const { + StylusTip result; + if (_useFingerInsteadOfStylus) { + result = getFingerWorldLocation(_side); + } else { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + float sensorScaleFactor = myAvatar->getSensorToWorldScale(); + result = getControllerWorldLocation(_side, sensorScaleFactor); + result.position += result.orientation * (TIP_OFFSET * sensorScaleFactor); + } + return result; +} + +PickResultPointer StylusPick::getDefaultResult(const QVariantMap& pickVariant) const { + return std::make_shared(); +} + +PickResultPointer StylusPick::getEntityIntersection(const StylusTip& pick) { + return PickResultPointer(); +} + +PickResultPointer StylusPick::getOverlayIntersection(const StylusTip& pick) { + if (!getFilter().doesPickOverlays()) { + return PickResultPointer(); + } + + std::vector results; + for (const auto& target : getIncludeItems()) { + if (target.isNull()) { + continue; + } + + auto overlay = qApp->getOverlays().getOverlay(target); + // Don't interact with non-3D or invalid overlays + if (!overlay || !overlay->is3D()) { + continue; + } + + if (!overlay->getVisible() && !getFilter().doesPickInvisible()) { + continue; + } + + auto overlayType = overlay->getType(); + auto overlay3D = std::static_pointer_cast(overlay); + const auto overlayRotation = overlay3D->getRotation(); + const auto overlayPosition = overlay3D->getPosition(); + + StylusPickResult result; + result.tip = pick; + result.overlayID = target; + result.normal = overlayRotation * Vectors::UNIT_Z; + result.distance = glm::dot(pick.position - overlayPosition, result.normal); + result.position = pick.position - (result.normal * result.distance); + if (overlayType == Web3DOverlay::TYPE) { + result.dimensions = vec3(std::static_pointer_cast(overlay3D)->getSize(), 0.01f); + } else if (overlayType == Sphere3DOverlay::TYPE) { + result.dimensions = std::static_pointer_cast(overlay3D)->getDimensions(); + } else { + result.dimensions = vec3(0.01f); + } + auto tipRelativePosition = result.position - overlayPosition; + auto localPos = glm::inverse(overlayRotation) * tipRelativePosition; + auto normalizedPosition = localPos / result.dimensions; + result.normalizedPosition = normalizedPosition + 0.5f; + result.position2D = { result.normalizedPosition.x * result.dimensions.x, + (1.0f - result.normalizedPosition.y) * result.dimensions.y }; + result.valid = true; + results.push_back(result); + } + + StylusPickResult nearestTarget; + for (const auto& result : results) { + if (result && result.isNormalized() && result.distance < nearestTarget.distance) { + nearestTarget = result; + } + } + return std::make_shared(nearestTarget); +} + +PickResultPointer StylusPick::getAvatarIntersection(const StylusTip& pick) { + return PickResultPointer(); +} + +PickResultPointer StylusPick::getHUDIntersection(const StylusTip& pick) { + return PickResultPointer(); +} + +StylusPointer::StylusPointer(Side side) + : Pointer(DependencyManager::get()->addPick(PickQuery::Stylus, std::make_shared(side)), + false, + true) + , _side(side) + , _sideData(SIDES[index(side)]) { + setIncludeItems({ { getHomeButtonID(), getTabletScreenID() } }); + STYLUSES[index(_side)] = this; +} + +StylusPointer::~StylusPointer() { + if (!_stylusOverlay.isNull()) { + qApp->getOverlays().deleteOverlay(_stylusOverlay); + } + STYLUSES[index(_side)] = nullptr; +} + +StylusPointer* StylusPointer::getOtherStylus() { + return STYLUSES[((index(_side) + 1) % 2)]; +} + +void StylusPointer::enable() { + Parent::enable(); + withWriteLock([&] { _renderingEnabled = true; }); +} + +void StylusPointer::disable() { + Parent::disable(); + withWriteLock([&] { _renderingEnabled = false; }); +} + +void StylusPointer::updateStylusTarget() { + const float minNearDistance = TABLET_MIN_TOUCH_DISTANCE * _sensorScaleFactor; + const float maxNearDistance = WEB_DISPLAY_STYLUS_DISTANCE * _sensorScaleFactor; + const float edgeBorder = EDGE_BORDER * _sensorScaleFactor; + + auto pickResult = DependencyManager::get()->getPrevPickResultTyped(_pickUID); + + if (pickResult) { + _state.target = *pickResult; + float hystersis = 0.0f; + // If we're already near the target, add hystersis to ensure we don't rapidly toggle between near and not near + // but only for the current near target + if (_previousState.nearTarget && pickResult->overlayID == _previousState.target.overlayID) { + hystersis = _nearHysteresis; + } + _state.nearTarget = pickResult->isNear(minNearDistance, maxNearDistance, edgeBorder, hystersis); + } + + // Not near anything, short circuit the rest + if (!_state.nearTarget) { + relinquishTouchFocus(); + hide(); + return; + } + + show(); + + auto minTouchDistance = TABLET_MIN_TOUCH_DISTANCE * _sensorScaleFactor; + auto maxTouchDistance = TABLET_MAX_TOUCH_DISTANCE * _sensorScaleFactor; + auto maxHoverDistance = TABLET_MAX_HOVER_DISTANCE * _sensorScaleFactor; + + float hystersis = 0.0f; + if (_previousState.nearTarget && _previousState.target.overlayID == _previousState.target.overlayID) { + hystersis = _nearHysteresis; + } + + // If we're in hover distance (calculated as the normal distance from the XY plane of the overlay) + if ((getOtherStylus() && getOtherStylus()->_state.touchingTarget) || !_state.target.isNearNormal(minTouchDistance, maxHoverDistance, hystersis)) { + relinquishTouchFocus(); + return; + } + + requestTouchFocus(_state.target); + + if (!_state.target.hasKeyboardFocus()) { + _state.target.setKeyboardFocus(); + } + + if (hasTouchFocus(_state.target) && !_previousState.touchingTarget) { + _state.target.sendHoverOverEvent(); + } + + hystersis = 0.0f; + if (_previousState.touchingTarget && _previousState.target.overlayID == _state.target.overlayID) { + hystersis = _touchHysteresis; + } + + // If we're in touch distance + if (_state.target.isNearNormal(minTouchDistance, maxTouchDistance, _touchHysteresis) && _state.target.isNormalized()) { + _state.touchingTarget = true; + } +} + +void StylusPointer::update(float deltaTime) { + // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts + withReadLock([&] { + auto myAvatar = DependencyManager::get()->getMyAvatar(); + + // Store and reset the state + { + _previousState = _state; + _state = State(); + } + +#if 0 + // Update finger as stylus setting + { + useFingerInsteadOfStylus = (USE_FINGER_AS_STYLUS.get() && myAvatar->getJointIndex(sideData.avatarJoint) != -1); + } +#endif + + // Update scale factor + { + _sensorScaleFactor = myAvatar->getSensorToWorldScale(); + _hoverHysteresis = HOVER_HYSTERESIS * _sensorScaleFactor; + _nearHysteresis = NEAR_HYSTERESIS * _sensorScaleFactor; + _touchHysteresis = TOUCH_HYSTERESIS * _sensorScaleFactor; + } + + // Identify the current near or touching target + updateStylusTarget(); + + // If we stopped touching, or if the target overlay ID changed, send a touching exit to the previous touch target + if (_previousState.touchingTarget && + (!_state.touchingTarget || _state.target.overlayID != _previousState.target.overlayID)) { + stylusTouchingExit(); + } + + // Handle new or continuing touch + if (_state.touchingTarget) { + // If we were previously not touching, or we were touching a different overlay, add a touch enter + if (!_previousState.touchingTarget || _previousState.target.overlayID != _state.target.overlayID) { + stylusTouchingEnter(); + } else { + _touchingEnterTimer += deltaTime; + } + + stylusTouching(); + } + }); + + setIncludeItems({ { getHomeButtonID(), getTabletScreenID() } }); +} + +void StylusPointer::show() { + if (!_stylusOverlay.isNull()) { + return; + } + + auto myAvatar = DependencyManager::get()->getMyAvatar(); + // FIXME perhaps instantiate a stylus and use show / hide instead of create / destroy + // however, the current design doesn't really allow for this because it assumes that + // hide / show are idempotent and low cost, but constantly querying the visibility + QVariantMap overlayProperties; + overlayProperties["name"] = "stylus"; + overlayProperties["url"] = PathUtils::resourcesPath() + "/meshes/tablet-stylus-fat.fbx"; + overlayProperties["loadPriority"] = 10.0f; + overlayProperties["dimensions"] = vec3toVariant(_sensorScaleFactor * vec3(0.01f, 0.01f, WEB_STYLUS_LENGTH)); + overlayProperties["solid"] = true; + overlayProperties["visible"] = true; + overlayProperties["ignoreRayIntersection"] = true; + overlayProperties["drawInFront"] = false; + overlayProperties["parentID"] = AVATAR_SELF_ID; + overlayProperties["parentJointIndex"] = myAvatar->getJointIndex(_sideData.cameraJoint); + + static const glm::quat X_ROT_NEG_90{ 0.70710678f, -0.70710678f, 0.0f, 0.0f }; + auto modelOrientation = _state.target.tip.orientation * X_ROT_NEG_90; + auto modelPositionOffset = modelOrientation * (vec3(0.0f, 0.0f, -WEB_STYLUS_LENGTH / 2.0f) * _sensorScaleFactor); + overlayProperties["position"] = vec3toVariant(_state.target.tip.position + modelPositionOffset); + overlayProperties["rotation"] = quatToVariant(modelOrientation); + _stylusOverlay = qApp->getOverlays().addOverlay("model", overlayProperties); +} + +void StylusPointer::hide() { + if (_stylusOverlay.isNull()) { + return; + } + + qApp->getOverlays().deleteOverlay(_stylusOverlay); + _stylusOverlay = OverlayID(); +} +#if 0 + void pointFinger(bool value) { + static const QString HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; + static const std::array KEYS{ { "pointLeftIndex", "pointLeftIndex" } }; + if (fingerPointing != value) { + QString message = QJsonDocument(QJsonObject{ { KEYS[index(side)], value } }).toJson(); + DependencyManager::get()->sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, message); + fingerPointing = value; + } + } +#endif +void StylusPointer::requestTouchFocus(const StylusPickResult& pickResult) { + if (!pickResult) { + return; + } + // send hover events to target if we can. + // record the entity or overlay we are hovering over. + if (!pickResult.overlayID.isNull() && pickResult.overlayID != _hoverOverlay && + getOtherStylus() && pickResult.overlayID != getOtherStylus()->_hoverOverlay) { + _hoverOverlay = pickResult.overlayID; + pickResult.sendHoverEnterEvent(); + } +} + +bool StylusPointer::hasTouchFocus(const StylusPickResult& pickResult) { + return (!pickResult.overlayID.isNull() && pickResult.overlayID == _hoverOverlay); +} + +void StylusPointer::relinquishTouchFocus() { + // send hover leave event. + if (!_hoverOverlay.isNull()) { + PointerEvent pointerEvent{ PointerEvent::Move, (uint32_t)(index(_side) + 1) }; + auto& overlays = qApp->getOverlays(); + overlays.sendMouseMoveOnOverlay(_hoverOverlay, pointerEvent); + overlays.sendHoverOverOverlay(_hoverOverlay, pointerEvent); + overlays.sendHoverLeaveOverlay(_hoverOverlay, pointerEvent); + _hoverOverlay = OverlayID(); + } +}; + +void StylusPointer::stealTouchFocus() { + // send hover events to target + if (getOtherStylus() && _state.target.overlayID == getOtherStylus()->_hoverOverlay) { + getOtherStylus()->relinquishTouchFocus(); + } + requestTouchFocus(_state.target); +} + +void StylusPointer::stylusTouchingEnter() { + stealTouchFocus(); + _state.target.sendTouchStartEvent(); + DependencyManager::get()->triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, + _sideData.hand); + _touchingEnterTimer = 0; + _touchingEnterPosition = _state.target.position2D; + _deadspotExpired = false; +} + +void StylusPointer::stylusTouchingExit() { + if (!_previousState.target) { + return; + } + + // special case to handle home button. + if (_previousState.target.overlayID == getHomeButtonID()) { + DependencyManager::get()->sendLocalMessage("home", _previousState.target.overlayID.toString()); + } + + // send touch end event + _state.target.sendTouchEndEvent(); +} + +void StylusPointer::stylusTouching() { + qDebug() << "QQQ " << __FUNCTION__; + if (_state.target.overlayID.isNull()) { + return; + } + + if (!_deadspotExpired) { + _deadspotExpired = + (_touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY) || + glm::distance2(_state.target.position2D, _touchingEnterPosition) > TOUCH_PRESS_TO_MOVE_DEADSPOT_SQUARED; + } + + // Only send moves if the target moves more than the deadspot position or if we've timed out the deadspot + if (_deadspotExpired) { + _state.target.sendTouchMoveEvent(); + } +} diff --git a/interface/src/raypick/StylusPointer.h b/interface/src/raypick/StylusPointer.h new file mode 100644 index 0000000000..7e67a7398e --- /dev/null +++ b/interface/src/raypick/StylusPointer.h @@ -0,0 +1,145 @@ +// +// Created by Bradley Austin Davis on 2017/10/24 +// Copyright 2013-2017 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_StylusPointer_h +#define hifi_StylusPointer_h + +#include +#include + +#include +#include +#include +#include +#include + +#include "ui/overlays/Overlay.h" + + +class StylusPick : public Pick { + using Side = bilateral::Side; +public: + StylusPick(Side side); + + StylusTip getMathematicalPick() const override; + PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const override; + PickResultPointer getEntityIntersection(const StylusTip& pick) override; + PickResultPointer getOverlayIntersection(const StylusTip& pick) override; + PickResultPointer getAvatarIntersection(const StylusTip& pick) override; + PickResultPointer getHUDIntersection(const StylusTip& pick) override; + +private: + const Side _side; + const bool _useFingerInsteadOfStylus{ false }; +}; + +struct SideData; + +struct StylusPickResult : public PickResult { + using Side = bilateral::Side; + // FIXME make into a single ID + OverlayID overlayID; + // FIXME restore entity functionality +#if 0 + EntityItemID entityID; +#endif + StylusTip tip; + float distance{ FLT_MAX }; + vec3 position; + vec2 position2D; + vec3 normal; + vec3 normalizedPosition; + vec3 dimensions; + bool valid{ false }; + + virtual bool doesIntersect() const override; + virtual std::shared_ptr compareAndProcessNewResult(const std::shared_ptr& newRes) override; + virtual bool checkOrFilterAgainstMaxDistance(float maxDistance) override; + + bool isNormalized() const; + bool isNearNormal(float min, float max, float hystersis) const; + bool isNear2D(float border, float hystersis) const; + bool isNear(float min, float max, float border, float hystersis) const; + operator bool() const; + bool hasKeyboardFocus() const; + void setKeyboardFocus() const; + void sendHoverOverEvent() const; + void sendHoverEnterEvent() const; + void sendTouchStartEvent() const; + void sendTouchEndEvent() const; + void sendTouchMoveEvent() const; + +private: + uint32_t deviceId() const; +}; + + +class StylusPointer : public Pointer { + using Parent = Pointer; + using Side = bilateral::Side; + using Ptr = std::shared_ptr; + +public: + StylusPointer(Side side); + ~StylusPointer(); + + void enable() override; + void disable() override; + void update(float deltaTime) override; + +private: + + virtual void setRenderState(const std::string& state) override {} + virtual void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) override {} + virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) override { return PickedObject(); } + virtual Buttons getPressedButtons() override { return {}; } + virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const override { return PointerEvent(); } + + + StylusPointer* getOtherStylus(); + + void updateStylusTarget(); + void requestTouchFocus(const StylusPickResult& pickResult); + bool hasTouchFocus(const StylusPickResult& pickResult); + void relinquishTouchFocus(); + void stealTouchFocus(); + void stylusTouchingEnter(); + void stylusTouchingExit(); + void stylusTouching(); + void show(); + void hide(); + + struct State { + StylusPickResult target; + bool nearTarget{ false }; + bool touchingTarget{ false }; + }; + + State _state; + State _previousState; + + float _nearHysteresis{ 0.0f }; + float _touchHysteresis{ 0.0f }; + float _hoverHysteresis{ 0.0f }; + + float _sensorScaleFactor{ 1.0f }; + float _touchingEnterTimer{ 0 }; + vec2 _touchingEnterPosition; + bool _deadspotExpired{ false }; + + bool _renderingEnabled; + OverlayID _stylusOverlay; + OverlayID _hoverOverlay; + const Side _side; + const SideData& _sideData; +}; + +#endif // hifi_StylusPointer_h + + + + diff --git a/libraries/pointers/src/pointers/Pick.h b/libraries/pointers/src/pointers/Pick.h index 9ab17f87d8..791238d601 100644 --- a/libraries/pointers/src/pointers/Pick.h +++ b/libraries/pointers/src/pointers/Pick.h @@ -118,7 +118,7 @@ public: // for example: if we want the closest result, compare based on distance // if we want all results, combine them // must return a new pointer - virtual std::shared_ptr compareAndProcessNewResult(const std::shared_ptr newRes) = 0; + virtual std::shared_ptr compareAndProcessNewResult(const std::shared_ptr& newRes) = 0; // returns true if this result contains any valid results with distance < maxDistance // can also filter out results with distance >= maxDistance @@ -198,7 +198,7 @@ class Pick : public PickQuery { public: Pick(const PickFilter& filter, const float maxDistance, const bool enabled) : PickQuery(filter, maxDistance, enabled) {} - virtual const T getMathematicalPick() const = 0; + virtual T getMathematicalPick() const = 0; virtual PickResultPointer getDefaultResult(const QVariantMap& pickVariant) const = 0; virtual PickResultPointer getEntityIntersection(const T& pick) = 0; virtual PickResultPointer getOverlayIntersection(const T& pick) = 0; diff --git a/libraries/pointers/src/pointers/PickCacheOptimizer.h b/libraries/pointers/src/pointers/PickCacheOptimizer.h index c4bf96ab51..58c3ac9098 100644 --- a/libraries/pointers/src/pointers/PickCacheOptimizer.h +++ b/libraries/pointers/src/pointers/PickCacheOptimizer.h @@ -87,7 +87,9 @@ void PickCacheOptimizer::update(QHash>& pic PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) { PickResultPointer entityRes = pick->getEntityIntersection(mathematicalPick); - cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick); + if (entityRes) { + cacheResult(entityRes->doesIntersect(), entityRes, entityKey, res, mathematicalPick, results, pick); + } } } @@ -95,7 +97,9 @@ void PickCacheOptimizer::update(QHash>& pic PickCacheKey overlayKey = { pick->getFilter().getOverlayFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, overlayKey)) { PickResultPointer overlayRes = pick->getOverlayIntersection(mathematicalPick); - cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); + if (overlayRes) { + cacheResult(overlayRes->doesIntersect(), overlayRes, overlayKey, res, mathematicalPick, results, pick); + } } } @@ -103,7 +107,9 @@ void PickCacheOptimizer::update(QHash>& pic PickCacheKey avatarKey = { pick->getFilter().getAvatarFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, avatarKey)) { PickResultPointer avatarRes = pick->getAvatarIntersection(mathematicalPick); - cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); + if (avatarRes) { + cacheResult(avatarRes->doesIntersect(), avatarRes, avatarKey, res, mathematicalPick, results, pick); + } } } @@ -112,7 +118,9 @@ void PickCacheOptimizer::update(QHash>& pic PickCacheKey hudKey = { pick->getFilter().getHUDFlags(), QVector(), QVector() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, hudKey)) { PickResultPointer hudRes = pick->getHUDIntersection(mathematicalPick); - cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick); + if (hudRes) { + cacheResult(true, hudRes, hudKey, res, mathematicalPick, results, pick); + } } } diff --git a/libraries/pointers/src/pointers/PickManager.cpp b/libraries/pointers/src/pointers/PickManager.cpp index 571f9f04cd..385115d732 100644 --- a/libraries/pointers/src/pointers/PickManager.cpp +++ b/libraries/pointers/src/pointers/PickManager.cpp @@ -40,12 +40,12 @@ void PickManager::removePick(const QUuid& uid) { }); } -QVariantMap PickManager::getPrevPickResult(const QUuid& uid) const { +PickResultPointer PickManager::getPrevPickResult(const QUuid& uid) const { auto pick = findPick(uid); - if (pick && pick->getPrevPickResult()) { - return pick->getPrevPickResult()->toVariantMap(); + if (pick) { + return pick->getPrevPickResult(); } - return QVariantMap(); + return PickResultPointer(); } void PickManager::enablePick(const QUuid& uid) const { @@ -91,4 +91,5 @@ void PickManager::update() { bool shouldPickHUD = _shouldPickHUDOperator(); _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], shouldPickHUD); + _stylusPickCacheOptimizer.update(cachedPicks[PickQuery::Stylus], false); } \ No newline at end of file diff --git a/libraries/pointers/src/pointers/PickManager.h b/libraries/pointers/src/pointers/PickManager.h index b8abb077c7..5c3ed6ef25 100644 --- a/libraries/pointers/src/pointers/PickManager.h +++ b/libraries/pointers/src/pointers/PickManager.h @@ -27,7 +27,12 @@ public: void enablePick(const QUuid& uid) const; void disablePick(const QUuid& uid) const; - QVariantMap getPrevPickResult(const QUuid& uid) const; + PickResultPointer getPrevPickResult(const QUuid& uid) const; + + template + std::shared_ptr getPrevPickResultTyped(const QUuid& uid) const { + return std::static_pointer_cast(getPrevPickResult(uid)); + } void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const; void setIgnoreItems(const QUuid& uid, const QVector& ignore) const; @@ -43,6 +48,7 @@ protected: QHash _typeMap; PickCacheOptimizer _rayPickCacheOptimizer; + PickCacheOptimizer _stylusPickCacheOptimizer; }; #endif // hifi_PickManager_h \ No newline at end of file diff --git a/libraries/pointers/src/pointers/Pointer.cpp b/libraries/pointers/src/pointers/Pointer.cpp index 8796b1e47d..8551f29dd9 100644 --- a/libraries/pointers/src/pointers/Pointer.cpp +++ b/libraries/pointers/src/pointers/Pointer.cpp @@ -30,7 +30,7 @@ void Pointer::disable() { DependencyManager::get()->disablePick(_pickUID); } -const QVariantMap Pointer::getPrevPickResult() { +PickResultPointer Pointer::getPrevPickResult() { return DependencyManager::get()->getPrevPickResult(_pickUID); } @@ -46,16 +46,16 @@ void Pointer::setIncludeItems(const QVector& includeItems) const { DependencyManager::get()->setIncludeItems(_pickUID, includeItems); } -void Pointer::update() { +void Pointer::update(float deltaTime) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { - QVariantMap pickResult = getPrevPickResult(); + auto pickResult = getPrevPickResult(); updateVisuals(pickResult); generatePointerEvents(pickResult); }); } -void Pointer::generatePointerEvents(const QVariantMap& pickResult) { +void Pointer::generatePointerEvents(const PickResultPointer& pickResult) { // TODO: avatars/HUD? auto pointerManager = DependencyManager::get(); @@ -176,4 +176,4 @@ PointerEvent::Button Pointer::chooseButton(const std::string& button) { } else { return PointerEvent::NoButtons; } -} \ No newline at end of file +} diff --git a/libraries/pointers/src/pointers/Pointer.h b/libraries/pointers/src/pointers/Pointer.h index ca35c38c7a..fa09442c83 100644 --- a/libraries/pointers/src/pointers/Pointer.h +++ b/libraries/pointers/src/pointers/Pointer.h @@ -16,6 +16,7 @@ #include #include +#include "Pick.h" #include #include "PointerEvent.h" @@ -44,7 +45,7 @@ public: virtual void enable(); virtual void disable(); - virtual const QVariantMap getPrevPickResult(); + virtual PickResultPointer getPrevPickResult(); virtual void setRenderState(const std::string& state) = 0; virtual void editRenderState(const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) = 0; @@ -57,13 +58,12 @@ public: virtual void setLength(float length) {} virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay) {} - void update(); - virtual void updateVisuals(const QVariantMap& pickResult) = 0; - void generatePointerEvents(const QVariantMap& pickResult); + virtual void update(float deltaTime); + virtual void updateVisuals(const PickResultPointer& pickResult) {} + void generatePointerEvents(const PickResultPointer& pickResult); struct PickedObject { - PickedObject() {} - PickedObject(const QUuid& objectID, IntersectionType type) : objectID(objectID), type(type) {} + PickedObject(const QUuid& objectID = QUuid(), IntersectionType type = IntersectionType::NONE) : objectID(objectID), type(type) {} QUuid objectID; IntersectionType type; @@ -71,7 +71,7 @@ public: using Buttons = std::unordered_set; - virtual PickedObject getHoveredObject(const QVariantMap& pickResult) = 0; + virtual PickedObject getHoveredObject(const PickResultPointer& pickResult) = 0; virtual Buttons getPressedButtons() = 0; QUuid getRayUID() { return _pickUID; } @@ -81,7 +81,7 @@ protected: bool _enabled; bool _hover; - virtual PointerEvent buildPointerEvent(const PickedObject& target, const QVariantMap& pickResult) const = 0; + virtual PointerEvent buildPointerEvent(const PickedObject& target, const PickResultPointer& pickResult) const = 0; private: PickedObject _prevHoveredObject; diff --git a/libraries/pointers/src/pointers/PointerManager.cpp b/libraries/pointers/src/pointers/PointerManager.cpp index 2d41543b6b..cd1273900d 100644 --- a/libraries/pointers/src/pointers/PointerManager.cpp +++ b/libraries/pointers/src/pointers/PointerManager.cpp @@ -61,21 +61,22 @@ void PointerManager::editRenderState(const QUuid& uid, const std::string& state, } } -const QVariantMap PointerManager::getPrevPickResult(const QUuid& uid) const { +PickResultPointer PointerManager::getPrevPickResult(const QUuid& uid) const { + PickResultPointer result; auto pointer = find(uid); if (pointer) { - return pointer->getPrevPickResult(); + result = pointer->getPrevPickResult(); } - return QVariantMap(); + return result; } -void PointerManager::update() { +void PointerManager::update(float deltaTime) { auto cachedPointers = resultWithReadLock>>([&] { return _pointers.values(); }); for (const auto& pointer : cachedPointers) { - pointer->update(); + pointer->update(deltaTime); } } diff --git a/libraries/pointers/src/pointers/PointerManager.h b/libraries/pointers/src/pointers/PointerManager.h index 9f477d9eb2..a9c0a47b17 100644 --- a/libraries/pointers/src/pointers/PointerManager.h +++ b/libraries/pointers/src/pointers/PointerManager.h @@ -29,7 +29,7 @@ public: void disablePointer(const QUuid& uid) const; void setRenderState(const QUuid& uid, const std::string& renderState) const; void editRenderState(const QUuid& uid, const std::string& state, const QVariant& startProps, const QVariant& pathProps, const QVariant& endProps) const; - const QVariantMap getPrevPickResult(const QUuid& uid) const; + PickResultPointer getPrevPickResult(const QUuid& uid) const; void setPrecisionPicking(const QUuid& uid, bool precisionPicking) const; void setIgnoreItems(const QUuid& uid, const QVector& ignoreEntities) const; @@ -38,7 +38,7 @@ public: void setLength(const QUuid& uid, float length) const; void setLockEndUUID(const QUuid& uid, const QUuid& objectID, bool isOverlay) const; - void update(); + void update(float deltaTime); private: std::shared_ptr find(const QUuid& uid) const; diff --git a/libraries/script-engine/src/ScriptCache.cpp b/libraries/script-engine/src/ScriptCache.cpp index dba2db0458..8e3cebfbf2 100644 --- a/libraries/script-engine/src/ScriptCache.cpp +++ b/libraries/script-engine/src/ScriptCache.cpp @@ -69,6 +69,7 @@ void ScriptCache::getScriptContents(const QString& scriptOrURL, contentAvailable #ifdef THREAD_DEBUGGING qCDebug(scriptengine) << "ScriptCache::getScriptContents() on thread [" << QThread::currentThread() << "] expected thread [" << thread() << "]"; #endif + forceDownload = true; QUrl unnormalizedURL(scriptOrURL); QUrl url = DependencyManager::get()->normalizeURL(unnormalizedURL); diff --git a/libraries/shared/src/PointerEvent.h b/libraries/shared/src/PointerEvent.h index 074e5ab79b..73e37c7509 100644 --- a/libraries/shared/src/PointerEvent.h +++ b/libraries/shared/src/PointerEvent.h @@ -37,9 +37,9 @@ public: PointerEvent(); PointerEvent(EventType type, uint32_t id, - const glm::vec2& pos2D, const glm::vec3& pos3D, - const glm::vec3& normal, const glm::vec3& direction, - Button button, uint32_t buttons = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::KeyboardModifier::NoModifier); + const glm::vec2& pos2D = glm::vec2(), const glm::vec3& pos3D = glm::vec3(), + const glm::vec3& normal = glm::vec3(), const glm::vec3& direction = glm::vec3(), + Button button = NoButtons, uint32_t buttons = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::KeyboardModifier::NoModifier); static QScriptValue toScriptValue(QScriptEngine* engine, const PointerEvent& event); static void fromScriptValue(const QScriptValue& object, PointerEvent& event); diff --git a/libraries/shared/src/RegisteredMetaTypes.h b/libraries/shared/src/RegisteredMetaTypes.h index a716c9231e..c4ac7aba14 100644 --- a/libraries/shared/src/RegisteredMetaTypes.h +++ b/libraries/shared/src/RegisteredMetaTypes.h @@ -21,6 +21,7 @@ #include "AACube.h" #include "SharedUtil.h" +#include "shared/Bilateral.h" class QColor; class QUrl; @@ -152,17 +153,73 @@ public: return pickRay; } }; + +struct StylusTip : public MathPick { + bilateral::Side side{ bilateral::Side::Invalid }; + glm::vec3 position; + glm::quat orientation; + glm::vec3 velocity; + virtual operator bool() const override { return side != bilateral::Side::Invalid; } + QVariantMap toVariantMap() const override { + QVariantMap pickRay; + pickRay["position"] = vec3toVariant(position); + pickRay["orientation"] = quatToVariant(orientation); + pickRay["velocity"] = vec3toVariant(velocity); + return pickRay; + } +}; + + namespace std { + inline void hash_combine(std::size_t& seed) { } + + template + inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + hash_combine(seed, rest...); + } + template <> - struct hash { - size_t operator()(const glm::vec3& a) const { - return ((hash()(a.x) ^ (hash()(a.y) << 1)) >> 1) ^ (hash()(a.z) << 1); + struct hash { + size_t operator()(const bilateral::Side& a) const { + return std::hash()((int)a); } }; - template <> + + template <> + struct hash { + size_t operator()(const glm::vec3& a) const { + size_t result = 0; + hash_combine(result, a.x, a.y, a.z); + return result; + } + }; + + template <> + struct hash { + size_t operator()(const glm::quat& a) const { + size_t result = 0; + hash_combine(result, a.x, a.y, a.z, a.w); + return result; + } + }; + + template <> struct hash { size_t operator()(const PickRay& a) const { - return (hash()(a.origin) ^ (hash()(a.direction) << 1)); + size_t result = 0; + hash_combine(result, a.origin, a.direction); + return result; + } + }; + + template <> + struct hash { + size_t operator()(const StylusTip& a) const { + size_t result = 0; + hash_combine(result, a.side, a.position, a.orientation, a.velocity); + return result; } }; } diff --git a/libraries/shared/src/shared/Bilateral.h b/libraries/shared/src/shared/Bilateral.h index edcaa49540..1ecc7f3d48 100644 --- a/libraries/shared/src/shared/Bilateral.h +++ b/libraries/shared/src/shared/Bilateral.h @@ -11,7 +11,8 @@ namespace bilateral { enum class Side { Left = 0, - Right = 1 + Right = 1, + Invalid = -1 }; using Indices = Side; @@ -27,8 +28,10 @@ namespace bilateral { return 0x01; case Side::Right: return 0x02; + default: + break; } - return std::numeric_limits::max(); + return 0x00; } inline uint8_t index(Side side) { @@ -37,10 +40,24 @@ namespace bilateral { return 0; case Side::Right: return 1; + default: + break; } return std::numeric_limits::max(); } + inline Side side(int index) { + switch (index) { + case 0: + return Side::Left; + case 1: + return Side::Right; + default: + break; + } + return Side::Invalid; + } + template void for_each_side(F f) { f(Side::Left); diff --git a/scripts/defaultScripts.js b/scripts/defaultScripts.js index 1243ed28e2..61e520af21 100644 --- a/scripts/defaultScripts.js +++ b/scripts/defaultScripts.js @@ -32,7 +32,7 @@ var DEFAULT_SCRIPTS_COMBINED = [ "system/tablet-ui/tabletUI.js" ]; var DEFAULT_SCRIPTS_SEPARATE = [ - //"system/controllers/controllerScripts.js" + "system/controllers/controllerScripts.js" // "system/chat.js" ]; diff --git a/scripts/system/controllers/controllerModules/tabletStylusInput.js b/scripts/system/controllers/controllerModules/tabletStylusInput.js index 46b630d023..dc41a5453e 100644 --- a/scripts/system/controllers/controllerModules/tabletStylusInput.js +++ b/scripts/system/controllers/controllerModules/tabletStylusInput.js @@ -16,256 +16,16 @@ Script.include("/~/system/libraries/controllerDispatcherUtils.js"); Script.include("/~/system/libraries/controllers.js"); (function() { - var TouchEventUtils = Script.require("/~/system/libraries/touchEventUtils.js"); - // triggered when stylus presses a web overlay/entity - var HAPTIC_STYLUS_STRENGTH = 1.0; - var HAPTIC_STYLUS_DURATION = 20.0; - - var WEB_DISPLAY_STYLUS_DISTANCE = 0.5; - var WEB_STYLUS_LENGTH = 0.2; - var WEB_TOUCH_Y_OFFSET = 0.105; // how far forward (or back with a negative number) to slide stylus in hand - - function isNearStylusTarget(stylusTargets, edgeBorder, minNormalDistance, maxNormalDistance) { - for (var i = 0; i < stylusTargets.length; i++) { - var stylusTarget = stylusTargets[i]; - - // check to see if the projected stylusTip is within within the 2d border - var borderMin = {x: -edgeBorder, y: -edgeBorder}; - var borderMax = {x: stylusTarget.dimensions.x + edgeBorder, y: stylusTarget.dimensions.y + edgeBorder}; - if (stylusTarget.distance >= minNormalDistance && stylusTarget.distance <= maxNormalDistance && - stylusTarget.position2D.x >= borderMin.x && stylusTarget.position2D.y >= borderMin.y && - stylusTarget.position2D.x <= borderMax.x && stylusTarget.position2D.y <= borderMax.y) { - return true; - } - } - return false; - } - - function calculateNearestStylusTarget(stylusTargets) { - var nearestStylusTarget; - - for (var i = 0; i < stylusTargets.length; i++) { - var stylusTarget = stylusTargets[i]; - - if ((!nearestStylusTarget || stylusTarget.distance < nearestStylusTarget.distance) && - stylusTarget.normalizedPosition.x >= 0 && stylusTarget.normalizedPosition.y >= 0 && - stylusTarget.normalizedPosition.x <= 1 && stylusTarget.normalizedPosition.y <= 1) { - nearestStylusTarget = stylusTarget; - } - } - - return nearestStylusTarget; - } - - function getFingerWorldLocation(hand) { - var fingerJointName = (hand === RIGHT_HAND) ? "RightHandIndex4" : "LeftHandIndex4"; - - var fingerJointIndex = MyAvatar.getJointIndex(fingerJointName); - var fingerPosition = MyAvatar.getAbsoluteJointTranslationInObjectFrame(fingerJointIndex); - var fingerRotation = MyAvatar.getAbsoluteJointRotationInObjectFrame(fingerJointIndex); - var worldFingerRotation = Quat.multiply(MyAvatar.orientation, fingerRotation); - var worldFingerPosition = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, fingerPosition)); - - return { - position: worldFingerPosition, - orientation: worldFingerRotation, - rotation: worldFingerRotation, - valid: true - }; - } - - function distance2D(a, b) { - var dx = (a.x - b.x); - var dy = (a.y - b.y); - return Math.sqrt(dx * dx + dy * dy); - } - - function TabletStylusInput(hand) { + function TabletStylusInput(hand) { this.hand = hand; - this.previousStylusTouchingTarget = false; - this.stylusTouchingTarget = false; - - this.useFingerInsteadOfStylus = false; - this.fingerPointing = false; - - // initialize stylus tip - var DEFAULT_STYLUS_TIP = { - position: {x: 0, y: 0, z: 0}, - orientation: {x: 0, y: 0, z: 0, w: 0}, - rotation: {x: 0, y: 0, z: 0, w: 0}, - velocity: {x: 0, y: 0, z: 0}, - valid: false - }; - this.stylusTip = DEFAULT_STYLUS_TIP; - this.parameters = makeDispatcherModuleParameters( 100, this.hand === RIGHT_HAND ? ["rightHand"] : ["leftHand"], [], 100); - - this.getOtherHandController = function() { - return (this.hand === RIGHT_HAND) ? leftTabletStylusInput : rightTabletStylusInput; - }; - - this.handToController = function() { - return (this.hand === RIGHT_HAND) ? Controller.Standard.RightHand : Controller.Standard.LeftHand; - }; - - this.updateFingerAsStylusSetting = function () { - var DEFAULT_USE_FINGER_AS_STYLUS = false; - var USE_FINGER_AS_STYLUS = Settings.getValue("preferAvatarFingerOverStylus"); - if (USE_FINGER_AS_STYLUS === "") { - USE_FINGER_AS_STYLUS = DEFAULT_USE_FINGER_AS_STYLUS; - } - if (USE_FINGER_AS_STYLUS && MyAvatar.getJointIndex("LeftHandIndex4") !== -1) { - this.useFingerInsteadOfStylus = true; - } else { - this.useFingerInsteadOfStylus = false; - } - }; - - this.updateStylusTip = function() { - if (this.useFingerInsteadOfStylus) { - this.stylusTip = getFingerWorldLocation(this.hand); - } else { - this.stylusTip = getControllerWorldLocation(this.handToController(), true); - - // translate tip forward according to constant. - var TIP_OFFSET = Vec3.multiply(MyAvatar.sensorToWorldScale, {x: 0, y: WEB_STYLUS_LENGTH - WEB_TOUCH_Y_OFFSET, z: 0}); - this.stylusTip.position = Vec3.sum(this.stylusTip.position, - Vec3.multiplyQbyV(this.stylusTip.orientation, TIP_OFFSET)); - } - - // compute tip velocity from hand controller motion, it is more accurate than computing it from previous positions. - var pose = Controller.getPoseValue(this.handToController()); - if (pose.valid) { - var worldControllerPos = Vec3.sum(MyAvatar.position, Vec3.multiplyQbyV(MyAvatar.orientation, pose.translation)); - var worldControllerLinearVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.velocity); - var worldControllerAngularVel = Vec3.multiplyQbyV(MyAvatar.orientation, pose.angularVelocity); - var tipVelocity = Vec3.sum(worldControllerLinearVel, Vec3.cross(worldControllerAngularVel, - Vec3.subtract(this.stylusTip.position, worldControllerPos))); - this.stylusTip.velocity = tipVelocity; - } else { - this.stylusTip.velocity = {x: 0, y: 0, z: 0}; - } - }; - - this.showStylus = function() { - if (this.stylus) { - var X_ROT_NEG_90 = { x: -0.70710678, y: 0, z: 0, w: 0.70710678 }; - var modelOrientation = Quat.multiply(this.stylusTip.orientation, X_ROT_NEG_90); - var modelOrientationAngles = Quat.safeEulerAngles(modelOrientation); - - var rotation = Overlays.getProperty(this.stylus, "rotation"); - var rotationAngles = Quat.safeEulerAngles(rotation); - - if(!Vec3.withinEpsilon(modelOrientationAngles, rotationAngles, 1)) { - var modelPositionOffset = Vec3.multiplyQbyV(modelOrientation, { x: 0, y: 0, z: MyAvatar.sensorToWorldScale * -WEB_STYLUS_LENGTH / 2 }); - - var updatedStylusProperties = { - position: Vec3.sum(this.stylusTip.position, modelPositionOffset), - rotation: modelOrientation, - dimensions: Vec3.multiply(MyAvatar.sensorToWorldScale, { x: 0.01, y: 0.01, z: WEB_STYLUS_LENGTH }), - }; - - Overlays.editOverlay(this.stylus, updatedStylusProperties); - } - - return; - } - - var X_ROT_NEG_90 = { x: -0.70710678, y: 0, z: 0, w: 0.70710678 }; - var modelOrientation = Quat.multiply(this.stylusTip.orientation, X_ROT_NEG_90); - var modelPositionOffset = Vec3.multiplyQbyV(modelOrientation, { x: 0, y: 0, z: MyAvatar.sensorToWorldScale * -WEB_STYLUS_LENGTH / 2 }); - - var stylusProperties = { - name: "stylus", - url: Script.resourcesPath() + "meshes/tablet-stylus-fat.fbx", - loadPriority: 10.0, - position: Vec3.sum(this.stylusTip.position, modelPositionOffset), - rotation: modelOrientation, - dimensions: Vec3.multiply(MyAvatar.sensorToWorldScale, { x: 0.01, y: 0.01, z: WEB_STYLUS_LENGTH }), - solid: true, - visible: true, - ignoreRayIntersection: true, - drawInFront: false, - parentID: MyAvatar.SELF_ID, - parentJointIndex: MyAvatar.getJointIndex(this.hand === RIGHT_HAND ? - "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND" : - "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND") - }; - this.stylus = Overlays.addOverlay("model", stylusProperties); - }; - - this.hideStylus = function() { - if (!this.stylus) { - return; - } - Overlays.deleteOverlay(this.stylus); - this.stylus = null; - }; - - this.stealTouchFocus = function(stylusTarget) { - // send hover events to target - // record the entity or overlay we are hovering over. - if ((stylusTarget.entityID === this.getOtherHandController().hoverEntity) || - (stylusTarget.overlayID === this.getOtherHandController().hoverOverlay)) { - this.getOtherHandController().relinquishTouchFocus(); - } - this.requestTouchFocus(stylusTarget); - }; - - this.requestTouchFocus = function(stylusTarget) { - - // send hover events to target if we can. - // record the entity or overlay we are hovering over. - if (stylusTarget.entityID && - stylusTarget.entityID !== this.hoverEntity && - stylusTarget.entityID !== this.getOtherHandController().hoverEntity) { - this.hoverEntity = stylusTarget.entityID; - TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, stylusTarget); - } else if (stylusTarget.overlayID && - stylusTarget.overlayID !== this.hoverOverlay && - stylusTarget.overlayID !== this.getOtherHandController().hoverOverlay) { - this.hoverOverlay = stylusTarget.overlayID; - TouchEventUtils.sendHoverEnterEventToTouchTarget(this.hand, stylusTarget); - } - }; - - this.hasTouchFocus = function(stylusTarget) { - return ((stylusTarget.entityID && stylusTarget.entityID === this.hoverEntity) || - (stylusTarget.overlayID && stylusTarget.overlayID === this.hoverOverlay)); - }; - - this.relinquishTouchFocus = function() { - // send hover leave event. - var pointerEvent = { type: "Move", id: this.hand + 1 }; - if (this.hoverEntity) { - Entities.sendHoverLeaveEntity(this.hoverEntity, pointerEvent); - this.hoverEntity = null; - } else if (this.hoverOverlay) { - Overlays.sendMouseMoveOnOverlay(this.hoverOverlay, pointerEvent); - Overlays.sendHoverOverOverlay(this.hoverOverlay, pointerEvent); - Overlays.sendHoverLeaveOverlay(this.hoverOverlay, pointerEvent); - this.hoverOverlay = null; - } - }; - - this.pointFinger = function(value) { - var HIFI_POINT_INDEX_MESSAGE_CHANNEL = "Hifi-Point-Index"; - if (this.fingerPointing !== value) { - var message; - if (this.hand === RIGHT_HAND) { - message = { pointRightIndex: value }; - } else { - message = { pointLeftIndex: value }; - } - Messages.sendMessage(HIFI_POINT_INDEX_MESSAGE_CHANNEL, JSON.stringify(message), true); - this.fingerPointing = value; - } - }; + + this.pointer = Pointers.createPointer(PickType.Stylus, { hand: this.hand }); this.otherModuleNeedsToRun = function(controllerData) { var grabOverlayModuleName = this.hand === RIGHT_HAND ? "RightNearParentingGrabOverlay" : "LeftNearParentingGrabOverlay"; @@ -277,189 +37,6 @@ Script.include("/~/system/libraries/controllers.js"); return grabOverlayModuleReady.active || farGrabModuleReady.active; }; - this.processStylus = function(controllerData) { - this.updateStylusTip(); - - if (!this.stylusTip.valid || this.overlayLaserActive(controllerData) || this.otherModuleNeedsToRun(controllerData)) { - this.pointFinger(false); - this.hideStylus(); - this.stylusTouchingTarget = false; - this.relinquishTouchFocus(); - return false; - } - - if (this.useFingerInsteadOfStylus) { - this.hideStylus(); - } - - // build list of stylus targets, near the stylusTip - var stylusTargets = []; - var candidateEntities = controllerData.nearbyEntityProperties; - var i, props, stylusTarget; - for (i = 0; i < candidateEntities.length; i++) { - props = candidateEntities[i]; - if (props && props.type === "Web") { - stylusTarget = TouchEventUtils.calculateTouchTargetFromEntity(this.stylusTip, candidateEntities[i]); - if (stylusTarget) { - stylusTargets.push(stylusTarget); - } - } - } - - // add the tabletScreen, if it is valid - if (HMD.tabletScreenID && HMD.tabletScreenID !== Uuid.NULL && - Overlays.getProperty(HMD.tabletScreenID, "visible")) { - stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.tabletScreenID); - if (stylusTarget) { - stylusTargets.push(stylusTarget); - } - } - - // add the tablet home button. - if (HMD.homeButtonID && HMD.homeButtonID !== Uuid.NULL && - Overlays.getProperty(HMD.homeButtonID, "visible")) { - stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, HMD.homeButtonID); - if (stylusTarget) { - stylusTargets.push(stylusTarget); - } - } - - var TABLET_MIN_HOVER_DISTANCE = 0.01; - var TABLET_MAX_HOVER_DISTANCE = 0.1; - var TABLET_MIN_TOUCH_DISTANCE = -0.05; - var TABLET_MAX_TOUCH_DISTANCE = TABLET_MIN_HOVER_DISTANCE; - var EDGE_BORDER = 0.075; - - var hysteresisOffset = 0.0; - if (this.isNearStylusTarget) { - hysteresisOffset = 0.05; - } - - var sensorScaleFactor = MyAvatar.sensorToWorldScale; - this.isNearStylusTarget = isNearStylusTarget(stylusTargets, - (EDGE_BORDER + hysteresisOffset) * sensorScaleFactor, - (TABLET_MIN_TOUCH_DISTANCE - hysteresisOffset) * sensorScaleFactor, - (WEB_DISPLAY_STYLUS_DISTANCE + hysteresisOffset) * sensorScaleFactor); - - if (this.isNearStylusTarget) { - if (!this.useFingerInsteadOfStylus) { - this.showStylus(); - } else { - this.pointFinger(true); - } - } else { - this.hideStylus(); - this.pointFinger(false); - } - - var nearestStylusTarget = calculateNearestStylusTarget(stylusTargets); - - var SCALED_TABLET_MIN_TOUCH_DISTANCE = TABLET_MIN_TOUCH_DISTANCE * sensorScaleFactor; - var SCALED_TABLET_MAX_TOUCH_DISTANCE = TABLET_MAX_TOUCH_DISTANCE * sensorScaleFactor; - var SCALED_TABLET_MAX_HOVER_DISTANCE = TABLET_MAX_HOVER_DISTANCE * sensorScaleFactor; - - if (nearestStylusTarget && nearestStylusTarget.distance > SCALED_TABLET_MIN_TOUCH_DISTANCE && - nearestStylusTarget.distance < SCALED_TABLET_MAX_HOVER_DISTANCE && !this.getOtherHandController().stylusTouchingTarget) { - - this.requestTouchFocus(nearestStylusTarget); - - if (!TouchEventUtils.touchTargetHasKeyboardFocus(nearestStylusTarget)) { - TouchEventUtils.setKeyboardFocusOnTouchTarget(nearestStylusTarget); - } - - if (this.hasTouchFocus(nearestStylusTarget) && !this.stylusTouchingTarget) { - TouchEventUtils.sendHoverOverEventToTouchTarget(this.hand, nearestStylusTarget); - } - - // filter out presses when tip is moving away from tablet. - // ensure that stylus is within bounding box by checking normalizedPosition - if (nearestStylusTarget.valid && nearestStylusTarget.distance > SCALED_TABLET_MIN_TOUCH_DISTANCE && - nearestStylusTarget.distance < SCALED_TABLET_MAX_TOUCH_DISTANCE && - Vec3.dot(this.stylusTip.velocity, nearestStylusTarget.normal) < 0 && - nearestStylusTarget.normalizedPosition.x >= 0 && nearestStylusTarget.normalizedPosition.x <= 1 && - nearestStylusTarget.normalizedPosition.y >= 0 && nearestStylusTarget.normalizedPosition.y <= 1) { - - this.stylusTarget = nearestStylusTarget; - this.stylusTouchingTarget = true; - } - } else { - this.relinquishTouchFocus(); - } - - this.homeButtonTouched = false; - - if (this.isNearStylusTarget) { - return true; - } else { - this.pointFinger(false); - this.hideStylus(); - return false; - } - }; - - this.stylusTouchingEnter = function () { - this.stealTouchFocus(this.stylusTarget); - TouchEventUtils.sendTouchStartEventToTouchTarget(this.hand, this.stylusTarget); - Controller.triggerHapticPulse(HAPTIC_STYLUS_STRENGTH, HAPTIC_STYLUS_DURATION, this.hand); - - this.touchingEnterTimer = 0; - this.touchingEnterStylusTarget = this.stylusTarget; - this.deadspotExpired = false; - - var TOUCH_PRESS_TO_MOVE_DEADSPOT = 0.0481; - this.deadspotRadius = TOUCH_PRESS_TO_MOVE_DEADSPOT; - }; - - this.stylusTouchingExit = function () { - - if (this.stylusTarget === undefined) { - return; - } - - // special case to handle home button. - if (this.stylusTarget.overlayID === HMD.homeButtonID) { - Messages.sendLocalMessage("home", this.stylusTarget.overlayID); - } - - // send press event - if (this.deadspotExpired) { - TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.stylusTarget); - } else { - TouchEventUtils.sendTouchEndEventToTouchTarget(this.hand, this.touchingEnterStylusTarget); - } - }; - - this.stylusTouching = function (controllerData, dt) { - - this.touchingEnterTimer += dt; - - if (this.stylusTarget.entityID) { - this.stylusTarget = TouchEventUtils.calculateTouchTargetFromEntity(this.stylusTip, this.stylusTarget.entityProps); - } else if (this.stylusTarget.overlayID) { - this.stylusTarget = TouchEventUtils.calculateTouchTargetFromOverlay(this.stylusTip, this.stylusTarget.overlayID); - } - - var TABLET_MIN_TOUCH_DISTANCE = -0.1; - var TABLET_MAX_TOUCH_DISTANCE = 0.01; - - if (this.stylusTarget) { - if (this.stylusTarget.distance > TABLET_MIN_TOUCH_DISTANCE && - this.stylusTarget.distance < TABLET_MAX_TOUCH_DISTANCE) { - var POINTER_PRESS_TO_MOVE_DELAY = 0.33; // seconds - if (this.deadspotExpired || this.touchingEnterTimer > POINTER_PRESS_TO_MOVE_DELAY || - distance2D(this.stylusTarget.position2D, - this.touchingEnterStylusTarget.position2D) > this.deadspotRadius) { - TouchEventUtils.sendTouchMoveEventToTouchTarget(this.hand, this.stylusTarget); - this.deadspotExpired = true; - } - } else { - this.stylusTouchingTarget = false; - } - } else { - this.stylusTouchingTarget = false; - } - }; - this.overlayLaserActive = function(controllerData) { var rightOverlayLaserModule = getEnabledModuleByName("RightOverlayLaserInput"); var leftOverlayLaserModule = getEnabledModuleByName("LeftOverlayLaserInput"); @@ -469,7 +46,7 @@ Script.include("/~/system/libraries/controllers.js"); }; this.isReady = function (controllerData) { - if (this.processStylus(controllerData)) { + if (!this.overlayLaserActive(controllerData) && !this.otherModuleNeedsToRun(controllerData)) { return makeRunningValues(true, [], []); } else { return makeRunningValues(false, [], []); @@ -477,28 +54,11 @@ Script.include("/~/system/libraries/controllers.js"); }; this.run = function (controllerData, deltaTime) { - this.updateFingerAsStylusSetting(); - - if (!this.previousStylusTouchingTarget && this.stylusTouchingTarget) { - this.stylusTouchingEnter(); - } - if (this.previousStylusTouchingTarget && !this.stylusTouchingTarget) { - this.stylusTouchingExit(); - } - this.previousStylusTouchingTarget = this.stylusTouchingTarget; - - if (this.stylusTouchingTarget) { - this.stylusTouching(controllerData, deltaTime); - } - if (this.processStylus(controllerData)) { - return makeRunningValues(true, [], []); - } else { - return makeRunningValues(false, [], []); - } + return this.isReady(controllerData); }; - + this.cleanup = function () { - this.hideStylus(); + Pointers.createPointer(this.pointer); }; } From b335ba9a75f92c50a2fdde765201f5203b16da66 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 1 Nov 2017 11:34:29 -0700 Subject: [PATCH 21/46] Timeout timer --- .../InspectionCertificate.qml | 25 ++++++++ interface/src/commerce/Ledger.h | 18 +++--- interface/src/commerce/QmlCommerce.cpp | 1 + interface/src/commerce/QmlCommerce.h | 2 + interface/src/commerce/Wallet.cpp | 62 +++++++++---------- .../ui/overlays/ContextOverlayInterface.cpp | 40 ++++++++---- .../src/ui/overlays/ContextOverlayInterface.h | 5 ++ libraries/entities/src/EntityTree.cpp | 4 +- 8 files changed, 105 insertions(+), 52 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index aa1372494f..06e04d6929 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -65,6 +65,31 @@ Rectangle { } } } + + onUpdateCertificateStatus: { + if (root.certificateId === certID) { + if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS + + } else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT + errorText.text = "Verification of this certificate timed out."; + errorText.color = hifi.colors.redHighlight; + } else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED + titleBarText.text = "Invalid Certificate"; + titleBarText.color = hifi.colors.redHighlight; + root.itemEdition = "Uncertified Copy"; + errorText.text = "The certificate associated with this entity is invalid."; + errorText.color = hifi.colors.redHighlight; + } else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED + titleBarText.text = "Invalid Certificate"; + titleBarText.color = hifi.colors.redHighlight; + root.itemEdition = "Uncertified Copy"; + errorText.text = "The certificate associated with this entity is invalid."; + errorText.color = hifi.colors.redHighlight; + } else { + console.log("Unknown certificate status received from ledger signal!"); + } + } + } } onCertificateIdChanged: { diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 75f6fb1ba8..42eb0ffc49 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -35,6 +35,14 @@ public: void updateLocation(const QString& asset_id, const QString location, const bool controlledFailure = false); void certificateInfo(const QString& certificateId); + enum CertificateStatus { + CERTIFICATE_STATUS_UNKNOWN = 0, + CERTIFICATE_STATUS_VERIFICATION_SUCCESS, + CERTIFICATE_STATUS_VERIFICATION_TIMEOUT, + CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED, + CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED, + }; + signals: void buyResult(QJsonObject result); void receiveAtResult(QJsonObject result); @@ -45,6 +53,8 @@ signals: void locationUpdateResult(QJsonObject result); void certificateInfoResult(QJsonObject result); + void updateCertificateStatus(const QString& certID, uint certStatus); + public slots: void buySuccess(QNetworkReply& reply); void buyFailure(QNetworkReply& reply); @@ -65,14 +75,6 @@ public slots: void certificateInfoSuccess(QNetworkReply& reply); void certificateInfoFailure(QNetworkReply& reply); - void updateCertificateStatus(const QString& certID, uint certStatus); - enum CertificateStatus { - CERTIFICATE_STATUS_UNKNOWN = 0, - CERTIFICATE_STATUS_VERIFICATION_SUCCESS, - CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED, - CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED, - }; - private: QJsonObject apiResponse(const QString& label, QNetworkReply& reply); QJsonObject failResponse(const QString& label, QNetworkReply& reply); diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index 803264fa9f..ac5d0e6a2d 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -30,6 +30,7 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(ledger.data(), &Ledger::accountResult, this, &QmlCommerce::accountResult); connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult); connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult); + connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus); } void QmlCommerce::getWalletStatus() { diff --git a/interface/src/commerce/QmlCommerce.h b/interface/src/commerce/QmlCommerce.h index ae63133425..d4f4aa35d2 100644 --- a/interface/src/commerce/QmlCommerce.h +++ b/interface/src/commerce/QmlCommerce.h @@ -45,6 +45,8 @@ signals: void accountResult(QJsonObject result); void certificateInfoResult(QJsonObject result); + void updateCertificateStatus(const QString& certID, uint certStatus); + protected: Q_INVOKABLE void getWalletStatus(); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index c31184eb56..c6d77982b8 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -740,6 +740,8 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack RSA* rsa = readKeys(keyFilePath().toStdString().c_str()); if (rsa) { + auto nodeList = DependencyManager::get(); + ERR_clear_error(); const int decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize, reinterpret_cast(encryptedText.constData()), @@ -749,41 +751,39 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack RSA_free(rsa); - if (decryptionStatus != -1) { - auto nodeList = DependencyManager::get(); + QByteArray decryptedTextByteArray = QByteArray(reinterpret_cast(decryptedText), decryptionStatus); + int decryptedTextByteArraySize = decryptedTextByteArray.size(); + int certIDSize = certID.size(); + // setup the packet + if (challengeOriginatedFromClient) { + auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, + certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int), + true); - QByteArray decryptedTextByteArray = QByteArray(reinterpret_cast(decryptedText), decryptionStatus); - int decryptedTextByteArraySize = decryptedTextByteArray.size(); - int certIDSize = certID.size(); - // setup the packet - if (challengeOriginatedFromClient) { - auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, - certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int), - true); + decryptedTextPacket->writePrimitive(certIDSize); + decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); + decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize); + decryptedTextPacket->write(certID); + decryptedTextPacket->write(decryptedTextByteArray); + decryptedTextPacket->write(challengingNodeUUID); - decryptedTextPacket->writePrimitive(certIDSize); - decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); - decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize); - decryptedTextPacket->write(certID); - decryptedTextPacket->write(decryptedTextByteArray); - decryptedTextPacket->write(challengingNodeUUID); + qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; - qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; - - nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); - } else { - auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true); - - decryptedTextPacket->writePrimitive(certIDSize); - decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); - decryptedTextPacket->write(certID); - decryptedTextPacket->write(decryptedTextByteArray); - - qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; - - nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); - } + nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); } else { + auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true); + + decryptedTextPacket->writePrimitive(certIDSize); + decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); + decryptedTextPacket->write(certID); + decryptedTextPacket->write(decryptedTextByteArray); + + qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; + + nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); + } + + if (decryptionStatus == -1) { qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed."; long error = ERR_get_error(); if (error != 0) { diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 48f2394ca3..75aefdc585 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -76,6 +76,7 @@ ContextOverlayInterface::ContextOverlayInterface() { auto nodeList = DependencyManager::get(); auto& packetReceiver = nodeList->getPacketReceiver(); packetReceiver.registerListener(PacketType::ChallengeOwnershipReply, this, "handleChallengeOwnershipReplyPacket"); + _challengeOwnershipTimeoutTimer.setSingleShot(true); } static const uint32_t MOUSE_HW_ID = 0; @@ -271,16 +272,16 @@ void ContextOverlayInterface::openInspectionCertificate() { tablet->loadQMLSource(INSPECTION_CERTIFICATE_QML_PATH); _hmdScriptingInterface->openTablet(); - EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_currentEntityWithContextOverlay, _entityPropertyFlags); + setLastInspectedEntity(_currentEntityWithContextOverlay); + + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); QUuid nodeToChallenge = entityProperties.getOwningAvatarID(); auto nodeList = DependencyManager::get(); // ZRF FIXME: Don't challenge ownership of avatar entities that I own if (entityProperties.getClientOnly()/* && nodeToChallenge != nodeList->getSessionUUID()*/) { - // ZRF FIXME! - //if (entityProperties.verifyStaticCertificateProperties()) { - if (true) { + if (entityProperties.verifyStaticCertificateProperties()) { SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); if (entityServer) { @@ -331,12 +332,12 @@ void ContextOverlayInterface::openInspectionCertificate() { nodeList->sendPacket(std::move(challengeOwnershipPacket), *entityServer); // Kickoff a 10-second timeout timer that marks the cert if we don't get an ownership response in time - //if (thread() != QThread::currentThread()) { - // QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer", Q_ARG(const EntityItemID&, entityItemID)); - // return; - //} else { - // startChallengeOwnershipTimer(entityItemID); - //} + if (thread() != QThread::currentThread()) { + QMetaObject::invokeMethod(this, "startChallengeOwnershipTimer"); + return; + } else { + startChallengeOwnershipTimer(); + } } } else { qCDebug(entities) << "Call to" << networkReply->url() << "failed with error" << networkReply->error() << @@ -350,8 +351,9 @@ void ContextOverlayInterface::openInspectionCertificate() { } } else { auto ledger = DependencyManager::get(); + _challengeOwnershipTimeoutTimer.stop(); emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED)); - qCDebug(context_overlay) << "Entity" << _currentEntityWithContextOverlay << "failed static certificate verification!"; + qCDebug(context_overlay) << "Entity" << _lastInspectedEntity << "failed static certificate verification!"; } } } @@ -388,9 +390,23 @@ void ContextOverlayInterface::deletingEntity(const EntityItemID& entityID) { } } +void ContextOverlayInterface::startChallengeOwnershipTimer() { + auto ledger = DependencyManager::get(); + EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); + + connect(&_challengeOwnershipTimeoutTimer, &QTimer::timeout, this, [=]() { + qCDebug(entities) << "Ownership challenge timed out for" << _lastInspectedEntity; + emit ledger->updateCertificateStatus(entityProperties.getCertificateID(), (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_TIMEOUT)); + }); + + _challengeOwnershipTimeoutTimer.start(5000); +} + void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer packet, SharedNodePointer sendingNode) { auto ledger = DependencyManager::get(); + _challengeOwnershipTimeoutTimer.stop(); + int certIDByteArraySize; int decryptedTextByteArraySize; @@ -403,6 +419,8 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer EntityItemID id; bool verificationSuccess = DependencyManager::get()->getTree()->verifyDecryptedNonce(certID, decryptedText, id); + qDebug() << "ZRF" << verificationSuccess; + if (verificationSuccess) { emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); } else { diff --git a/interface/src/ui/overlays/ContextOverlayInterface.h b/interface/src/ui/overlays/ContextOverlayInterface.h index eedc1790d3..8f0a40ef8e 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.h +++ b/interface/src/ui/overlays/ContextOverlayInterface.h @@ -51,6 +51,7 @@ public: Q_INVOKABLE QUuid getCurrentEntityWithContextOverlay() { return _currentEntityWithContextOverlay; } void setCurrentEntityWithContextOverlay(const QUuid& entityID) { _currentEntityWithContextOverlay = entityID; } + void setLastInspectedEntity(const QUuid& entityID) { _challengeOwnershipTimeoutTimer.stop(); _lastInspectedEntity = entityID; } void setEnabled(bool enabled); bool getEnabled() { return _enabled; } bool getIsInMarketplaceInspectionMode() { return _isInMarketplaceInspectionMode; } @@ -77,6 +78,7 @@ private: bool _verboseLogging { true }; bool _enabled { true }; EntityItemID _currentEntityWithContextOverlay{}; + EntityItemID _lastInspectedEntity{}; QString _entityMarketplaceID; bool _contextOverlayJustClicked { false }; @@ -90,6 +92,9 @@ private: void deletingEntity(const EntityItemID& entityItemID); SelectionToSceneHandler _selectionToSceneHandler; + + Q_INVOKABLE void startChallengeOwnershipTimer(); + QTimer _challengeOwnershipTimeoutTimer; }; #endif // hifi_ContextOverlayInterface_h diff --git a/libraries/entities/src/EntityTree.cpp b/libraries/entities/src/EntityTree.cpp index f748291077..333a514377 100644 --- a/libraries/entities/src/EntityTree.cpp +++ b/libraries/entities/src/EntityTree.cpp @@ -1220,9 +1220,9 @@ bool EntityTree::verifyDecryptedNonce(const QString& certID, const QString& decr bool verificationSuccess = (actualNonce == decryptedNonce); if (verificationSuccess) { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded for entity" << id; + qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "succeeded."; } else { - qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed for entity" << id + qCDebug(entities) << "Ownership challenge for Cert ID" << certID << "failed." << "\nActual nonce:" << actualNonce << "\nDecrypted nonce:" << decryptedNonce; } From 56cb98d96f64f9f3f4458ae072c3507a4db0b268 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 1 Nov 2017 12:02:09 -0700 Subject: [PATCH 22/46] Make inspection cert work --- .../InspectionCertificate.qml | 40 +++++++++---------- .../ui/overlays/ContextOverlayInterface.cpp | 3 +- 2 files changed, 20 insertions(+), 23 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 06e04d6929..bd8808f05f 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -67,27 +67,25 @@ Rectangle { } onUpdateCertificateStatus: { - if (root.certificateId === certID) { - if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS - - } else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT - errorText.text = "Verification of this certificate timed out."; - errorText.color = hifi.colors.redHighlight; - } else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED - titleBarText.text = "Invalid Certificate"; - titleBarText.color = hifi.colors.redHighlight; - root.itemEdition = "Uncertified Copy"; - errorText.text = "The certificate associated with this entity is invalid."; - errorText.color = hifi.colors.redHighlight; - } else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED - titleBarText.text = "Invalid Certificate"; - titleBarText.color = hifi.colors.redHighlight; - root.itemEdition = "Uncertified Copy"; - errorText.text = "The certificate associated with this entity is invalid."; - errorText.color = hifi.colors.redHighlight; - } else { - console.log("Unknown certificate status received from ledger signal!"); - } + if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS + // NOP + } else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT + errorText.text = "Verification of this certificate timed out."; + errorText.color = hifi.colors.redHighlight; + } else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED + titleBarText.text = "Invalid Certificate"; + titleBarText.color = hifi.colors.redHighlight; + root.itemEdition = "Uncertified Copy"; + errorText.text = "The certificate associated with this entity is invalid."; + errorText.color = hifi.colors.redHighlight; + } else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED + titleBarText.text = "Invalid Certificate"; + titleBarText.color = hifi.colors.redHighlight; + root.itemEdition = "Uncertified Copy"; + errorText.text = "The certificate associated with this entity is invalid."; + errorText.color = hifi.colors.redHighlight; + } else { + console.log("Unknown certificate status received from ledger signal!"); } } } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 75aefdc585..5990c710ad 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -279,8 +279,7 @@ void ContextOverlayInterface::openInspectionCertificate() { QUuid nodeToChallenge = entityProperties.getOwningAvatarID(); auto nodeList = DependencyManager::get(); - // ZRF FIXME: Don't challenge ownership of avatar entities that I own - if (entityProperties.getClientOnly()/* && nodeToChallenge != nodeList->getSessionUUID()*/) { + if (entityProperties.getClientOnly()) { if (entityProperties.verifyStaticCertificateProperties()) { SharedNodePointer entityServer = nodeList->soloNodeOfType(NodeType::EntityServer); From 72d61f1825c83023029936930b1437c626c12715 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Wed, 1 Nov 2017 12:03:44 -0700 Subject: [PATCH 23/46] Remove unnecessary log - static verif isn't working --- interface/src/ui/overlays/ContextOverlayInterface.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index 5990c710ad..e6568b56dd 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -418,8 +418,6 @@ void ContextOverlayInterface::handleChallengeOwnershipReplyPacket(QSharedPointer EntityItemID id; bool verificationSuccess = DependencyManager::get()->getTree()->verifyDecryptedNonce(certID, decryptedText, id); - qDebug() << "ZRF" << verificationSuccess; - if (verificationSuccess) { emit ledger->updateCertificateStatus(certID, (uint)(ledger->CERTIFICATE_STATUS_VERIFICATION_SUCCESS)); } else { From edc7b91a425353b47f38a98ad94ad8ced981229d Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 1 Nov 2017 12:57:07 -0700 Subject: [PATCH 24/46] Fix comment in NetworkingConstants referencing shared.js location --- libraries/networking/src/NetworkingConstants.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/networking/src/NetworkingConstants.h b/libraries/networking/src/NetworkingConstants.h index 0bb0cee5d2..0c210e4360 100644 --- a/libraries/networking/src/NetworkingConstants.h +++ b/libraries/networking/src/NetworkingConstants.h @@ -16,8 +16,8 @@ namespace NetworkingConstants { // If you want to use STAGING instead of STABLE, - // don't forget to ALSO change the Domain Server Metaverse Server URL, which is at the top of: - // \domain-server\resources\web\settings\js\settings.js + // don't forget to ALSO change the Domain Server Metaverse Server URL inside of: + // \domain-server\resources\web\js\shared.js const QUrl METAVERSE_SERVER_URL_STABLE("https://metaverse.highfidelity.com"); const QUrl METAVERSE_SERVER_URL_STAGING("https://staging.highfidelity.com"); const QUrl METAVERSE_SERVER_URL = METAVERSE_SERVER_URL_STABLE; From 8401aecef566c642c942e60f30975c186e4d5158 Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Wed, 1 Nov 2017 15:42:35 -0700 Subject: [PATCH 25/46] Update wizard to make request for domain info --- domain-server/resources/web/js/shared.js | 384 +++++++++++------- .../resources/web/settings/js/settings.js | 16 - .../resources/web/wizard/js/wizard.js | 1 + 3 files changed, 227 insertions(+), 174 deletions(-) diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index a5dd522a53..4db608ddb4 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -146,187 +146,255 @@ function sendUpdatePlaceRequest(id, path, domainID, clearDomainID, onSuccess, on }); } +var pendingDomainRequest = null; +function getDomainFromAPI(callback) { + if (pendingDomainRequest !== null) { + pendingDomainRequest.success(callback); + pendingDomainRequest.error(function() { callback({ status: 'fail' }) }); + return pendingDomainRequest; + } + + if (callback === undefined) { + callback = function() {}; + } + + var domainID = Settings.data.values.metaverse.id; + if (domainID === null || domainID === undefined || domainID === '') { + callback({ status: 'fail' }); + return null; + } + + pendingDomainRequest = $.ajax({ + url: "/api/domains/" + domainID, + dataType: 'json', + success: function(data) { + pendingDomainRequest = null; + + if (data.status === 'success') { + DomainInfo = data.domain; + } else { + DomainInfo = null; + } + callback(data); + }, + error: function() { + pendingDomainRequest = null; + + DomainInfo = null; + callback({ status: 'fail' }); + } + }); + + return pendingDomainRequest; +} + function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded) { if (accessToken) { var loadingDialog = showLoadingDialog(Strings.ADD_PLACE_LOADING_DIALOG); - $.ajax("/api/places", { - dataType: 'json', - jsonp: false, - success: function(data) { - if (data.status == 'success') { - var modal_buttons = { - cancel: { - label: Strings.ADD_PLACE_CANCEL_BUTTON, - className: 'add-place-cancel-button btn-default' - } - }; - - var dialog; - var modal_body; - - if (data.data.places.length) { - var places_by_id = {}; - - modal_body = $('
'); - - modal_body.append($("

Choose a place name that you own or register a new place name

")); - - var currentDomainIDType = getCurrentDomainIDType(); - if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) { - var warning = "
"; - warning += "If you choose a place name it will replace your current temporary place name."; - warning += "
"; - modal_body.append(warning); - } - - // setup a select box for the returned places - modal_body.append($("")); - place_select = $(""); - _.each(data.data.places, function(place) { - places_by_id[place.id] = place; - place_select.append(""); - }) - modal_body.append(place_select); - modal_body.append($("")); - - if (forcePathTo === undefined || forcePathTo === null) { - var path = "
"; - path += ""; - path += ""; - path += "
"; - modal_body.append($(path)); - } - - var place_select = modal_body.find("#place-name-select") - place_select.change(function(ev) { - var warning = modal_body.find("#place-name-warning"); - var place = places_by_id[$(this).val()]; - if (place === undefined || place.pointee === null) { - warning.hide(); - } else { - warning.show(); + function loadPlaces() { + $.ajax("/api/places", { + dataType: 'json', + jsonp: false, + success: function(data) { + if (data.status == 'success') { + var modal_buttons = { + cancel: { + label: Strings.ADD_PLACE_CANCEL_BUTTON, + className: 'add-place-cancel-button btn-default' } - }); - place_select.trigger('change'); + }; - modal_buttons["success"] = { - label: Strings.ADD_PLACE_CONFIRM_BUTTON, - className: 'add-place-confirm-button btn btn-primary', - callback: function() { - var placeID = $('#place-name-select').val(); - // set the place ID on the form - $(Settings.place_ID_SELECTOR).val(placeID).change(); + var dialog; + var modal_body; - if (forcePathTo === undefined || forcePathTo === null) { - var placePath = $('#place-path-input').val(); + if (data.data.places.length) { + var places_by_id = {}; + + modal_body = $('
'); + + modal_body.append($("

Choose a place name that you own or register a new place name

")); + + var currentDomainIDType = getCurrentDomainIDType(); + if (currentDomainIDType === DOMAIN_ID_TYPE_TEMP) { + var warning = "
"; + warning += "If you choose a place name it will replace your current temporary place name."; + warning += "
"; + modal_body.append(warning); + } + + // setup a select box for the returned places + modal_body.append($("")); + place_select = $(""); + _.each(data.data.places, function(place) { + places_by_id[place.id] = place; + place_select.append(""); + }) + modal_body.append(place_select); + modal_body.append($("")); + + if (forcePathTo === undefined || forcePathTo === null) { + var path = "
"; + path += ""; + path += ""; + path += "
"; + modal_body.append($(path)); + } + + var place_select = modal_body.find("#place-name-select") + place_select.change(function(ev) { + var warning = modal_body.find("#place-name-warning"); + var place = places_by_id[$(this).val()]; + if (place === undefined || place.pointee === null) { + warning.hide(); } else { - var placePath = forcePathTo; + warning.show(); } + }); + place_select.trigger('change'); - $('.add-place-confirm-button').attr('disabled', 'disabled'); - $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING); - $('.add-place-cancel-button').attr('disabled', 'disabled'); + modal_buttons["success"] = { + label: Strings.ADD_PLACE_CONFIRM_BUTTON, + className: 'add-place-confirm-button btn btn-primary', + callback: function() { + var placeID = $('#place-name-select').val(); + // set the place ID on the form + $(Settings.place_ID_SELECTOR).val(placeID).change(); - function finalizeSaveDomainID(domainID) { - var jsonSettings = { - metaverse: { - id: domainID + if (forcePathTo === undefined || forcePathTo === null) { + var placePath = $('#place-path-input').val(); + } else { + var placePath = forcePathTo; + } + + $('.add-place-confirm-button').attr('disabled', 'disabled'); + $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON_PENDING); + $('.add-place-cancel-button').attr('disabled', 'disabled'); + + function finalizeSaveDomainID(domainID) { + var jsonSettings = { + metaverse: { + id: domainID + } + } + var dialog = showLoadingDialog("Waiting for Domain Server to restart..."); + $.ajax('/settings.json', { + data: JSON.stringify(jsonSettings), + contentType: 'application/json', + type: 'POST' + }).done(function(data) { + if (data.status == "success") { + waitForDomainServerRestart(function() { + dialog.modal('hide'); + if (onSuccessfullyAdded) { + onSuccessfullyAdded(places_by_id[placeID].name, domainID); + } + }); + } else { + bootbox.alert("Failed to add place"); + } + }).fail(function() { + bootbox.alert("Failed to add place"); + }); + } + + function finishSettingUpPlace(domainID) { + sendUpdatePlaceRequest( + placeID, + placePath, + domainID, + false, + function(data) { + $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); + dialog.modal('hide') + if (domainID) { + finalizeSaveDomainID(domainID); + } else { + if (onSuccessfullyAdded) { + onSuccessfullyAdded(places_by_id[placeID].name); + } + } + }, + function(data) { + $('.add-place-confirm-button').removeAttr('disabled'); + $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON); + $('.add-place-cancel-button').removeAttr('disabled'); + bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR); + } + ); + } + + function maybeCreateNewDomainID() { + console.log("Maybe creating domain id", currentDomainIDType) + if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) { + finishSettingUpPlace(); + } else { + sendCreateDomainRequest(function(domainID) { + console.log("Created domain", domainID); + finishSettingUpPlace(domainID); + }, function() { + $('.add-place-confirm-button').removeAttr('disabled'); + $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON); + $('.add-place-cancel-button').removeAttr('disabled'); + bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR); + bootbox.alert("FAIL"); + }); } } - var dialog = showLoadingDialog("Waiting for Domain Server to restart..."); - $.ajax('/settings.json', { - data: JSON.stringify(jsonSettings), - contentType: 'application/json', - type: 'POST' - }).done(function(data) { - if (data.status == "success") { - waitForDomainServerRestart(function() { - dialog.modal('hide'); - if (onSuccessfullyAdded) { - onSuccessfullyAdded(places_by_id[placeID].name, domainID); - } - }); - } else { - bootbox.alert("Failed to add place"); - } - }).fail(function() { - bootbox.alert("Failed to add place"); - }); - } - function finishSettingUpPlace(domainID) { - sendUpdatePlaceRequest( - placeID, - placePath, - domainID, - false, - function(data) { - $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); - dialog.modal('hide') - if (domainID) { - finalizeSaveDomainID(domainID); - } else { - if (onSuccessfullyAdded) { - onSuccessfullyAdded(places_by_id[placeID].name); - } - } - }, - function(data) { - $('.add-place-confirm-button').removeAttr('disabled'); - $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON); - $('.add-place-cancel-button').removeAttr('disabled'); - bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR); - } - ); - } + maybeCreateNewDomainID(); - if (currentDomainIDType === DOMAIN_ID_TYPE_FULL) { - finishSettingUpPlace(); - } else { - sendCreateDomainRequest(function(domainID) { - console.log("Created domain", domainID); - finishSettingUpPlace(domainID); - }, function() { - $('.add-place-confirm-button').removeAttr('disabled'); - $('.add-place-confirm-button').html(Strings.ADD_PLACE_CONFIRM_BUTTON); - $('.add-place-cancel-button').removeAttr('disabled'); - bootbox.alert(Strings.ADD_PLACE_UNKNOWN_ERROR); - bootbox.alert("FAIL"); - }); + return false; } - - return false; } + } else { + modal_buttons["success"] = { + label: Strings.ADD_PLACE_NO_PLACES_BUTTON, + callback: function() { + window.open(URLs.METAVERSE_URL + "/user/places", '_blank'); + } + } + modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE; } + + dialog = bootbox.dialog({ + title: Strings.ADD_PLACE_TITLE, + message: modal_body, + closeButton: false, + buttons: modal_buttons + }); } else { - modal_buttons["success"] = { - label: Strings.ADD_PLACE_NO_PLACES_BUTTON, - callback: function() { - window.open(URLs.METAVERSE_URL + "/user/places", '_blank'); - } - } - modal_body = Strings.ADD_PLACE_NO_PLACES_MESSAGE; + bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR); } - - dialog = bootbox.dialog({ - title: Strings.ADD_PLACE_TITLE, - message: modal_body, - closeButton: false, - buttons: modal_buttons - }); - } else { + }, + error: function() { bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR); + }, + complete: function() { + loadingDialog.modal('hide'); } - }, - error: function() { - bootbox.alert(Strings.ADD_PLACE_UNABLE_TO_LOAD_ERROR); - }, - complete: function() { - loadingDialog.modal('hide'); - } - }); + }); + } + + var domainType = getCurrentDomainIDType(); + if (domainType !== DOMAIN_ID_TYPE_UNKNOWN) { + loadPlaces(); + } else { + getDomainFromAPI(function(data) { + if (data.status === 'success') { + var domainType = getCurrentDomainIDType(); + loadPlaces(); + } else { + loadingDialog.modal('hide'); + bootbox.confirm("We were not able to load your domain information from the Metaverse. Would you like to retry?", function(response) { + if (response) { + chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAdded); + } + }); + } + }) + } } else { bootbox.alert({ diff --git a/domain-server/resources/web/settings/js/settings.js b/domain-server/resources/web/settings/js/settings.js index 1224b724da..7f99b367a3 100644 --- a/domain-server/resources/web/settings/js/settings.js +++ b/domain-server/resources/web/settings/js/settings.js @@ -980,20 +980,6 @@ function placeTableRowForPlaceObject(place) { return placeTableRow(place.name, placePathOrIndex, false, place.id); } -function getDomainFromAPI(callback) { - var domainID = Settings.data.values.metaverse.id; - $.ajax({ - url: "/api/domains/" + domainID, - dataType: 'json', - success: function(data) { - callback(data); - }, - error: function() { - callback({ status: 'fail' }); - } - }); -} - function reloadDomainInfo() { $('#' + Settings.PLACES_TABLE_ID + " tbody tr").not('.headers').remove(); @@ -1010,7 +996,6 @@ function reloadDomainInfo() { // check if we have owner_places (for a real domain) or a name (for a temporary domain) if (data.status == "success") { $('.domain-loading-hide').show(); - DomainInfo = data.domain; if (data.domain.owner_places) { // add a table row for each of these names _.each(data.domain.owner_places, function(place){ @@ -1043,7 +1028,6 @@ function reloadDomainInfo() { appendAddButtonToPlacesTable(); } else { - DomainInfo = null; $('.domain-loading-error').show(); } }) diff --git a/domain-server/resources/web/wizard/js/wizard.js b/domain-server/resources/web/wizard/js/wizard.js index 24a82402a6..1af3f305b7 100644 --- a/domain-server/resources/web/wizard/js/wizard.js +++ b/domain-server/resources/web/wizard/js/wizard.js @@ -58,6 +58,7 @@ $(document).ready(function(){ reloadSettings(function(success) { if (success) { + getDomainFromAPI(); setupWizardSteps(); updatePlaceNameDisplay(); updateUsernameDisplay(); From 12792c3821730a8f271d2f802cf57606c02c1ca3 Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 1 Nov 2017 17:26:00 -0700 Subject: [PATCH 26/46] stop drawing ignored avatars, even if PAL is still open --- interface/src/avatar/AvatarManager.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index e8b800db69..4cf8ba6d4e 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -179,6 +179,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const AvatarPriority& sortData = sortedAvatars.top(); const auto& avatar = std::static_pointer_cast(sortData.avatar); + bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); + if (ignoring) { + sortedAvatars.pop(); + continue; + } + // for ALL avatars... if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); From f8718efe25dbeb5e6f3a8162e3bdaa5be19e4cd3 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 2 Nov 2017 09:42:33 -0700 Subject: [PATCH 27/46] Fix warnings --- interface/src/ui/overlays/ContextOverlayInterface.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index e6568b56dd..f83427a1a1 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -276,7 +276,6 @@ void ContextOverlayInterface::openInspectionCertificate() { EntityItemProperties entityProperties = _entityScriptingInterface->getEntityProperties(_lastInspectedEntity, _entityPropertyFlags); - QUuid nodeToChallenge = entityProperties.getOwningAvatarID(); auto nodeList = DependencyManager::get(); if (entityProperties.getClientOnly()) { From 19945c5991425745be0355d953f6537248837dd3 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 2 Nov 2017 10:22:00 -0700 Subject: [PATCH 28/46] Update cert UI --- .../InspectionCertificate.qml | 31 ++++++++++++++----- .../ui/overlays/ContextOverlayInterface.cpp | 4 +-- 2 files changed, 26 insertions(+), 9 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index bd8808f05f..6a7a181b92 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -33,6 +33,7 @@ Rectangle { property string dateOfPurchase: "--"; property bool isLightbox: false; property bool isMyCert: false; + property bool isCertificateInvalid: false; // Style color: hifi.colors.faintGray; Hifi.QmlCommerce { @@ -44,10 +45,11 @@ Rectangle { } else { root.marketplaceUrl = result.data.marketplace_item_url; root.isMyCert = result.isMyCert ? result.isMyCert : false; - root.itemOwner = root.isMyCert ? Account.username : - "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"; - root.itemEdition = result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run); - root.dateOfPurchase = getFormattedDate(result.data.transfer_created_at * 1000); + root.itemOwner = root.isCertificateInvalid ? "--" : (root.isMyCert ? Account.username : + "\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022\u2022"); + root.itemEdition = root.isCertificateInvalid ? "Uncertified Copy" : + (result.data.edition_number + "/" + (result.data.limited_run === -1 ? "\u221e" : result.data.limited_run)); + root.dateOfPurchase = root.isCertificateInvalid ? "" : getFormattedDate(result.data.transfer_created_at * 1000); root.itemName = result.data.marketplace_item_name; if (result.data.invalid_reason || result.data.transfer_status[0] === "failed") { @@ -70,20 +72,35 @@ Rectangle { if (certStatus === 1) { // CERTIFICATE_STATUS_VERIFICATION_SUCCESS // NOP } else if (certStatus === 2) { // CERTIFICATE_STATUS_VERIFICATION_TIMEOUT + root.isCertificateInvalid = true; errorText.text = "Verification of this certificate timed out."; errorText.color = hifi.colors.redHighlight; } else if (certStatus === 3) { // CERTIFICATE_STATUS_STATIC_VERIFICATION_FAILED + root.isCertificateInvalid = true; titleBarText.text = "Invalid Certificate"; titleBarText.color = hifi.colors.redHighlight; + + popText.text = ""; + root.itemOwner = ""; + dateOfPurchaseHeader.text = ""; + root.dateOfPurchase = ""; root.itemEdition = "Uncertified Copy"; + errorText.text = "The certificate associated with this entity is invalid."; - errorText.color = hifi.colors.redHighlight; + errorText.color = hifi.colors.baseGray; } else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED + root.isCertificateInvalid = true; titleBarText.text = "Invalid Certificate"; titleBarText.color = hifi.colors.redHighlight; + + popText.text = ""; + root.itemOwner = ""; + dateOfPurchaseHeader.text = ""; + root.dateOfPurchase = ""; root.itemEdition = "Uncertified Copy"; + errorText.text = "The certificate associated with this entity is invalid."; - errorText.color = hifi.colors.redHighlight; + errorText.color = hifi.colors.baseGray; } else { console.log("Unknown certificate status received from ledger signal!"); } @@ -239,7 +256,7 @@ Rectangle { } AnonymousProRegular { id: isMyCertText; - visible: root.isMyCert; + visible: root.isMyCert && !root.isCertificateInvalid; text: "(Private)"; size: 18; // Anchors diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index f83427a1a1..b5af529f2b 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -304,9 +304,9 @@ void ContextOverlayInterface::openInspectionCertificate() { if (!jsonObject["invalid_reason"].toString().isEmpty()) { qCDebug(entities) << "invalid_reason not empty"; } else if (jsonObject["transfer_status"].toArray().first().toString() == "failed") { - qCDebug(entities) << "'transfer_status' is 'failed'";; + qCDebug(entities) << "'transfer_status' is 'failed'"; } else if (jsonObject["transfer_status"].toArray().first().toString() == "pending") { - qCDebug(entities) << "'transfer_status' is 'pending'";; + qCDebug(entities) << "'transfer_status' is 'pending'"; } else { QString ownerKey = jsonObject["transfer_recipient_key"].toString(); From e77b9e0e97786015e39989155ff5f11f77a14f7f Mon Sep 17 00:00:00 2001 From: Seth Alves Date: Wed, 1 Nov 2017 17:26:00 -0700 Subject: [PATCH 29/46] stop drawing ignored avatars, even if PAL is still open --- interface/src/avatar/AvatarManager.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index e8b800db69..4cf8ba6d4e 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -179,6 +179,12 @@ void AvatarManager::updateOtherAvatars(float deltaTime) { const AvatarPriority& sortData = sortedAvatars.top(); const auto& avatar = std::static_pointer_cast(sortData.avatar); + bool ignoring = DependencyManager::get()->isPersonalMutingNode(avatar->getID()); + if (ignoring) { + sortedAvatars.pop(); + continue; + } + // for ALL avatars... if (_shouldRender) { avatar->ensureInScene(avatar, qApp->getMain3DScene()); From db9c3cc103ef37f9a40c4e404fd03de0b0e865f8 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 2 Nov 2017 12:31:36 -0700 Subject: [PATCH 30/46] make Model::scaleToFit() public not protected --- libraries/render-utils/src/Model.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 8bce976b4e..3abf7e2758 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -262,6 +262,8 @@ public: Q_INVOKABLE MeshProxyList getMeshes() const; + void scaleToFit(); + public slots: void loadURLFinished(bool success); @@ -320,7 +322,6 @@ protected: virtual void initJointStates(); void setScaleInternal(const glm::vec3& scale); - void scaleToFit(); void snapToRegistrationPoint(); void computeMeshPartLocalBounds(); From 490483fde889e31ddee4c2697e4716eccc4bb792 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 2 Nov 2017 12:32:51 -0700 Subject: [PATCH 31/46] call Model::scaleToFit() before update render items --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7db19704b4..03380ad321 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -215,6 +215,7 @@ void RenderableModelEntityItem::updateModelBounds() { model->setScaleToFit(true, getDimensions()); model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); updateRenderItems = true; + model->scaleToFit(); } bool success; From 60fce3d4f712c079aa373dd868f9eb372d4ef5c4 Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 2 Nov 2017 12:31:36 -0700 Subject: [PATCH 32/46] make Model::scaleToFit() public not protected --- libraries/render-utils/src/Model.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libraries/render-utils/src/Model.h b/libraries/render-utils/src/Model.h index 8bce976b4e..3abf7e2758 100644 --- a/libraries/render-utils/src/Model.h +++ b/libraries/render-utils/src/Model.h @@ -262,6 +262,8 @@ public: Q_INVOKABLE MeshProxyList getMeshes() const; + void scaleToFit(); + public slots: void loadURLFinished(bool success); @@ -320,7 +322,6 @@ protected: virtual void initJointStates(); void setScaleInternal(const glm::vec3& scale); - void scaleToFit(); void snapToRegistrationPoint(); void computeMeshPartLocalBounds(); From 2b06df1c335224fefd9272d518584bdb3319a3be Mon Sep 17 00:00:00 2001 From: Andrew Meadows Date: Thu, 2 Nov 2017 12:32:51 -0700 Subject: [PATCH 33/46] call Model::scaleToFit() before update render items --- libraries/entities-renderer/src/RenderableModelEntityItem.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp index 7db19704b4..03380ad321 100644 --- a/libraries/entities-renderer/src/RenderableModelEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableModelEntityItem.cpp @@ -215,6 +215,7 @@ void RenderableModelEntityItem::updateModelBounds() { model->setScaleToFit(true, getDimensions()); model->setSnapModelToRegistrationPoint(true, getRegistrationPoint()); updateRenderItems = true; + model->scaleToFit(); } bool success; From 23e627a46f740cb3f4e5ccb2384cde0876c6799f Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 2 Nov 2017 13:46:24 -0700 Subject: [PATCH 34/46] Send empty decrypted text if impossible to decrypt --- interface/src/commerce/Wallet.cpp | 92 ++++++++++++++++--------------- 1 file changed, 48 insertions(+), 44 deletions(-) diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index c6d77982b8..85632ff8f1 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -718,6 +718,8 @@ bool Wallet::changePassphrase(const QString& newPassphrase) { } void Wallet::handleChallengeOwnershipPacket(QSharedPointer packet, SharedNodePointer sendingNode) { + auto nodeList = DependencyManager::get(); + bool challengeOriginatedFromClient = packet->getType() == PacketType::ChallengeOwnershipRequest; unsigned char decryptedText[64]; int certIDByteArraySize; @@ -738,62 +740,64 @@ void Wallet::handleChallengeOwnershipPacket(QSharedPointer pack } RSA* rsa = readKeys(keyFilePath().toStdString().c_str()); + int decryptionStatus = -1; if (rsa) { - auto nodeList = DependencyManager::get(); - ERR_clear_error(); - const int decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize, + decryptionStatus = RSA_private_decrypt(encryptedTextByteArraySize, reinterpret_cast(encryptedText.constData()), decryptedText, rsa, RSA_PKCS1_OAEP_PADDING); RSA_free(rsa); - - QByteArray decryptedTextByteArray = QByteArray(reinterpret_cast(decryptedText), decryptionStatus); - int decryptedTextByteArraySize = decryptedTextByteArray.size(); - int certIDSize = certID.size(); - // setup the packet - if (challengeOriginatedFromClient) { - auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, - certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int), - true); - - decryptedTextPacket->writePrimitive(certIDSize); - decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); - decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize); - decryptedTextPacket->write(certID); - decryptedTextPacket->write(decryptedTextByteArray); - decryptedTextPacket->write(challengingNodeUUID); - - qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; - - nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); - } else { - auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true); - - decryptedTextPacket->writePrimitive(certIDSize); - decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); - decryptedTextPacket->write(certID); - decryptedTextPacket->write(decryptedTextByteArray); - - qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; - - nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); - } - - if (decryptionStatus == -1) { - qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed."; - long error = ERR_get_error(); - if (error != 0) { - const char* error_str = ERR_error_string(error, NULL); - qCWarning(entities) << "RSA error:" << error_str; - } - } } else { qCDebug(commerce) << "During entity ownership challenge, creating the RSA object failed."; } + + QByteArray decryptedTextByteArray; + if (decryptionStatus > -1) { + decryptedTextByteArray = QByteArray(reinterpret_cast(decryptedText), decryptionStatus); + } + int decryptedTextByteArraySize = decryptedTextByteArray.size(); + int certIDSize = certID.size(); + // setup the packet + if (challengeOriginatedFromClient) { + auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnershipReply, + certIDSize + decryptedTextByteArraySize + challengingNodeUUIDByteArraySize + 3 * sizeof(int), + true); + + decryptedTextPacket->writePrimitive(certIDSize); + decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); + decryptedTextPacket->writePrimitive(challengingNodeUUIDByteArraySize); + decryptedTextPacket->write(certID); + decryptedTextPacket->write(decryptedTextByteArray); + decryptedTextPacket->write(challengingNodeUUID); + + qCDebug(commerce) << "Sending ChallengeOwnershipReply Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; + + nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); + } else { + auto decryptedTextPacket = NLPacket::create(PacketType::ChallengeOwnership, certIDSize + decryptedTextByteArraySize + 2 * sizeof(int), true); + + decryptedTextPacket->writePrimitive(certIDSize); + decryptedTextPacket->writePrimitive(decryptedTextByteArraySize); + decryptedTextPacket->write(certID); + decryptedTextPacket->write(decryptedTextByteArray); + + qCDebug(commerce) << "Sending ChallengeOwnership Packet containing decrypted text" << decryptedTextByteArray << "for CertID" << certID; + + nodeList->sendPacket(std::move(decryptedTextPacket), *sendingNode); + } + + if (decryptionStatus == -1) { + qCDebug(commerce) << "During entity ownership challenge, decrypting the encrypted text failed."; + long error = ERR_get_error(); + if (error != 0) { + const char* error_str = ERR_error_string(error, NULL); + qCWarning(entities) << "RSA error:" << error_str; + } + } } void Wallet::account() { From 56b1c7d0eeb03bc6ba1504ac71dd1ebbc26b2bec Mon Sep 17 00:00:00 2001 From: Brad Davis Date: Thu, 2 Nov 2017 14:51:40 -0700 Subject: [PATCH 35/46] Fix finding the SSL binary path --- cmake/modules/FindOpenSSL.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmake/modules/FindOpenSSL.cmake b/cmake/modules/FindOpenSSL.cmake index 338dee7bc8..0619c4d587 100644 --- a/cmake/modules/FindOpenSSL.cmake +++ b/cmake/modules/FindOpenSSL.cmake @@ -60,7 +60,7 @@ if (WIN32 AND NOT CYGWIN) select_library_configurations(LIB_EAY) select_library_configurations(SSL_EAY) set(OPENSSL_LIBRARIES ${SSL_EAY_LIBRARY} ${LIB_EAY_LIBRARY}) - find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" ${_OPENSSL_ROOT_HINTS_AND_PATHS}) + find_path(OPENSSL_DLL_PATH NAMES ssleay32.dll PATH_SUFFIXES "bin" HINTS ${_OPENSSL_ROOT_HINTS_AND_PATHS} NO_DEFAULT_PATH) endif() else() From 6173ad810b3c9207d649734a741c062459f99a53 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 2 Nov 2017 15:48:59 -0700 Subject: [PATCH 36/46] Reset wallet passphrase when login username changes --- interface/src/commerce/QmlCommerce.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/interface/src/commerce/QmlCommerce.cpp b/interface/src/commerce/QmlCommerce.cpp index ac5d0e6a2d..9d07ddb4ab 100644 --- a/interface/src/commerce/QmlCommerce.cpp +++ b/interface/src/commerce/QmlCommerce.cpp @@ -31,6 +31,11 @@ QmlCommerce::QmlCommerce(QQuickItem* parent) : OffscreenQmlDialog(parent) { connect(wallet.data(), &Wallet::walletStatusResult, this, &QmlCommerce::walletStatusResult); connect(ledger.data(), &Ledger::certificateInfoResult, this, &QmlCommerce::certificateInfoResult); connect(ledger.data(), &Ledger::updateCertificateStatus, this, &QmlCommerce::updateCertificateStatus); + + auto accountManager = DependencyManager::get(); + connect(accountManager.data(), &AccountManager::usernameChanged, [&]() { + setPassphrase(""); + }); } void QmlCommerce::getWalletStatus() { From b1fe3699f99cd239b7f14aa598fe10086d1372fa Mon Sep 17 00:00:00 2001 From: Ryan Huffman Date: Thu, 2 Nov 2017 17:42:24 -0700 Subject: [PATCH 37/46] Fix domain id getting set to empty when adding place name --- domain-server/resources/web/js/shared.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domain-server/resources/web/js/shared.js b/domain-server/resources/web/js/shared.js index 4db608ddb4..66159209ea 100644 --- a/domain-server/resources/web/js/shared.js +++ b/domain-server/resources/web/js/shared.js @@ -299,6 +299,7 @@ function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAd }); } + // If domainID is not specified, the current domain id will be used. function finishSettingUpPlace(domainID) { sendUpdatePlaceRequest( placeID, @@ -306,9 +307,9 @@ function chooseFromHighFidelityPlaces(accessToken, forcePathTo, onSuccessfullyAd domainID, false, function(data) { - $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); dialog.modal('hide') if (domainID) { + $(Settings.DOMAIN_ID_SELECTOR).val(domainID).change(); finalizeSaveDomainID(domainID); } else { if (onSuccessfullyAdded) { From dd4275430b830309ae4a341605c1599e81dcf055 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Thu, 2 Nov 2017 17:48:37 -0700 Subject: [PATCH 38/46] fix hover events, working on HUD events --- interface/src/Application.cpp | 36 ++++++++++ interface/src/Application.h | 2 + .../src/raypick/PointerScriptingInterface.h | 2 +- interface/src/ui/overlays/Web3DOverlay.cpp | 27 +++---- .../src/RenderableWebEntityItem.cpp | 10 +-- .../src/pointers/PickCacheOptimizer.h | 9 +-- libraries/pointers/src/pointers/Pointer.cpp | 70 +++++++++++++------ libraries/pointers/src/pointers/Pointer.h | 2 + libraries/shared/src/PointerEvent.h | 9 ++- libraries/ui/CMakeLists.txt | 3 +- libraries/ui/src/ui/OffscreenQmlSurface.cpp | 59 +++------------- libraries/ui/src/ui/OffscreenQmlSurface.h | 5 -- 12 files changed, 129 insertions(+), 105 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 3a1f970438..d81332e7d5 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1504,6 +1504,14 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); }); + auto pointerManager = DependencyManager::get(); + connect(pointerManager.data(), &PointerManager::hoverBeginHUD, this, &Application::handleHUDPointerEvent); + connect(pointerManager.data(), &PointerManager::hoverContinueHUD, this, &Application::handleHUDPointerEvent); + connect(pointerManager.data(), &PointerManager::hoverEndHUD, this, &Application::handleHUDPointerEvent); + connect(pointerManager.data(), &PointerManager::triggerBeginHUD, this, &Application::handleHUDPointerEvent); + connect(pointerManager.data(), &PointerManager::triggerContinueHUD, this, &Application::handleHUDPointerEvent); + connect(pointerManager.data(), &PointerManager::triggerEndHUD, this, &Application::handleHUDPointerEvent); + // Add periodic checks to send user activity data static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000; static int NEARBY_AVATAR_RADIUS_METERS = 10; @@ -7479,4 +7487,32 @@ void Application::setAvatarOverrideUrl(const QUrl& url, bool save) { _avatarOverrideUrl = url; _saveAvatarOverrideUrl = save; } + +void Application::handleHUDPointerEvent(const QUuid& id, const PointerEvent& event) { + QEvent::Type type = QEvent::Type::MouseMove; + switch (event.getType()) { + case PointerEvent::Press: + type = QEvent::Type::MouseButtonPress; + break; + case PointerEvent::DoublePress: + type = QEvent::Type::MouseButtonDblClick; + break; + case PointerEvent::Release: + type = QEvent::Type::MouseButtonRelease; + break; + case PointerEvent::Move: + type = QEvent::Type::MouseMove; + break; + default: + break; + } + + QPointF screenPos(event.getPos2D().x, event.getPos2D().y); + //QMouseEvent mouseEvent(type, screenPos, Qt::MouseButton(event.getButton()), Qt::MouseButtons(event.getButtons()), event.getKeyboardModifiers()); + QMouseEvent moveEvent(QEvent::Type::MouseMove, screenPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + sendEvent(_glWidget, &moveEvent); + QMouseEvent mouseEvent(type, screenPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); + sendEvent(_glWidget, &mouseEvent); +} + #include "Application.moc" diff --git a/interface/src/Application.h b/interface/src/Application.h index d2a55db216..a229c4b91a 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -434,6 +434,8 @@ private slots: void handleSandboxStatus(QNetworkReply* reply); void switchDisplayMode(); + + void handleHUDPointerEvent(const QUuid& id, const PointerEvent& event); private: static void initDisplay(); void init(); diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 722ba8cec5..8ea9594fda 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -35,7 +35,7 @@ public slots: Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities) const; Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities) const; - Q_INVOKABLE void setLockEndUid(unsigned int uid, const QUuid& objectID, bool isOverlay) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay); } + Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay); } signals: void triggerBegin(const QUuid& id, const PointerEvent& pointerEvent); diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index 5339deba2b..b8683b43e3 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -160,14 +160,6 @@ void Web3DOverlay::buildWebSurface() { QObject::connect(_webSurface.data(), &OffscreenQmlSurface::webEventReceived, this, &Web3DOverlay::webEventReceived); } -void Web3DOverlay::hoverLeaveOverlay(const PointerEvent& event) { - if ((_pressed || (!_activeTouchPoints.empty() && _touchBeginAccepted))) { - PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), - event.getButton(), event.getButtons(), event.getKeyboardModifiers()); - handlePointerEvent(endEvent); - } -} - void Web3DOverlay::update(float deltatime) { if (_webSurface) { // update globalPosition @@ -348,6 +340,17 @@ void Web3DOverlay::setProxyWindow(QWindow* proxyWindow) { _webSurface->setProxyWindow(proxyWindow); } +void Web3DOverlay::hoverLeaveOverlay(const PointerEvent& event) { + if ((_pressed || (!_activeTouchPoints.empty() && _touchBeginAccepted)) && event.sendReleaseOnHoverLeave()) { + PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), + event.getButton(), event.getButtons(), event.getKeyboardModifiers()); + handlePointerEvent(endEvent); + // QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited. + PointerEvent endMoveEvent(PointerEvent::Move, event.getID()); + handlePointerEvent(endMoveEvent); + } +} + void Web3DOverlay::handlePointerEvent(const PointerEvent& event) { if (event.getType() == PointerEvent::Press) { _pressed = true; @@ -386,7 +389,7 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { } if (state == Qt::TouchPointReleased && _activeTouchPoints.size() == 1 && _activeTouchPoints.count(event.getID())) { // If the last active touch point is being released, send an end touchType = QEvent::TouchEnd; - } + } { QTouchEvent::TouchPoint point; @@ -428,7 +431,7 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) if (event.getType() == PointerEvent::Move) { - QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); + QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); } #endif @@ -446,7 +449,7 @@ void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { #if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) if (event.getType() == PointerEvent::Move) { - QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); + QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); } #endif @@ -485,7 +488,7 @@ void Web3DOverlay::handlePointerEventAsMouse(const PointerEvent& event) { return; } - QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); + QMouseEvent mouseEvent(type, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index ad4230c55b..cdeba865ba 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -315,11 +315,13 @@ void WebEntityRenderer::loadSourceURL() { } void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { - if (!_lastLocked && _webSurface && _pressed) { - // If the user mouses off the entity while the button is down, simulate a touch end. + if (!_lastLocked && _webSurface && _pressed && event.sendReleaseOnHoverLeave()) { PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), event.getButton(), event.getButtons(), event.getKeyboardModifiers()); handlePointerEvent(endEvent); + // QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited. + PointerEvent endMoveEvent(PointerEvent::Move, event.getID()); + handlePointerEvent(endMoveEvent); } } @@ -396,7 +398,7 @@ void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { #if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) if (event.getType() == PointerEvent::Move) { - QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); + QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); } #endif @@ -414,7 +416,7 @@ void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { #if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) if (event.getType() == PointerEvent::Move) { - QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, Qt::NoModifier); + QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); } #endif diff --git a/libraries/pointers/src/pointers/PickCacheOptimizer.h b/libraries/pointers/src/pointers/PickCacheOptimizer.h index f3bdbb87b0..18285f3a6f 100644 --- a/libraries/pointers/src/pointers/PickCacheOptimizer.h +++ b/libraries/pointers/src/pointers/PickCacheOptimizer.h @@ -71,18 +71,15 @@ void PickCacheOptimizer::update(QHash> pick = std::static_pointer_cast>(picks[uid]); - if (!pick->isEnabled() || pick->getFilter().doesPickNothing() || pick->getMaxDistance() < 0.0f) { - continue; - } T mathematicalPick = pick->getMathematicalPick(); + PickResultPointer res = pick->getDefaultResult(mathematicalPick.toVariantMap()); - if (!mathematicalPick) { + if (!pick->isEnabled() || pick->getFilter().doesPickNothing() || pick->getMaxDistance() < 0.0f || !mathematicalPick) { + pick->setPickResult(res); continue; } - PickResultPointer res = pick->getDefaultResult(mathematicalPick.toVariantMap()); - if (pick->getFilter().doesPickEntities()) { PickCacheKey entityKey = { pick->getFilter().getEntityFlags(), pick->getIncludeItems(), pick->getIgnoreItems() }; if (!checkAndCompareCachedResults(mathematicalPick, results, res, entityKey)) { diff --git a/libraries/pointers/src/pointers/Pointer.cpp b/libraries/pointers/src/pointers/Pointer.cpp index f57bded5ad..354669a55e 100644 --- a/libraries/pointers/src/pointers/Pointer.cpp +++ b/libraries/pointers/src/pointers/Pointer.cpp @@ -59,14 +59,45 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p // TODO: avatars? auto pointerManager = DependencyManager::get(); + // NOTE: After this loop: _prevButtons = buttons that were removed + // If switching to disabled or should stop triggering, release all buttons + Buttons buttons; + Buttons newButtons; + Buttons sameButtons; + const std::string PRIMARY_BUTTON = "Primary"; + bool primaryPressed = false; + if (_enabled && shouldTrigger()) { + buttons = getPressedButtons(); + primaryPressed = buttons.find(PRIMARY_BUTTON) != buttons.end(); + for (const std::string& button : buttons) { + if (_prevButtons.find(button) == _prevButtons.end()) { + newButtons.insert(button); + } else { + sameButtons.insert(button); + _prevButtons.erase(button); + } + } + } + // Hover events + bool doHover = shouldHover(); Pointer::PickedObject hoveredObject = getHoveredObject(pickResult); PointerEvent hoveredEvent = buildPointerEvent(hoveredObject, pickResult); hoveredEvent.setType(PointerEvent::Move); hoveredEvent.setID(pointerID); - // TODO: set buttons on hover events - hoveredEvent.setButton(PointerEvent::NoButtons); - if (_enabled && _hover && shouldHover()) { + bool releaseOnHoverLeave = !primaryPressed || (!_enabled && _prevEnabled) || (!doHover && _prevDoHover); + hoveredEvent.setReleaseOnHoverLeave(releaseOnHoverLeave); + + // if shouldHover && !_prevDoHover, only send hoverBegin + if (_enabled && _hover && doHover && !_prevDoHover) { + if (hoveredObject.type == ENTITY) { + emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); + } else if (hoveredObject.type == OVERLAY) { + emit pointerManager->hoverBeginOverlay(hoveredObject.objectID, hoveredEvent); + } else if (hoveredObject.type == HUD) { + emit pointerManager->hoverBeginHUD(hoveredObject.objectID, hoveredEvent); + } + } else if (_enabled && _hover && doHover) { if (hoveredObject.type == OVERLAY) { if (_prevHoveredObject.type == OVERLAY) { if (hoveredObject.objectID == _prevHoveredObject.objectID) { @@ -74,6 +105,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p } else { PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject, pickResult); prevHoveredEvent.setID(pointerID); + prevHoveredEvent.setReleaseOnHoverLeave(releaseOnHoverLeave); emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, prevHoveredEvent); emit pointerManager->hoverBeginOverlay(hoveredObject.objectID, hoveredEvent); } @@ -95,6 +127,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p } else { PointerEvent prevHoveredEvent = buildPointerEvent(_prevHoveredObject, pickResult); prevHoveredEvent.setID(pointerID); + prevHoveredEvent.setReleaseOnHoverLeave(releaseOnHoverLeave); emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, prevHoveredEvent); emit pointerManager->hoverBeginEntity(hoveredObject.objectID, hoveredEvent); } @@ -123,20 +156,15 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p } } - // Trigger events - Buttons buttons; - Buttons newButtons; - Buttons sameButtons; - // NOTE: After this loop: _prevButtons = buttons that were removed - // If switching to disabled or should stop triggering, release all buttons - if (_enabled && shouldTrigger()) { - buttons = getPressedButtons(); - for (const std::string& button : buttons) { - if (_prevButtons.find(button) == _prevButtons.end()) { - newButtons.insert(button); - } else { - sameButtons.insert(button); - _prevButtons.erase(button); + if (_hover) { + // send hoverEnd events if we disable the pointer, disable hovering, or actually stop hovering over an object + if ((!_enabled && _prevEnabled) || (!doHover && _prevDoHover) || (hoveredObject.type == NONE && _prevHoveredObject.type != NONE)) { + if (_prevHoveredObject.type == ENTITY) { + emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); + } else if (_prevHoveredObject.type == OVERLAY) { + emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent); + } else if (_prevHoveredObject.type == HUD) { + emit pointerManager->hoverEndHUD(_prevHoveredObject.objectID, hoveredEvent); } } } @@ -156,15 +184,13 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p } _triggeredObjects[button] = hoveredObject; } - // We still want other trigger events for Focus buttons, but they only set focus on triggerBegin - hoveredEvent.setShouldFocus(false); // Trigger continue for (const std::string& button : sameButtons) { PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult); triggeredEvent.setID(pointerID); triggeredEvent.setType(PointerEvent::Move); - hoveredEvent.setButton(chooseButton(button)); + triggeredEvent.setButton(chooseButton(button)); if (_triggeredObjects[button].type == ENTITY) { emit pointerManager->triggerContinueEntity(_triggeredObjects[button].objectID, triggeredEvent); } else if (_triggeredObjects[button].type == OVERLAY) { @@ -179,7 +205,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p PointerEvent triggeredEvent = buildPointerEvent(_triggeredObjects[button], pickResult); triggeredEvent.setID(pointerID); triggeredEvent.setType(PointerEvent::Release); - hoveredEvent.setButton(chooseButton(button)); + triggeredEvent.setButton(chooseButton(button)); if (_triggeredObjects[button].type == ENTITY) { emit pointerManager->triggerEndEntity(_triggeredObjects[button].objectID, triggeredEvent); } else if (_triggeredObjects[button].type == OVERLAY) { @@ -192,6 +218,8 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p _prevHoveredObject = hoveredObject; _prevButtons = buttons; + _prevEnabled = _enabled; + _prevDoHover = doHover; } PointerEvent::Button Pointer::chooseButton(const std::string& button) { diff --git a/libraries/pointers/src/pointers/Pointer.h b/libraries/pointers/src/pointers/Pointer.h index fc43e121f0..d8661436b9 100644 --- a/libraries/pointers/src/pointers/Pointer.h +++ b/libraries/pointers/src/pointers/Pointer.h @@ -89,6 +89,8 @@ protected: private: PickedObject _prevHoveredObject; Buttons _prevButtons; + bool _prevEnabled { false }; + bool _prevDoHover { false }; std::unordered_map _triggeredObjects; PointerEvent::Button chooseButton(const std::string& button); diff --git a/libraries/shared/src/PointerEvent.h b/libraries/shared/src/PointerEvent.h index 239e7c6e4e..f6b1a81602 100644 --- a/libraries/shared/src/PointerEvent.h +++ b/libraries/shared/src/PointerEvent.h @@ -37,9 +37,9 @@ public: PointerEvent(); PointerEvent(EventType type, uint32_t id, - const glm::vec2& pos2D, const glm::vec3& pos3D, - const glm::vec3& normal, const glm::vec3& direction, - Button button, uint32_t buttons = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::KeyboardModifier::NoModifier); + const glm::vec2& pos2D = glm::vec2(NAN), const glm::vec3& pos3D = glm::vec3(NAN), + const glm::vec3& normal = glm::vec3(NAN), const glm::vec3& direction = glm::vec3(NAN), + Button button = NoButtons, uint32_t buttons = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::KeyboardModifier::NoModifier); static QScriptValue toScriptValue(QScriptEngine* engine, const PointerEvent& event); static void fromScriptValue(const QScriptValue& object, PointerEvent& event); @@ -56,11 +56,13 @@ public: uint32_t getButtons() const { return _buttons; } Qt::KeyboardModifiers getKeyboardModifiers() const { return _keyboardModifiers; } bool shouldFocus() const { return _shouldFocus; } + bool sendReleaseOnHoverLeave() const { return _releaseOnHoverLeave; } void setID(uint32_t id) { _id = id; } void setType(EventType type) { _type = type; } void setButton(Button button); void setShouldFocus(bool focus) { _shouldFocus = focus; } + void setReleaseOnHoverLeave(bool releaseOnHoverLeave) { _releaseOnHoverLeave = releaseOnHoverLeave; } private: EventType _type; @@ -75,6 +77,7 @@ private: Qt::KeyboardModifiers _keyboardModifiers; // set of keys held when event was generated bool _shouldFocus { true }; + bool _releaseOnHoverLeave { true }; }; QDebug& operator<<(QDebug& dbg, const PointerEvent& p); diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt index 334cce97e5..f28157ff97 100644 --- a/libraries/ui/CMakeLists.txt +++ b/libraries/ui/CMakeLists.txt @@ -1,7 +1,6 @@ set(TARGET_NAME ui) setup_hifi_library(OpenGL Network Qml Quick Script WebChannel WebEngine WebSockets XmlPatterns) -link_hifi_libraries(shared networking gl audio pointers) -include_hifi_library_headers(controllers) +link_hifi_libraries(shared networking gl audio) # Required for some low level GL interaction in the OffscreenQMLSurface target_opengl() diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 204986090c..08662031da 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -49,8 +49,6 @@ #include "Logging.h" -#include - Q_LOGGING_CATEGORY(trace_render_qml, "trace.render.qml") Q_LOGGING_CATEGORY(trace_render_qml_gl, "trace.render.qml.gl") Q_LOGGING_CATEGORY(offscreenFocus, "hifi.offscreen.focus") @@ -531,13 +529,6 @@ bool OffscreenQmlSurface::allowNewFrame(uint8_t fps) { } OffscreenQmlSurface::OffscreenQmlSurface() { - auto pointerManager = DependencyManager::get(); - connect(pointerManager.data(), &PointerManager::hoverBeginHUD, this, &OffscreenQmlSurface::handlePointerEvent); - connect(pointerManager.data(), &PointerManager::hoverContinueHUD, this, &OffscreenQmlSurface::handlePointerEvent); - connect(pointerManager.data(), &PointerManager::hoverEndHUD, this, &OffscreenQmlSurface::handlePointerEvent); - connect(pointerManager.data(), &PointerManager::triggerBeginHUD, this, &OffscreenQmlSurface::handlePointerEvent); - connect(pointerManager.data(), &PointerManager::triggerContinueHUD, this, &OffscreenQmlSurface::handlePointerEvent); - connect(pointerManager.data(), &PointerManager::triggerEndHUD, this, &OffscreenQmlSurface::handlePointerEvent); } OffscreenQmlSurface::~OffscreenQmlSurface() { @@ -941,8 +932,14 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even transformedPos, mouseEvent->screenPos(), mouseEvent->button(), mouseEvent->buttons(), mouseEvent->modifiers()); - if (sendMouseEvent(mappedEvent)) { - return true; + if (event->type() == QEvent::MouseMove) { + // TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install + // need to investigate into why this crash is happening. + //_qmlContext->setContextProperty("lastMousePosition", mouseEvent.localPos()); + } + mappedEvent.ignore(); + if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) { + return mappedEvent.isAccepted(); } break; } @@ -954,46 +951,6 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even return false; } -void OffscreenQmlSurface::handlePointerEvent(const QUuid& id, const PointerEvent& event) { - if (_paused) { - return; - } - - QEvent::Type type = QEvent::Type::MouseMove; - switch (event.getType()) { - case PointerEvent::Press: - type = QEvent::Type::MouseButtonPress; - break; - case PointerEvent::DoublePress: - type = QEvent::Type::MouseButtonDblClick; - break; - case PointerEvent::Release: - type = QEvent::Type::MouseButtonRelease; - break; - case PointerEvent::Move: - type = QEvent::Type::MouseMove; - break; - default: - break; - } - QPointF screenPos(event.getPos2D().x, event.getPos2D().y); - QMouseEvent mouseEvent(type, screenPos, Qt::MouseButton(event.getButton()), Qt::MouseButtons(event.getButtons()), event.getKeyboardModifiers()); - sendMouseEvent(mouseEvent); -} - -bool OffscreenQmlSurface::sendMouseEvent(QMouseEvent& mouseEvent) { - if (mouseEvent.type() == QEvent::MouseMove) { - // TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install - // need to investigate into why this crash is happening. - //_qmlContext->setContextProperty("lastMousePosition", mouseEvent.localPos()); - } - mouseEvent.ignore(); - if (QCoreApplication::sendEvent(_quickWindow, &mouseEvent)) { - return mouseEvent.isAccepted(); - } - return false; -} - void OffscreenQmlSurface::pause() { _paused = true; } diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 3deab07411..12ee9e59a1 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -30,8 +30,6 @@ class QQmlContext; class QQmlComponent; class QQuickWindow; class QQuickItem; -class QMouseEvent; -class PointerEvent; // GPU resources are typically buffered for one copy being used by the renderer, // one copy in flight, and one copy being used by the receiver @@ -137,7 +135,6 @@ private: private slots: void updateQuick(); void onFocusObjectChanged(QObject* newFocus); - void handlePointerEvent(const QUuid& id, const PointerEvent& event); private: QQuickWindow* _quickWindow { nullptr }; @@ -164,8 +161,6 @@ private: QWindow* _proxyWindow { nullptr }; QQuickItem* _currentFocusItem { nullptr }; - - bool sendMouseEvent(QMouseEvent& mouseEvent); }; #endif From 6f3c61c4e0314baa448cc09a8ff31470f21bdae6 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 2 Nov 2017 17:57:52 -0700 Subject: [PATCH 39/46] Slightly modify invalid cert language --- .../commerce/inspectionCertificate/InspectionCertificate.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml index 6a7a181b92..b6c29a1fad 100644 --- a/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml +++ b/interface/resources/qml/hifi/commerce/inspectionCertificate/InspectionCertificate.qml @@ -86,7 +86,7 @@ Rectangle { root.dateOfPurchase = ""; root.itemEdition = "Uncertified Copy"; - errorText.text = "The certificate associated with this entity is invalid."; + errorText.text = "The information associated with this item has been modified and it no longer matches the original certified item."; errorText.color = hifi.colors.baseGray; } else if (certStatus === 4) { // CERTIFICATE_STATUS_OWNER_VERIFICATION_FAILED root.isCertificateInvalid = true; @@ -99,7 +99,7 @@ Rectangle { root.dateOfPurchase = ""; root.itemEdition = "Uncertified Copy"; - errorText.text = "The certificate associated with this entity is invalid."; + errorText.text = "The avatar who rezzed this item doesn't own it."; errorText.color = hifi.colors.baseGray; } else { console.log("Unknown certificate status received from ledger signal!"); From babaef8399012346342f2fb5651d7ac7a1aa223c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Fri, 3 Nov 2017 05:59:59 -0700 Subject: [PATCH 40/46] Fix the audio input device peak meters --- interface/resources/qml/hifi/audio/Audio.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/interface/resources/qml/hifi/audio/Audio.qml b/interface/resources/qml/hifi/audio/Audio.qml index 12c2ec1835..87ddce49ca 100644 --- a/interface/resources/qml/hifi/audio/Audio.qml +++ b/interface/resources/qml/hifi/audio/Audio.qml @@ -213,8 +213,8 @@ Rectangle { anchors.right: parent.right peak: model.peak; anchors.verticalCenter: parent.verticalCenter - visible: (bar.currentIndex === 1 && selectedHMD && isVR) || - (bar.currentIndex === 0 && selectedDesktop && !isVR) && + visible: ((bar.currentIndex === 1 && isVR) || + (bar.currentIndex === 0 && !isVR)) && Audio.devices.input.peakValuesAvailable; } } From 317141817317668cfaa652c08a909aab0018694b Mon Sep 17 00:00:00 2001 From: Nex-Pro Date: Fri, 3 Nov 2017 17:21:35 +0000 Subject: [PATCH 41/46] Update NameCard.qml --- interface/resources/qml/hifi/NameCard.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/NameCard.qml b/interface/resources/qml/hifi/NameCard.qml index b122f71760..f16b8de337 100644 --- a/interface/resources/qml/hifi/NameCard.qml +++ b/interface/resources/qml/hifi/NameCard.qml @@ -433,7 +433,7 @@ Item { anchors.verticalCenter: nameCardRemoveConnectionImage.verticalCenter x: 240 onClicked: { - console.log("Vist user button clicked."); // Remove after debugging. + console.log("Vist user button clicked."); // Remove after debugging. AddressManager.goToUser(thisNameCard.userName, false); UserActivityLogger.palAction("go_to_user", thisNameCard.userName); } From c496429a85cad2616f6c9f696ccbae19dbad3d25 Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Fri, 3 Nov 2017 10:40:25 -0700 Subject: [PATCH 42/46] Add Machine Fingerprint to receive_at endpoint txn' --- interface/src/commerce/Ledger.cpp | 10 ++++++++-- interface/src/commerce/Ledger.h | 2 +- interface/src/commerce/Wallet.cpp | 4 +++- 3 files changed, 12 insertions(+), 4 deletions(-) diff --git a/interface/src/commerce/Ledger.cpp b/interface/src/commerce/Ledger.cpp index 904847cb5f..f607c923ee 100644 --- a/interface/src/commerce/Ledger.cpp +++ b/interface/src/commerce/Ledger.cpp @@ -90,7 +90,7 @@ void Ledger::buy(const QString& hfc_key, int cost, const QString& asset_id, cons signedSend("transaction", transactionString, hfc_key, "buy", "buySuccess", "buyFailure", controlled_failure); } -bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) { +bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key, const QString& machine_fingerprint) { auto accountManager = DependencyManager::get(); if (!accountManager->isLoggedIn()) { qCWarning(commerce) << "Cannot set receiveAt when not logged in."; @@ -99,7 +99,13 @@ bool Ledger::receiveAt(const QString& hfc_key, const QString& old_key) { return false; // We know right away that we will fail, so tell the caller. } - signedSend("public_key", hfc_key.toUtf8(), old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); + QJsonObject transaction; + transaction["hfc_key"] = hfc_key; + transaction["machine_fingerprint"] = machine_fingerprint; + QJsonDocument transactionDoc{ transaction }; + auto transactionString = transactionDoc.toJson(QJsonDocument::Compact); + + signedSend("transaction", transactionString, old_key, "receive_at", "receiveAtSuccess", "receiveAtFailure"); return true; // Note that there may still be an asynchronous signal of failure that callers might be interested in. } diff --git a/interface/src/commerce/Ledger.h b/interface/src/commerce/Ledger.h index 42eb0ffc49..54f3f780f3 100644 --- a/interface/src/commerce/Ledger.h +++ b/interface/src/commerce/Ledger.h @@ -26,7 +26,7 @@ class Ledger : public QObject, public Dependency { public: void buy(const QString& hfc_key, int cost, const QString& asset_id, const QString& inventory_key, const bool controlled_failure = false); - bool receiveAt(const QString& hfc_key, const QString& old_key); + bool receiveAt(const QString& hfc_key, const QString& old_key, const QString& machine_fingerprint); void balance(const QStringList& keys); void inventory(const QStringList& keys); void history(const QStringList& keys); diff --git a/interface/src/commerce/Wallet.cpp b/interface/src/commerce/Wallet.cpp index 85632ff8f1..9bc8dc9e43 100644 --- a/interface/src/commerce/Wallet.cpp +++ b/interface/src/commerce/Wallet.cpp @@ -16,6 +16,7 @@ #include "ui/ImageProvider.h" #include "scripting/HMDScriptingInterface.h" +#include #include #include #include @@ -541,7 +542,8 @@ bool Wallet::generateKeyPair() { // 2. It is maximally private, and we can step back from that later if desired. // 3. It maximally exercises all the machinery, so we are most likely to surface issues now. auto ledger = DependencyManager::get(); - return ledger->receiveAt(key, oldKey); + QString machineFingerprint = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); + return ledger->receiveAt(key, oldKey, machineFingerprint); } QStringList Wallet::listPublicKeys() { From ce58b6c4017b2f0343694417065c6363af33235c Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Fri, 3 Nov 2017 17:15:30 -0700 Subject: [PATCH 43/46] trying to fix HUD events, abstracting touch event logic (wip), seth's comments --- interface/src/Application.cpp | 44 +---- interface/src/Application.h | 2 - interface/src/raypick/LaserPointer.cpp | 2 +- .../src/raypick/PickScriptingInterface.cpp | 4 +- .../src/raypick/PointerScriptingInterface.cpp | 2 +- .../src/RenderableWebEntityItem.cpp | 106 +----------- .../src/RenderableWebEntityItem.h | 8 +- libraries/pointers/src/pointers/Pick.h | 9 + .../src/pointers/PickCacheOptimizer.h | 9 +- .../pointers/src/pointers/PickManager.cpp | 10 +- libraries/pointers/src/pointers/PickManager.h | 10 +- libraries/pointers/src/pointers/Pointer.cpp | 18 +- .../pointers/src/pointers/PointerManager.cpp | 16 +- .../pointers/src/pointers/PointerManager.h | 19 +- libraries/shared/src/PointerEvent.cpp | 25 ++- libraries/shared/src/PointerEvent.h | 26 +-- libraries/ui/CMakeLists.txt | 3 +- libraries/ui/src/OffscreenUi.cpp | 42 +++++ libraries/ui/src/OffscreenUi.h | 4 + libraries/ui/src/ui/OffscreenQmlSurface.cpp | 163 +++++++++++++++--- libraries/ui/src/ui/OffscreenQmlSurface.h | 15 +- 21 files changed, 304 insertions(+), 233 deletions(-) diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index d81332e7d5..d9770818e1 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -1504,14 +1504,6 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo setKeyboardFocusEntity(UNKNOWN_ENTITY_ID); }); - auto pointerManager = DependencyManager::get(); - connect(pointerManager.data(), &PointerManager::hoverBeginHUD, this, &Application::handleHUDPointerEvent); - connect(pointerManager.data(), &PointerManager::hoverContinueHUD, this, &Application::handleHUDPointerEvent); - connect(pointerManager.data(), &PointerManager::hoverEndHUD, this, &Application::handleHUDPointerEvent); - connect(pointerManager.data(), &PointerManager::triggerBeginHUD, this, &Application::handleHUDPointerEvent); - connect(pointerManager.data(), &PointerManager::triggerContinueHUD, this, &Application::handleHUDPointerEvent); - connect(pointerManager.data(), &PointerManager::triggerEndHUD, this, &Application::handleHUDPointerEvent); - // Add periodic checks to send user activity data static int CHECK_NEARBY_AVATARS_INTERVAL_MS = 10000; static int NEARBY_AVATAR_RADIUS_METERS = 10; @@ -3304,7 +3296,7 @@ void Application::mouseMoveEvent(QMouseEvent* event) { auto offscreenUi = DependencyManager::get(); auto eventPosition = compositor.getMouseEventPosition(event); - QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget); + QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition); auto button = event->button(); auto buttons = event->buttons(); // Determine if the ReticleClick Action is 1 and if so, fake include the LeftMouseButton @@ -3350,7 +3342,7 @@ void Application::mousePressEvent(QMouseEvent* event) { offscreenUi->unfocusWindows(); auto eventPosition = getApplicationCompositor().getMouseEventPosition(event); - QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget); + QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition); QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), @@ -3380,7 +3372,7 @@ void Application::mousePressEvent(QMouseEvent* event) { void Application::mouseDoublePressEvent(QMouseEvent* event) { auto offscreenUi = DependencyManager::get(); auto eventPosition = getApplicationCompositor().getMouseEventPosition(event); - QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget); + QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition); QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), @@ -3406,7 +3398,7 @@ void Application::mouseReleaseEvent(QMouseEvent* event) { auto offscreenUi = DependencyManager::get(); auto eventPosition = getApplicationCompositor().getMouseEventPosition(event); - QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition, _glWidget); + QPointF transformedPos = offscreenUi->mapToVirtualScreen(eventPosition); QMouseEvent mappedEvent(event->type(), transformedPos, event->screenPos(), event->button(), @@ -7487,32 +7479,4 @@ void Application::setAvatarOverrideUrl(const QUrl& url, bool save) { _avatarOverrideUrl = url; _saveAvatarOverrideUrl = save; } - -void Application::handleHUDPointerEvent(const QUuid& id, const PointerEvent& event) { - QEvent::Type type = QEvent::Type::MouseMove; - switch (event.getType()) { - case PointerEvent::Press: - type = QEvent::Type::MouseButtonPress; - break; - case PointerEvent::DoublePress: - type = QEvent::Type::MouseButtonDblClick; - break; - case PointerEvent::Release: - type = QEvent::Type::MouseButtonRelease; - break; - case PointerEvent::Move: - type = QEvent::Type::MouseMove; - break; - default: - break; - } - - QPointF screenPos(event.getPos2D().x, event.getPos2D().y); - //QMouseEvent mouseEvent(type, screenPos, Qt::MouseButton(event.getButton()), Qt::MouseButtons(event.getButtons()), event.getKeyboardModifiers()); - QMouseEvent moveEvent(QEvent::Type::MouseMove, screenPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); - sendEvent(_glWidget, &moveEvent); - QMouseEvent mouseEvent(type, screenPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); - sendEvent(_glWidget, &mouseEvent); -} - #include "Application.moc" diff --git a/interface/src/Application.h b/interface/src/Application.h index a229c4b91a..d2a55db216 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -434,8 +434,6 @@ private slots: void handleSandboxStatus(QNetworkReply* reply); void switchDisplayMode(); - - void handleHUDPointerEvent(const QUuid& id, const PointerEvent& event); private: static void initDisplay(); void init(); diff --git a/interface/src/raypick/LaserPointer.cpp b/interface/src/raypick/LaserPointer.cpp index 2506c6222d..7d681cd35d 100644 --- a/interface/src/raypick/LaserPointer.cpp +++ b/interface/src/raypick/LaserPointer.cpp @@ -303,7 +303,7 @@ PointerEvent LaserPointer::buildPointerEvent(const PickedObject& target, const Q } else if (target.type == HUD) { pos2D = DependencyManager::get()->calculatePos2DFromHUD(intersection); } - return PointerEvent(PointerEvent::Move, 0, pos2D, intersection, surfaceNormal, direction, PointerEvent::NoButtons); + return PointerEvent(pos2D, intersection, surfaceNormal, direction); } glm::vec3 LaserPointer::intersectRayWithXYPlane(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& point, const glm::quat rotation, const glm::vec3& registration) const { diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index a59e96ccbf..445a7e9b69 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -25,7 +25,7 @@ unsigned int PickScriptingInterface::createPick(const PickQuery::PickType type, case PickQuery::PickType::Ray: return createRayPick(properties); default: - return 0; + return PickManager::INVALID_PICK_ID; } } @@ -78,7 +78,7 @@ unsigned int PickScriptingInterface::createRayPick(const QVariant& properties) { return DependencyManager::get()->addPick(PickQuery::Ray, std::make_shared(position, direction, filter, maxDistance, enabled)); } - return 0; + return PickManager::INVALID_PICK_ID; } void PickScriptingInterface::enablePick(unsigned int uid) { diff --git a/interface/src/raypick/PointerScriptingInterface.cpp b/interface/src/raypick/PointerScriptingInterface.cpp index b7211830a8..2f044acc4c 100644 --- a/interface/src/raypick/PointerScriptingInterface.cpp +++ b/interface/src/raypick/PointerScriptingInterface.cpp @@ -26,7 +26,7 @@ unsigned int PointerScriptingInterface::createPointer(const PickQuery::PickType& case PickQuery::PickType::Ray: return createLaserPointer(properties); default: - return 0; + return PointerEvent::INVALID_POINTER_ID; } } diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index cdeba865ba..b87a1a994f 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -315,111 +315,17 @@ void WebEntityRenderer::loadSourceURL() { } void WebEntityRenderer::hoverLeaveEntity(const PointerEvent& event) { - if (!_lastLocked && _webSurface && _pressed && event.sendReleaseOnHoverLeave()) { - PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), - event.getButton(), event.getButtons(), event.getKeyboardModifiers()); - handlePointerEvent(endEvent); - // QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited. - PointerEvent endMoveEvent(PointerEvent::Move, event.getID()); - handlePointerEvent(endMoveEvent); + if (!_lastLocked && _webSurface) { + _webSurface->hoverEndEvent(event, _touchDevice); } } -void WebEntityRenderer::handlePointerEvent(const PointerEvent& event) { +void WebEntityRenderer::handlePointerEvent(PointerEvent& event) { + event.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _lastDPI)); // Ignore mouse interaction if we're locked - if (_lastLocked || !_webSurface) { - return; + if (!_lastLocked && _webSurface) { + _webSurface->handlePointerEvent(event, _touchDevice); } - - if (event.getType() == PointerEvent::Press) { - _pressed = true; - } else if (event.getType() == PointerEvent::Release) { - _pressed = false; - } - - glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _lastDPI); - QPointF windowPoint(windowPos.x, windowPos.y); - - Qt::TouchPointState state = Qt::TouchPointStationary; - if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) { - state = Qt::TouchPointPressed; - } else if (event.getType() == PointerEvent::Release) { - state = Qt::TouchPointReleased; - } else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].pos()) { - state = Qt::TouchPointMoved; - } - - QEvent::Type touchType = QEvent::TouchUpdate; - if (_activeTouchPoints.empty()) { - // If the first active touch point is being created, send a begin - touchType = QEvent::TouchBegin; - } if (state == Qt::TouchPointReleased && _activeTouchPoints.size() == 1 && _activeTouchPoints.count(event.getID())) { - // If the last active touch point is being released, send an end - touchType = QEvent::TouchEnd; - } - - { - QTouchEvent::TouchPoint point; - point.setId(event.getID()); - point.setState(state); - point.setPos(windowPoint); - point.setScreenPos(windowPoint); - _activeTouchPoints[event.getID()] = point; - } - - QTouchEvent touchEvent(touchType, &_touchDevice, event.getKeyboardModifiers()); - { - QList touchPoints; - Qt::TouchPointStates touchPointStates; - for (const auto& entry : _activeTouchPoints) { - touchPointStates |= entry.second.state(); - touchPoints.push_back(entry.second); - } - - touchEvent.setWindow(_webSurface->getWindow()); - touchEvent.setDevice(&_touchDevice); - touchEvent.setTarget(_webSurface->getRootItem()); - touchEvent.setTouchPoints(touchPoints); - touchEvent.setTouchPointStates(touchPointStates); - } - - // Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover. - // - // In Qt 5.9 mouse events must be sent before touch events to make sure some QtQuick components will - // receive mouse events - Qt::MouseButton button = Qt::NoButton; - Qt::MouseButtons buttons = Qt::NoButton; - if (event.getButton() == PointerEvent::PrimaryButton) { - button = Qt::LeftButton; - } - if (event.getButtons() & PointerEvent::PrimaryButton) { - buttons |= Qt::LeftButton; - } - -#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) - if (event.getType() == PointerEvent::Move) { - QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); - QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); - } -#endif - - if (touchType == QEvent::TouchBegin) { - _touchBeginAccepted = QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent); - } else if (_touchBeginAccepted) { - QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent); - } - - // If this was a release event, remove the point from the active touch points - if (state == Qt::TouchPointReleased) { - _activeTouchPoints.erase(event.getID()); - } - -#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) - if (event.getType() == PointerEvent::Move) { - QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); - QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); - } -#endif } void WebEntityRenderer::setProxyWindow(QWindow* proxyWindow) { diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.h b/libraries/entities-renderer/src/RenderableWebEntityItem.h index 8adbc17a75..84f2a6ccb7 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.h +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.h @@ -12,8 +12,6 @@ #include #include "RenderableEntityItem.h" -#include - class OffscreenQmlSurface; class PointerEvent; @@ -28,7 +26,7 @@ public: WebEntityRenderer(const EntityItemPointer& entity); Q_INVOKABLE void hoverLeaveEntity(const PointerEvent& event); - Q_INVOKABLE void handlePointerEvent(const PointerEvent& event); + Q_INVOKABLE void handlePointerEvent(PointerEvent& event); protected: virtual void onRemoveFromSceneTyped(const TypedEntityPointer& entity) override; @@ -66,10 +64,6 @@ private: QTimer _timer; uint64_t _lastRenderTime { 0 }; Transform _renderTransform; - - bool _pressed{ false }; - bool _touchBeginAccepted{ false }; - std::map _activeTouchPoints; }; } } // namespace diff --git a/libraries/pointers/src/pointers/Pick.h b/libraries/pointers/src/pointers/Pick.h index 9ab17f87d8..e4ce5d3dce 100644 --- a/libraries/pointers/src/pointers/Pick.h +++ b/libraries/pointers/src/pointers/Pick.h @@ -206,4 +206,13 @@ public: virtual PickResultPointer getHUDIntersection(const T& pick) = 0; }; +namespace std { + template <> + struct hash { + size_t operator()(const PickQuery::PickType& a) const { + return a; + } + }; +} + #endif // hifi_Pick_h diff --git a/libraries/pointers/src/pointers/PickCacheOptimizer.h b/libraries/pointers/src/pointers/PickCacheOptimizer.h index 18285f3a6f..46038d14b5 100644 --- a/libraries/pointers/src/pointers/PickCacheOptimizer.h +++ b/libraries/pointers/src/pointers/PickCacheOptimizer.h @@ -37,7 +37,7 @@ template class PickCacheOptimizer { public: - void update(QHash>& picks, bool shouldPickHUD); + void update(std::unordered_map>& picks, bool shouldPickHUD); protected: typedef std::unordered_map> PickCache; @@ -67,10 +67,11 @@ void PickCacheOptimizer::cacheResult(const bool intersects, const PickResultP } template -void PickCacheOptimizer::update(QHash>& picks, bool shouldPickHUD) { +void PickCacheOptimizer::update(std::unordered_map>& picks, bool shouldPickHUD) { PickCache results; - for (const auto& uid : picks.keys()) { - std::shared_ptr> pick = std::static_pointer_cast>(picks[uid]); + for (const auto& pickPair : picks) { + unsigned int uid = pickPair.first; + std::shared_ptr> pick = std::static_pointer_cast>(pickPair.second); T mathematicalPick = pick->getMathematicalPick(); PickResultPointer res = pick->getDefaultResult(mathematicalPick.toVariantMap()); diff --git a/libraries/pointers/src/pointers/PickManager.cpp b/libraries/pointers/src/pointers/PickManager.cpp index 903113d79d..cb9d715ba9 100644 --- a/libraries/pointers/src/pointers/PickManager.cpp +++ b/libraries/pointers/src/pointers/PickManager.cpp @@ -13,7 +13,7 @@ PickManager::PickManager() { } unsigned int PickManager::addPick(PickQuery::PickType type, const std::shared_ptr pick) { - unsigned int id = 0; + unsigned int id = INVALID_PICK_ID; withWriteLock([&] { // Don't let the pick IDs overflow if (_nextPickID < UINT32_MAX) { @@ -29,7 +29,7 @@ std::shared_ptr PickManager::findPick(unsigned int uid) const { return resultWithReadLock>([&] { auto type = _typeMap.find(uid); if (type != _typeMap.end()) { - return _picks[type.value()][uid]; + return _picks.find(type->second)->second.find(uid)->second; } return std::shared_ptr(); }); @@ -39,8 +39,8 @@ void PickManager::removePick(unsigned int uid) { withWriteLock([&] { auto type = _typeMap.find(uid); if (type != _typeMap.end()) { - _picks[type.value()].remove(uid); - _typeMap.remove(uid); + _picks[type->second].erase(uid); + _typeMap.erase(uid); } }); } @@ -89,7 +89,7 @@ void PickManager::setIncludeItems(unsigned int uid, const QVector& includ } void PickManager::update() { - QHash>> cachedPicks; + std::unordered_map>> cachedPicks; withReadLock([&] { cachedPicks = _picks; }); diff --git a/libraries/pointers/src/pointers/PickManager.h b/libraries/pointers/src/pointers/PickManager.h index 7ff4fcc941..0794ac0a41 100644 --- a/libraries/pointers/src/pointers/PickManager.h +++ b/libraries/pointers/src/pointers/PickManager.h @@ -37,16 +37,16 @@ public: void setCalculatePos2DFromHUDOperator(std::function calculatePos2DFromHUDOperator) { _calculatePos2DFromHUDOperator = calculatePos2DFromHUDOperator; } glm::vec2 calculatePos2DFromHUD(const glm::vec3& intersection) { return _calculatePos2DFromHUDOperator(intersection); } + static const unsigned int INVALID_PICK_ID { 0 }; + protected: std::function _shouldPickHUDOperator; std::function _calculatePos2DFromHUDOperator; std::shared_ptr findPick(unsigned int uid) const; - QHash>> _picks; - QHash _typeMap; - // 0 = invalid - const unsigned int FIRST_PICK_ID { 1 }; - unsigned int _nextPickID { FIRST_PICK_ID }; + std::unordered_map>> _picks; + std::unordered_map _typeMap; + unsigned int _nextPickID { INVALID_PICK_ID + 1 }; PickCacheOptimizer _rayPickCacheOptimizer; }; diff --git a/libraries/pointers/src/pointers/Pointer.cpp b/libraries/pointers/src/pointers/Pointer.cpp index 354669a55e..0fc5c54ad2 100644 --- a/libraries/pointers/src/pointers/Pointer.cpp +++ b/libraries/pointers/src/pointers/Pointer.cpp @@ -95,7 +95,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p } else if (hoveredObject.type == OVERLAY) { emit pointerManager->hoverBeginOverlay(hoveredObject.objectID, hoveredEvent); } else if (hoveredObject.type == HUD) { - emit pointerManager->hoverBeginHUD(hoveredObject.objectID, hoveredEvent); + emit pointerManager->hoverBeginHUD(hoveredEvent); } } else if (_enabled && _hover && doHover) { if (hoveredObject.type == OVERLAY) { @@ -114,7 +114,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p if (_prevHoveredObject.type == ENTITY) { emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == HUD) { - emit pointerManager->hoverEndHUD(_prevHoveredObject.objectID, hoveredEvent); + emit pointerManager->hoverEndHUD(hoveredEvent); } } } @@ -136,7 +136,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p if (_prevHoveredObject.type == OVERLAY) { emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == HUD) { - emit pointerManager->hoverEndHUD(_prevHoveredObject.objectID, hoveredEvent); + emit pointerManager->hoverEndHUD(hoveredEvent); } } } @@ -144,9 +144,9 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p if (hoveredObject.type == HUD) { if (_prevHoveredObject.type == HUD) { // There's only one HUD - emit pointerManager->hoverContinueHUD(hoveredObject.objectID, hoveredEvent); + emit pointerManager->hoverContinueHUD(hoveredEvent); } else { - emit pointerManager->hoverBeginHUD(hoveredObject.objectID, hoveredEvent); + emit pointerManager->hoverBeginHUD(hoveredEvent); if (_prevHoveredObject.type == ENTITY) { emit pointerManager->hoverEndEntity(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == OVERLAY) { @@ -164,7 +164,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p } else if (_prevHoveredObject.type == OVERLAY) { emit pointerManager->hoverEndOverlay(_prevHoveredObject.objectID, hoveredEvent); } else if (_prevHoveredObject.type == HUD) { - emit pointerManager->hoverEndHUD(_prevHoveredObject.objectID, hoveredEvent); + emit pointerManager->hoverEndHUD(hoveredEvent); } } } @@ -180,7 +180,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p } else if (hoveredObject.type == OVERLAY) { emit pointerManager->triggerBeginOverlay(hoveredObject.objectID, hoveredEvent); } else if (hoveredObject.type == HUD) { - emit pointerManager->triggerBeginHUD(hoveredObject.objectID, hoveredEvent); + emit pointerManager->triggerBeginHUD(hoveredEvent); } _triggeredObjects[button] = hoveredObject; } @@ -196,7 +196,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p } else if (_triggeredObjects[button].type == OVERLAY) { emit pointerManager->triggerContinueOverlay(_triggeredObjects[button].objectID, triggeredEvent); } else if (_triggeredObjects[button].type == HUD) { - emit pointerManager->triggerContinueHUD(_triggeredObjects[button].objectID, triggeredEvent); + emit pointerManager->triggerContinueHUD(triggeredEvent); } } @@ -211,7 +211,7 @@ void Pointer::generatePointerEvents(unsigned int pointerID, const QVariantMap& p } else if (_triggeredObjects[button].type == OVERLAY) { emit pointerManager->triggerEndOverlay(_triggeredObjects[button].objectID, triggeredEvent); } else if (_triggeredObjects[button].type == HUD) { - emit pointerManager->triggerEndHUD(_triggeredObjects[button].objectID, triggeredEvent); + emit pointerManager->triggerEndHUD(triggeredEvent); } _triggeredObjects.erase(button); } diff --git a/libraries/pointers/src/pointers/PointerManager.cpp b/libraries/pointers/src/pointers/PointerManager.cpp index b6d826c6f1..b117b24770 100644 --- a/libraries/pointers/src/pointers/PointerManager.cpp +++ b/libraries/pointers/src/pointers/PointerManager.cpp @@ -8,19 +8,21 @@ #include "PointerManager.h" +#include "PickManager.h" + std::shared_ptr PointerManager::find(unsigned int uid) const { return resultWithReadLock>([&] { auto itr = _pointers.find(uid); if (itr != _pointers.end()) { - return *itr; + return itr->second; } return std::shared_ptr(); }); } unsigned int PointerManager::addPointer(std::shared_ptr pointer) { - unsigned int result = 0; - if (pointer->getRayUID() > 0) { + unsigned int result = PointerEvent::INVALID_POINTER_ID; + if (pointer->getRayUID() != PickManager::INVALID_PICK_ID) { withWriteLock([&] { // Don't let the pointer IDs overflow if (_nextPointerID < UINT32_MAX) { @@ -34,7 +36,7 @@ unsigned int PointerManager::addPointer(std::shared_ptr pointer) { void PointerManager::removePointer(unsigned int uid) { withWriteLock([&] { - _pointers.remove(uid); + _pointers.erase(uid); }); } @@ -75,12 +77,12 @@ const QVariantMap PointerManager::getPrevPickResult(unsigned int uid) const { } void PointerManager::update() { - auto cachedPointers = resultWithReadLock>>([&] { + auto cachedPointers = resultWithReadLock>>([&] { return _pointers; }); - for (const auto& uid : cachedPointers.keys()) { - cachedPointers[uid]->update(uid); + for (const auto& pointerPair : cachedPointers) { + pointerPair.second->update(pointerPair.first); } } diff --git a/libraries/pointers/src/pointers/PointerManager.h b/libraries/pointers/src/pointers/PointerManager.h index fcb347fb82..a415c9dbd9 100644 --- a/libraries/pointers/src/pointers/PointerManager.h +++ b/libraries/pointers/src/pointers/PointerManager.h @@ -40,11 +40,12 @@ public: void update(); + static const unsigned int MOUSE_POINTER_ID { PointerEvent::INVALID_POINTER_ID + 1 }; + private: std::shared_ptr find(unsigned int uid) const; - QHash> _pointers; - // 0 = invalid, 1 = reserved for system mouse - unsigned int _nextPointerID { 2 }; + std::unordered_map> _pointers; + unsigned int _nextPointerID { MOUSE_POINTER_ID + 1 }; signals: void triggerBeginOverlay(const QUuid& id, const PointerEvent& pointerEvent); @@ -61,12 +62,12 @@ signals: void hoverContinueEntity(const QUuid& id, const PointerEvent& pointerEvent); void hoverEndEntity(const QUuid& id, const PointerEvent& pointerEvent); - void triggerBeginHUD(const QUuid& id, const PointerEvent& pointerEvent); - void triggerContinueHUD(const QUuid& id, const PointerEvent& pointerEvent); - void triggerEndHUD(const QUuid& id, const PointerEvent& pointerEvent); - void hoverBeginHUD(const QUuid& id, const PointerEvent& pointerEvent); - void hoverContinueHUD(const QUuid& id, const PointerEvent& pointerEvent); - void hoverEndHUD(const QUuid& id, const PointerEvent& pointerEvent); + void triggerBeginHUD(const PointerEvent& pointerEvent); + void triggerContinueHUD(const PointerEvent& pointerEvent); + void triggerEndHUD(const PointerEvent& pointerEvent); + void hoverBeginHUD(const PointerEvent& pointerEvent); + void hoverContinueHUD(const PointerEvent& pointerEvent); + void hoverEndHUD(const PointerEvent& pointerEvent); }; #endif // hifi_pointers_PointerManager_h diff --git a/libraries/shared/src/PointerEvent.cpp b/libraries/shared/src/PointerEvent.cpp index 183b7c332a..f0f868655c 100644 --- a/libraries/shared/src/PointerEvent.cpp +++ b/libraries/shared/src/PointerEvent.cpp @@ -20,8 +20,28 @@ static bool areFlagsSet(uint32_t flags, uint32_t mask) { return (flags & mask) != 0; } -PointerEvent::PointerEvent() { - ; +PointerEvent::PointerEvent(EventType type, uint32_t id) : + _type(type), + _id(id) +{ +} + +PointerEvent::PointerEvent(EventType type, uint32_t id, const glm::vec2& pos2D, Button button, uint32_t buttons, Qt::KeyboardModifiers keyboardModifiers) : + _type(type), + _id(id), + _pos2D(pos2D), + _button(button), + _buttons(buttons), + _keyboardModifiers(keyboardModifiers) +{ +} + +PointerEvent::PointerEvent(const glm::vec2& pos2D, const glm::vec3& pos3D, const glm::vec3& normal, const glm::vec3& direction) : + _pos2D(pos2D), + _pos3D(pos3D), + _normal(normal), + _direction(direction) +{ } PointerEvent::PointerEvent(EventType type, uint32_t id, @@ -38,7 +58,6 @@ PointerEvent::PointerEvent(EventType type, uint32_t id, _buttons(buttons), _keyboardModifiers(keyboardModifiers) { - ; } void PointerEvent::setButton(Button button) { diff --git a/libraries/shared/src/PointerEvent.h b/libraries/shared/src/PointerEvent.h index f6b1a81602..6ace262fe1 100644 --- a/libraries/shared/src/PointerEvent.h +++ b/libraries/shared/src/PointerEvent.h @@ -35,11 +35,14 @@ public: NumEventTypes }; - PointerEvent(); + PointerEvent() {} + PointerEvent(EventType type, uint32_t id); + PointerEvent(EventType type, uint32_t id, const glm::vec2& pos2D, Button button, uint32_t buttons, Qt::KeyboardModifiers keyboardModifiers); + PointerEvent(const glm::vec2& pos2D, const glm::vec3& pos3D, const glm::vec3& normal, const glm::vec3& direction); PointerEvent(EventType type, uint32_t id, - const glm::vec2& pos2D = glm::vec2(NAN), const glm::vec3& pos3D = glm::vec3(NAN), - const glm::vec3& normal = glm::vec3(NAN), const glm::vec3& direction = glm::vec3(NAN), - Button button = NoButtons, uint32_t buttons = NoButtons, Qt::KeyboardModifiers keyboardModifiers = Qt::KeyboardModifier::NoModifier); + const glm::vec2& pos2D, const glm::vec3& pos3D, + const glm::vec3& normal, const glm::vec3& direction, + Button button, uint32_t buttons, Qt::KeyboardModifiers keyboardModifiers); static QScriptValue toScriptValue(QScriptEngine* engine, const PointerEvent& event); static void fromScriptValue(const QScriptValue& object, PointerEvent& event); @@ -63,18 +66,21 @@ public: void setButton(Button button); void setShouldFocus(bool focus) { _shouldFocus = focus; } void setReleaseOnHoverLeave(bool releaseOnHoverLeave) { _releaseOnHoverLeave = releaseOnHoverLeave; } + void setPos2D(const glm::vec2& pos2D) { _pos2D = pos2D; } + + static const unsigned int INVALID_POINTER_ID { 0 }; private: EventType _type; - uint32_t _id; // used to identify the pointer. (left vs right hand, for example) - glm::vec2 _pos2D; // (in meters) projected onto the xy plane of entities dimension box, (0, 0) is upper right hand corner - glm::vec3 _pos3D; // surface location in world coordinates (in meters) - glm::vec3 _normal; // surface normal - glm::vec3 _direction; // incoming direction of pointer ray. + uint32_t _id { INVALID_POINTER_ID }; // used to identify the pointer. (left vs right hand, for example) + glm::vec2 _pos2D { glm::vec2(NAN) }; // (in meters) projected onto the xy plane of entities dimension box, (0, 0) is upper right hand corner + glm::vec3 _pos3D { glm::vec3(NAN) }; // surface location in world coordinates (in meters) + glm::vec3 _normal { glm::vec3(NAN) }; // surface normal + glm::vec3 _direction { glm::vec3(NAN) }; // incoming direction of pointer ray. Button _button { NoButtons }; // button associated with this event, (if type is Press, this will be the button that is pressed) uint32_t _buttons { NoButtons }; // the current state of all the buttons. - Qt::KeyboardModifiers _keyboardModifiers; // set of keys held when event was generated + Qt::KeyboardModifiers _keyboardModifiers { Qt::KeyboardModifier::NoModifier }; // set of keys held when event was generated bool _shouldFocus { true }; bool _releaseOnHoverLeave { true }; diff --git a/libraries/ui/CMakeLists.txt b/libraries/ui/CMakeLists.txt index f28157ff97..334cce97e5 100644 --- a/libraries/ui/CMakeLists.txt +++ b/libraries/ui/CMakeLists.txt @@ -1,6 +1,7 @@ set(TARGET_NAME ui) setup_hifi_library(OpenGL Network Qml Quick Script WebChannel WebEngine WebSockets XmlPatterns) -link_hifi_libraries(shared networking gl audio) +link_hifi_libraries(shared networking gl audio pointers) +include_hifi_library_headers(controllers) # Required for some low level GL interaction in the OffscreenQMLSurface target_opengl() diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 297ed9ca50..c41ef9d4a4 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -28,6 +28,7 @@ #include "ui/Logging.h" +#include // Needs to match the constants in resources/qml/Global.js class OffscreenFlags : public QObject { @@ -84,7 +85,31 @@ bool OffscreenUi::shouldSwallowShortcut(QEvent* event) { return false; } +static QTouchDevice _touchDevice; OffscreenUi::OffscreenUi() { + static std::once_flag once; + std::call_once(once, [&] { + _touchDevice.setCapabilities(QTouchDevice::Position); + _touchDevice.setType(QTouchDevice::TouchScreen); + _touchDevice.setName("OffscreenUiTouchDevice"); + _touchDevice.setMaximumTouchPoints(4); + }); + + auto pointerManager = DependencyManager::get(); + connect(pointerManager.data(), &PointerManager::hoverBeginHUD, this, &OffscreenUi::handlePointerEvent); + connect(pointerManager.data(), &PointerManager::hoverContinueHUD, this, &OffscreenUi::handlePointerEvent); + connect(pointerManager.data(), &PointerManager::hoverEndHUD, this, &OffscreenUi::hoverEndEvent); + connect(pointerManager.data(), &PointerManager::triggerBeginHUD, this, &OffscreenUi::handlePointerEvent); + connect(pointerManager.data(), &PointerManager::triggerContinueHUD, this, &OffscreenUi::handlePointerEvent); + connect(pointerManager.data(), &PointerManager::triggerEndHUD, this, &OffscreenUi::handlePointerEvent); +} + +void OffscreenUi::hoverEndEvent(const PointerEvent& event) { + OffscreenQmlSurface::hoverEndEvent(event, _touchDevice); +} + +void OffscreenUi::handlePointerEvent(const PointerEvent& event) { + OffscreenQmlSurface::handlePointerEvent(event, _touchDevice); } QObject* OffscreenUi::getFlags() { @@ -1072,6 +1097,23 @@ bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { // let the parent class do it's work bool result = OffscreenQmlSurface::eventFilter(originalDestination, event); + switch (event->type()) { + // Fall through + case QEvent::MouseButtonDblClick: + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseMove: { + QMouseEvent* mouseEvent = static_cast(event); + QPointF transformedPos = mapToVirtualScreen(mouseEvent->localPos()); + PointerEvent pointerEvent(choosePointerEventType(mouseEvent->type()), PointerManager::MOUSE_POINTER_ID, glm::vec2(transformedPos.x(), transformedPos.y()), + PointerEvent::Button(mouseEvent->button()), mouseEvent->buttons(), mouseEvent->modifiers()); + result = OffscreenQmlSurface::handlePointerEvent(pointerEvent, _touchDevice); + break; + } + default: + break; + } + // Check if this is a key press/release event that might need special attention auto type = event->type(); diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index 93c55d06f7..0261591295 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -249,6 +249,10 @@ signals: public slots: void removeModalDialog(QObject* modal); +private slots: + void hoverEndEvent(const PointerEvent& event); + void handlePointerEvent(const PointerEvent& event); + private: QString fileDialog(const QVariantMap& properties); ModalDialogListener *fileDialogAsync(const QVariantMap &properties); diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 08662031da..e2675008f9 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -855,7 +855,7 @@ QPointF OffscreenQmlSurface::mapWindowToUi(const QPointF& sourcePosition, QObjec return QPointF(offscreenPosition.x, offscreenPosition.y); } -QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget) { +QPointF OffscreenQmlSurface::mapToVirtualScreen(const QPointF& originalPoint) { return _mouseTranslator(originalPoint); } @@ -909,7 +909,7 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even case QEvent::Wheel: { QWheelEvent* wheelEvent = static_cast(event); - QPointF transformedPos = mapToVirtualScreen(wheelEvent->pos(), originalDestination); + QPointF transformedPos = mapToVirtualScreen(wheelEvent->pos()); QWheelEvent mappedEvent( transformedPos, wheelEvent->delta(), wheelEvent->buttons(), @@ -920,30 +920,6 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even } break; } - - // Fall through - case QEvent::MouseButtonDblClick: - case QEvent::MouseButtonPress: - case QEvent::MouseButtonRelease: - case QEvent::MouseMove: { - QMouseEvent* mouseEvent = static_cast(event); - QPointF transformedPos = mapToVirtualScreen(mouseEvent->localPos(), originalDestination); - QMouseEvent mappedEvent(mouseEvent->type(), - transformedPos, - mouseEvent->screenPos(), mouseEvent->button(), - mouseEvent->buttons(), mouseEvent->modifiers()); - if (event->type() == QEvent::MouseMove) { - // TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install - // need to investigate into why this crash is happening. - //_qmlContext->setContextProperty("lastMousePosition", mouseEvent.localPos()); - } - mappedEvent.ignore(); - if (QCoreApplication::sendEvent(_quickWindow, &mappedEvent)) { - return mappedEvent.isAccepted(); - } - break; - } - default: break; } @@ -951,6 +927,141 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even return false; } +PointerEvent::EventType OffscreenQmlSurface::choosePointerEventType(QEvent::Type type) { + switch (type) { + case QEvent::MouseButtonDblClick: + return PointerEvent::DoublePress; + case QEvent::MouseButtonPress: + return PointerEvent::Press; + case QEvent::MouseButtonRelease: + return PointerEvent::Release; + case QEvent::MouseMove: + return PointerEvent::Move; + default: + return PointerEvent::Move; + } +} + +void OffscreenQmlSurface::hoverEndEvent(const PointerEvent& event, class QTouchDevice& device) { + if (!_paused && _quickWindow && _pressed && event.sendReleaseOnHoverLeave()) { + PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), + event.getButton(), event.getButtons(), event.getKeyboardModifiers()); + handlePointerEvent(endEvent, device); + // QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited. + PointerEvent endMoveEvent(PointerEvent::Move, event.getID()); + handlePointerEvent(endMoveEvent, device); + } +} + +bool OffscreenQmlSurface::handlePointerEvent(const PointerEvent& event, class QTouchDevice& device) { + // Ignore mouse interaction if we're paused + if (_paused || !_quickWindow) { + return false; + } + + if (event.getType() == PointerEvent::Press) { + _pressed = true; + } else if (event.getType() == PointerEvent::Release) { + _pressed = false; + } + + QPointF windowPoint(event.getPos2D().x, event.getPos2D().y); + + Qt::TouchPointState state = Qt::TouchPointStationary; + if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) { + state = Qt::TouchPointPressed; + } else if (event.getType() == PointerEvent::Release) { + state = Qt::TouchPointReleased; + } else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].pos()) { + state = Qt::TouchPointMoved; + } + + QEvent::Type touchType = QEvent::TouchUpdate; + if (_activeTouchPoints.empty()) { + // If the first active touch point is being created, send a begin + touchType = QEvent::TouchBegin; + } if (state == Qt::TouchPointReleased && _activeTouchPoints.size() == 1 && _activeTouchPoints.count(event.getID())) { + // If the last active touch point is being released, send an end + touchType = QEvent::TouchEnd; + } + + { + QTouchEvent::TouchPoint point; + point.setId(event.getID()); + point.setState(state); + point.setPos(windowPoint); + point.setScreenPos(windowPoint); + _activeTouchPoints[event.getID()] = point; + } + + QTouchEvent touchEvent(touchType, &device, event.getKeyboardModifiers()); + { + QList touchPoints; + Qt::TouchPointStates touchPointStates; + for (const auto& entry : _activeTouchPoints) { + touchPointStates |= entry.second.state(); + touchPoints.push_back(entry.second); + } + + touchEvent.setWindow(_quickWindow); + touchEvent.setDevice(&device); + touchEvent.setTarget(_rootItem); + touchEvent.setTouchPoints(touchPoints); + touchEvent.setTouchPointStates(touchPointStates); + } + + // Send mouse events to the surface so that HTML dialog elements work with mouse press and hover. + // + // In Qt 5.9 mouse events must be sent before touch events to make sure some QtQuick components will + // receive mouse events + Qt::MouseButton button = Qt::NoButton; + Qt::MouseButtons buttons = Qt::NoButton; + if (event.getButton() == PointerEvent::PrimaryButton) { + button = Qt::LeftButton; + } + if (event.getButtons() & PointerEvent::PrimaryButton) { + buttons |= Qt::LeftButton; + } + + bool eventsAccepted = false; + +#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) + if (event.getType() == PointerEvent::Move) { + QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); + // TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install + // need to investigate into why this crash is happening. + //_qmlContext->setContextProperty("lastMousePosition", windowPoint); + QCoreApplication::sendEvent(_quickWindow, &mouseEvent); + eventsAccepted &= mouseEvent.isAccepted(); + } +#endif + + if (touchType == QEvent::TouchBegin) { + _touchBeginAccepted = QCoreApplication::sendEvent(_quickWindow, &touchEvent); + } else if (_touchBeginAccepted) { + QCoreApplication::sendEvent(_quickWindow, &touchEvent); + } + eventsAccepted &= touchEvent.isAccepted(); + + // If this was a release event, remove the point from the active touch points + if (state == Qt::TouchPointReleased) { + _activeTouchPoints.erase(event.getID()); + } + +#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) + if (event.getType() == PointerEvent::Move) { + // TODO - this line necessary for the QML Tooltop to work (which is not currently being used), but it causes interface to crash on launch on a fresh install + // need to investigate into why this crash is happening. + //_qmlContext->setContextProperty("lastMousePosition", windowPoint); + QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); + QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); + eventsAccepted &= mouseEvent.isAccepted(); + } +#endif + + return eventsAccepted; +} + void OffscreenQmlSurface::pause() { _paused = true; } diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 12ee9e59a1..1f1817faa2 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -21,6 +21,9 @@ #include #include +#include +#include "PointerEvent.h" + class QWindow; class QMyQuickRenderControl; class OffscreenGLCanvas; @@ -79,7 +82,7 @@ public: QObject* getEventHandler(); QQmlContext* getSurfaceContext(); - QPointF mapToVirtualScreen(const QPointF& originalPoint, QObject* originalWidget); + QPointF mapToVirtualScreen(const QPointF& originalPoint); bool eventFilter(QObject* originalDestination, QEvent* event) override; void setKeyboardRaised(QObject* object, bool raised, bool numeric = false, bool passwordField = false); @@ -95,6 +98,8 @@ public: static std::function getDiscardLambda(); static size_t getUsedTextureMemory(); + PointerEvent::EventType choosePointerEventType(QEvent::Type type); + signals: void focusObjectChanged(QObject* newFocus); void focusTextChanged(bool focusText); @@ -136,6 +141,10 @@ private slots: void updateQuick(); void onFocusObjectChanged(QObject* newFocus); +public slots: + void hoverEndEvent(const PointerEvent& event, class QTouchDevice& device); + bool handlePointerEvent(const PointerEvent& event, class QTouchDevice& device); + private: QQuickWindow* _quickWindow { nullptr }; QMyQuickRenderControl* _renderControl{ nullptr }; @@ -161,6 +170,10 @@ private: QWindow* _proxyWindow { nullptr }; QQuickItem* _currentFocusItem { nullptr }; + + bool _pressed { false }; + bool _touchBeginAccepted { false }; + std::map _activeTouchPoints; }; #endif From 6ca9cc02399f30d1034f7d945b47992573a2e99c Mon Sep 17 00:00:00 2001 From: Ken Cooke Date: Fri, 3 Nov 2017 18:40:55 -0700 Subject: [PATCH 44/46] Update WASAPI audio plugin --- cmake/externals/wasapi/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmake/externals/wasapi/CMakeLists.txt b/cmake/externals/wasapi/CMakeLists.txt index 4437024962..4c0ffaf88f 100644 --- a/cmake/externals/wasapi/CMakeLists.txt +++ b/cmake/externals/wasapi/CMakeLists.txt @@ -6,8 +6,8 @@ if (WIN32) include(ExternalProject) ExternalProject_Add( ${EXTERNAL_NAME} - URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi9.zip - URL_MD5 94f4765bdbcd53cd099f349ae031e769 + URL http://hifi-public.s3.amazonaws.com/dependencies/qtaudio_wasapi10.zip + URL_MD5 4f40e49715a420fb67b45b9cee19052c CONFIGURE_COMMAND "" BUILD_COMMAND "" INSTALL_COMMAND "" From d4b4d188ed28bdf6875dd66ef34598aa0ad2005b Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Sat, 4 Nov 2017 13:40:41 -0700 Subject: [PATCH 45/46] allow splitting of edit or add entity packets across multiple edit packets when property list is larger than MTU --- .../entities/src/EntityEditPacketSender.cpp | 42 ++++++++++++------- libraries/entities/src/EntityItem.cpp | 4 +- .../entities/src/EntityItemProperties.cpp | 34 ++++++--------- libraries/entities/src/EntityItemProperties.h | 4 +- libraries/entities/src/EntityPropertyFlags.h | 29 +++++++------ libraries/octree/src/OctreePacketData.cpp | 12 +++++- libraries/octree/src/OctreePacketData.h | 1 + tests/octree/src/OctreeTests.cpp | 2 +- 8 files changed, 71 insertions(+), 57 deletions(-) diff --git a/libraries/entities/src/EntityEditPacketSender.cpp b/libraries/entities/src/EntityEditPacketSender.cpp index e82ed82093..168b0cd446 100644 --- a/libraries/entities/src/EntityEditPacketSender.cpp +++ b/libraries/entities/src/EntityEditPacketSender.cpp @@ -93,27 +93,41 @@ void EntityEditPacketSender::queueEditEntityMessage(PacketType type, QByteArray bufferOut(NLPacket::maxPayloadSize(type), 0); - bool success; + OctreeElement::AppendState encodeResult = OctreeElement::PARTIAL; // start the loop assuming there's more to send auto nodeList = DependencyManager::get(); + + EntityPropertyFlags didntFitProperties; + EntityItemProperties propertiesCopy = properties; + if (properties.parentIDChanged() && properties.getParentID() == AVATAR_SELF_ID) { - EntityItemProperties propertiesCopy = properties; const QUuid myNodeID = nodeList->getSessionUUID(); propertiesCopy.setParentID(myNodeID); - success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut); - } else { - success = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, properties, bufferOut); } - if (success) { - #ifdef WANT_DEBUG - qCDebug(entities) << "calling queueOctreeEditMessage()..."; - qCDebug(entities) << " id:" << entityItemID; - qCDebug(entities) << " properties:" << properties; - #endif - queueOctreeEditMessage(type, bufferOut); - if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) { - emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get()->getPlaceName()); + EntityPropertyFlags requestedProperties = propertiesCopy.getChangedProperties(); + + while (encodeResult == OctreeElement::PARTIAL) { + encodeResult = EntityItemProperties::encodeEntityEditPacket(type, entityItemID, propertiesCopy, bufferOut, requestedProperties, didntFitProperties); + + if (encodeResult != OctreeElement::NONE) { + #ifdef WANT_DEBUG + qCDebug(entities) << "calling queueOctreeEditMessage()..."; + qCDebug(entities) << " id:" << entityItemID; + qCDebug(entities) << " properties:" << properties; + #endif + queueOctreeEditMessage(type, bufferOut); + if (type == PacketType::EntityAdd && !properties.getCertificateID().isEmpty()) { + emit addingEntityWithCertificate(properties.getCertificateID(), DependencyManager::get()->getPlaceName()); + } } + + // if we still have properties to send, switch the message type to edit, and request only the packets that didn't fit + if (encodeResult != OctreeElement::COMPLETED) { + type = PacketType::EntityEdit; + requestedProperties = didntFitProperties; + } + + bufferOut.resize(NLPacket::maxPayloadSize(type)); // resize our output buffer for the next packet } } diff --git a/libraries/entities/src/EntityItem.cpp b/libraries/entities/src/EntityItem.cpp index f6f4e48a73..3f054e1ccb 100644 --- a/libraries/entities/src/EntityItem.cpp +++ b/libraries/entities/src/EntityItem.cpp @@ -83,7 +83,7 @@ EntityPropertyFlags EntityItem::getEntityProperties(EncodeBitstreamParams& param requestedProperties += PROP_ANGULAR_VELOCITY; requestedProperties += PROP_ACCELERATION; - requestedProperties += PROP_DIMENSIONS; // NOTE: PROP_RADIUS obsolete + requestedProperties += PROP_DIMENSIONS; requestedProperties += PROP_DENSITY; requestedProperties += PROP_GRAVITY; requestedProperties += PROP_DAMPING; @@ -241,7 +241,7 @@ OctreeElement::AppendState EntityItem::appendEntityData(OctreePacketData* packet APPEND_ENTITY_PROPERTY(PROP_ANGULAR_VELOCITY, getLocalAngularVelocity()); APPEND_ENTITY_PROPERTY(PROP_ACCELERATION, getAcceleration()); - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); // NOTE: PROP_RADIUS obsolete + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, getDimensions()); APPEND_ENTITY_PROPERTY(PROP_DENSITY, getDensity()); APPEND_ENTITY_PROPERTY(PROP_GRAVITY, getGravity()); APPEND_ENTITY_PROPERTY(PROP_DAMPING, getDamping()); diff --git a/libraries/entities/src/EntityItemProperties.cpp b/libraries/entities/src/EntityItemProperties.cpp index f774b208c4..108fc14e30 100644 --- a/libraries/entities/src/EntityItemProperties.cpp +++ b/libraries/entities/src/EntityItemProperties.cpp @@ -1221,8 +1221,9 @@ void EntityItemProperties::entityPropertyFlagsFromScriptValue(const QScriptValue // // TODO: Implement support for script and visible properties. // -bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, - QByteArray& buffer) { +OctreeElement::AppendState EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, + QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties) { + OctreePacketData ourDataPacket(false, buffer.size()); // create a packetData object to add out packet details too. OctreePacketData* packetData = &ourDataPacket; // we want a pointer to this so we can use our APPEND_ENTITY_PROPERTY macro @@ -1264,17 +1265,8 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem QByteArray encodedUpdateDelta = updateDeltaCoder; EntityPropertyFlags propertyFlags(PROP_LAST_ITEM); - EntityPropertyFlags requestedProperties = properties.getChangedProperties(); EntityPropertyFlags propertiesDidntFit = requestedProperties; - // TODO: we need to handle the multi-pass form of this, similar to how we handle entity data - // - // If we are being called for a subsequent pass at appendEntityData() that failed to completely encode this item, - // then our modelTreeElementExtraEncodeData should include data about which properties we need to append. - //if (modelTreeElementExtraEncodeData && modelTreeElementExtraEncodeData->includedItems.contains(getEntityItemID())) { - // requestedProperties = modelTreeElementExtraEncodeData->includedItems.value(getEntityItemID()); - //} - LevelDetails entityLevel = packetData->startLevel(); // Last Edited quint64 always first, before any other details, which allows us easy access to adjusting this @@ -1302,7 +1294,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem int propertyCount = 0; bool headerFits = successIDFits && successTypeFits && successLastEditedFits - && successLastUpdatedFits && successPropertyFlagsFits; + && successLastUpdatedFits && successPropertyFlagsFits; int startOfEntityItemData = packetData->getUncompressedByteOffset(); @@ -1316,7 +1308,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem APPEND_ENTITY_PROPERTY(PROP_SIMULATION_OWNER, properties._simulationOwner.toByteArray()); APPEND_ENTITY_PROPERTY(PROP_POSITION, properties.getPosition()); - APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); // NOTE: PROP_RADIUS obsolete + APPEND_ENTITY_PROPERTY(PROP_DIMENSIONS, properties.getDimensions()); APPEND_ENTITY_PROPERTY(PROP_ROTATION, properties.getRotation()); APPEND_ENTITY_PROPERTY(PROP_DENSITY, properties.getDensity()); APPEND_ENTITY_PROPERTY(PROP_VELOCITY, properties.getVelocity()); @@ -1472,6 +1464,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem properties.getType() == EntityTypes::Sphere) { APPEND_ENTITY_PROPERTY(PROP_SHAPE, properties.getShape()); } + APPEND_ENTITY_PROPERTY(PROP_NAME, properties.getName()); APPEND_ENTITY_PROPERTY(PROP_COLLISION_SOUND_URL, properties.getCollisionSoundURL()); APPEND_ENTITY_PROPERTY(PROP_ACTION_DATA, properties.getActionData()); @@ -1522,12 +1515,7 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem // If any part of the model items didn't fit, then the element is considered partial if (appendState != OctreeElement::COMPLETED) { - // TODO: handle mechanism for handling partial fitting data! - // add this item into our list for the next appendElementData() pass - //modelTreeElementExtraEncodeData->includedItems.insert(getEntityItemID(), propertiesDidntFit); - - // for now, if it's not complete, it's not successful - success = false; + didntFitProperties = propertiesDidntFit; } } @@ -1543,11 +1531,15 @@ bool EntityItemProperties::encodeEntityEditPacket(PacketType command, EntityItem } else { qCDebug(entities) << "ERROR - encoded edit message doesn't fit in output buffer."; success = false; + appendState = OctreeElement::NONE; // if we got here, then we didn't include the item + // maybe we should assert!!! } } else { packetData->discardSubTree(); } - return success; + + + return appendState; } QByteArray EntityItemProperties::getPackedNormals() const { @@ -1673,7 +1665,7 @@ bool EntityItemProperties::decodeEntityEditPacket(const unsigned char* data, int READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_SIMULATION_OWNER, QByteArray, setSimulationOwner); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_POSITION, glm::vec3, setPosition); - READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); // NOTE: PROP_RADIUS obsolete + READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DIMENSIONS, glm::vec3, setDimensions); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_ROTATION, glm::quat, setRotation); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_DENSITY, float, setDensity); READ_ENTITY_PROPERTY_TO_PROPERTIES(PROP_VELOCITY, glm::vec3, setVelocity); diff --git a/libraries/entities/src/EntityItemProperties.h b/libraries/entities/src/EntityItemProperties.h index a8bb063934..732dbdf69f 100644 --- a/libraries/entities/src/EntityItemProperties.h +++ b/libraries/entities/src/EntityItemProperties.h @@ -262,8 +262,8 @@ public: float getLocalRenderAlpha() const { return _localRenderAlpha; } void setLocalRenderAlpha(float value) { _localRenderAlpha = value; _localRenderAlphaChanged = true; } - static bool encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, - QByteArray& buffer); + static OctreeElement::AppendState encodeEntityEditPacket(PacketType command, EntityItemID id, const EntityItemProperties& properties, + QByteArray& buffer, EntityPropertyFlags requestedProperties, EntityPropertyFlags& didntFitProperties); static bool encodeEraseEntityMessage(const EntityItemID& entityItemID, QByteArray& buffer); diff --git a/libraries/entities/src/EntityPropertyFlags.h b/libraries/entities/src/EntityPropertyFlags.h index f0f22b0091..35d40b669a 100644 --- a/libraries/entities/src/EntityPropertyFlags.h +++ b/libraries/entities/src/EntityPropertyFlags.h @@ -21,8 +21,7 @@ enum EntityPropertyList { // these properties are supported by the EntityItem base class PROP_VISIBLE, PROP_POSITION, - PROP_RADIUS, // NOTE: PROP_RADIUS is obsolete and only included in old format streams - PROP_DIMENSIONS = PROP_RADIUS, + PROP_DIMENSIONS, PROP_ROTATION, PROP_DENSITY, PROP_VELOCITY, @@ -47,13 +46,13 @@ enum EntityPropertyList { PROP_ANGULAR_VELOCITY, PROP_ANGULAR_DAMPING, PROP_COLLISIONLESS, - PROP_DYNAMIC, + PROP_DYNAMIC, // 24 // property used by Light entity PROP_IS_SPOTLIGHT, PROP_DIFFUSE_COLOR, - PROP_AMBIENT_COLOR_UNUSED, - PROP_SPECULAR_COLOR_UNUSED, + PROP_AMBIENT_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol + PROP_SPECULAR_COLOR_UNUSED, // FIXME - No longer used, can remove and bump protocol PROP_INTENSITY, // Previously PROP_CONSTANT_ATTENUATION PROP_LINEAR_ATTENUATION_UNUSED, PROP_QUADRATIC_ATTENUATION_UNUSED, @@ -61,30 +60,30 @@ enum EntityPropertyList { PROP_CUTOFF, // available to all entities - PROP_LOCKED, + PROP_LOCKED, // 34 PROP_TEXTURES, // used by Model entities - PROP_ANIMATION_SETTINGS, // used by Model entities - PROP_USER_DATA, // all entities + PROP_ANIMATION_SETTINGS_UNUSED, // FIXME - No longer used, can remove and bump protocol + PROP_USER_DATA, // all entities -- 37 PROP_SHAPE_TYPE, // used by Model + zones entities // used by ParticleEffect entities - PROP_MAX_PARTICLES, - PROP_LIFESPAN, + PROP_MAX_PARTICLES, // 39 + PROP_LIFESPAN, // 40 -- used by all entities PROP_EMIT_RATE, PROP_EMIT_SPEED, PROP_EMIT_STRENGTH, - PROP_EMIT_ACCELERATION, - PROP_PARTICLE_RADIUS, + PROP_EMIT_ACCELERATION, // FIXME - doesn't seem to get set in mark all changed???? + PROP_PARTICLE_RADIUS, // 45!! PROP_COMPOUND_SHAPE_URL, // used by Model + zones entities PROP_MARKETPLACE_ID, // all entities PROP_ACCELERATION, // all entities PROP_SIMULATION_OWNER, // formerly known as PROP_SIMULATOR_ID - PROP_NAME, // all entities + PROP_NAME, // all entities -- 50 PROP_COLLISION_SOUND_URL, PROP_RESTITUTION, - PROP_FRICTION, + PROP_FRICTION, // 53 PROP_VOXEL_VOLUME_SIZE, PROP_VOXEL_DATA, @@ -96,7 +95,7 @@ enum EntityPropertyList { // used by hyperlinks PROP_HREF, - PROP_DESCRIPTION, + PROP_DESCRIPTION, // 61 PROP_FACE_CAMERA, PROP_SCRIPT_TIMESTAMP, diff --git a/libraries/octree/src/OctreePacketData.cpp b/libraries/octree/src/OctreePacketData.cpp index 493dfdcff5..b5b4a161ef 100644 --- a/libraries/octree/src/OctreePacketData.cpp +++ b/libraries/octree/src/OctreePacketData.cpp @@ -68,8 +68,8 @@ bool OctreePacketData::append(const unsigned char* data, int length) { _dirty = true; } - const bool wantDebug = false; - if (wantDebug && !success) { + #ifdef WANT_DEBUG + if (!success) { qCDebug(octree) << "OctreePacketData::append(const unsigned char* data, int length) FAILING...."; qCDebug(octree) << " length=" << length; qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable; @@ -77,6 +77,7 @@ bool OctreePacketData::append(const unsigned char* data, int length) { qCDebug(octree) << " _targetSize=" << _targetSize; qCDebug(octree) << " _bytesReserved=" << _bytesReserved; } + #endif return success; } @@ -647,6 +648,13 @@ void OctreePacketData::debugContent() { printf("\n"); } +void OctreePacketData::debugBytes() { + qCDebug(octree) << " _bytesAvailable=" << _bytesAvailable; + qCDebug(octree) << " _bytesInUse=" << _bytesInUse; + qCDebug(octree) << " _targetSize=" << _targetSize; + qCDebug(octree) << " _bytesReserved=" << _bytesReserved; +} + int OctreePacketData::unpackDataFromBytes(const unsigned char* dataBytes, QString& result) { uint16_t length; memcpy(&length, dataBytes, sizeof(length)); diff --git a/libraries/octree/src/OctreePacketData.h b/libraries/octree/src/OctreePacketData.h index ed6a49941b..37c171504b 100644 --- a/libraries/octree/src/OctreePacketData.h +++ b/libraries/octree/src/OctreePacketData.h @@ -240,6 +240,7 @@ public: /// displays contents for debugging void debugContent(); + void debugBytes(); static quint64 getCompressContentTime() { return _compressContentTime; } /// total time spent compressing content static quint64 getCompressContentCalls() { return _compressContentCalls; } /// total calls to compress content diff --git a/tests/octree/src/OctreeTests.cpp b/tests/octree/src/OctreeTests.cpp index 64d68e8e13..81300a1293 100644 --- a/tests/octree/src/OctreeTests.cpp +++ b/tests/octree/src/OctreeTests.cpp @@ -74,7 +74,7 @@ void OctreeTests::propertyFlagsTests() { EntityPropertyFlags props; props.setHasProperty(PROP_VISIBLE); props.setHasProperty(PROP_POSITION); - props.setHasProperty(PROP_RADIUS); + props.setHasProperty(PROP_DIMENSIONS); props.setHasProperty(PROP_MODEL_URL); props.setHasProperty(PROP_COMPOUND_SHAPE_URL); props.setHasProperty(PROP_ROTATION); From 7d420f5242bb421485a6c67d4f89fe536aae9440 Mon Sep 17 00:00:00 2001 From: SamGondelman Date: Mon, 6 Nov 2017 12:29:21 -0800 Subject: [PATCH 46/46] isLeftHand, isRightHand, isMouse --- interface/resources/qml/controls-uit/Key.qml | 9 +- interface/src/raypick/JointRayPick.h | 3 + .../raypick/LaserPointerScriptingInterface.h | 6 +- interface/src/raypick/MouseRayPick.h | 2 + .../src/raypick/PickScriptingInterface.cpp | 12 ++ .../src/raypick/PickScriptingInterface.h | 6 +- .../src/raypick/PointerScriptingInterface.h | 5 +- .../src/raypick/RayPickScriptingInterface.cpp | 12 ++ .../src/raypick/RayPickScriptingInterface.h | 7 +- .../ui/overlays/ContextOverlayInterface.cpp | 14 ++- interface/src/ui/overlays/Overlays.cpp | 4 +- interface/src/ui/overlays/Web3DOverlay.cpp | 113 +++--------------- interface/src/ui/overlays/Web3DOverlay.h | 4 +- .../src/EntityTreeRenderer.cpp | 16 ++- libraries/pointers/src/pointers/Pick.h | 4 + .../pointers/src/pointers/PickManager.cpp | 24 ++++ libraries/pointers/src/pointers/PickManager.h | 4 + libraries/pointers/src/pointers/Pointer.cpp | 12 ++ libraries/pointers/src/pointers/Pointer.h | 4 + .../pointers/src/pointers/PointerManager.cpp | 24 ++++ .../pointers/src/pointers/PointerManager.h | 4 + libraries/ui/src/ui/OffscreenQmlSurface.cpp | 12 ++ libraries/ui/src/ui/OffscreenQmlSurface.h | 2 + 23 files changed, 176 insertions(+), 127 deletions(-) diff --git a/interface/resources/qml/controls-uit/Key.qml b/interface/resources/qml/controls-uit/Key.qml index e54250c872..3bba1e5f07 100644 --- a/interface/resources/qml/controls-uit/Key.qml +++ b/interface/resources/qml/controls-uit/Key.qml @@ -64,11 +64,12 @@ Item { keyItem.state = "mouseOver"; var globalPosition = keyItem.mapToGlobal(mouseArea1.mouseX, mouseArea1.mouseY); - var deviceId = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y); - var hand = deviceId - 1; // based on touchEventUtils.js, deviceId is 'hand + 1', so 'hand' is 'deviceId' - 1 + var pointerID = Web3DOverlay.deviceIdByTouchPoint(globalPosition.x, globalPosition.y); - if (hand == leftHand || hand == rightHand) { - Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, hand); + if (Pointers.isLeftHand(pointerID)) { + Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, leftHand); + } else if (Pointers.isRightHand(pointerID)) { + Controller.triggerHapticPulse(_HAPTIC_STRENGTH, _HAPTIC_DURATION, rightHand); } } diff --git a/interface/src/raypick/JointRayPick.h b/interface/src/raypick/JointRayPick.h index ab44bf67c8..67e6bf6945 100644 --- a/interface/src/raypick/JointRayPick.h +++ b/interface/src/raypick/JointRayPick.h @@ -20,6 +20,9 @@ public: const PickRay getMathematicalPick() const override; + bool isLeftHand() const override { return (_jointName == "_CONTROLLER_LEFTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_LEFTHAND"); } + bool isRightHand() const override { return (_jointName == "_CONTROLLER_RIGHTHAND") || (_jointName == "_CAMERA_RELATIVE_CONTROLLER_RIGHTHAND"); } + private: std::string _jointName; glm::vec3 _posOffset; diff --git a/interface/src/raypick/LaserPointerScriptingInterface.h b/interface/src/raypick/LaserPointerScriptingInterface.h index da62f4eb12..baf8f0b2e6 100644 --- a/interface/src/raypick/LaserPointerScriptingInterface.h +++ b/interface/src/raypick/LaserPointerScriptingInterface.h @@ -20,7 +20,7 @@ class LaserPointerScriptingInterface : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY -public slots: +public: Q_INVOKABLE unsigned int createLaserPointer(const QVariant& properties) const; Q_INVOKABLE void enableLaserPointer(unsigned int uid) const { DependencyManager::get()->enablePointer(uid); } Q_INVOKABLE void disableLaserPointer(unsigned int uid) const { DependencyManager::get()->disablePointer(uid); } @@ -36,6 +36,10 @@ public slots: Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay); } + Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get()->isLeftHand(uid); } + Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get()->isRightHand(uid); } + Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get()->isMouse(uid); } + }; #endif // hifi_LaserPointerScriptingInterface_h diff --git a/interface/src/raypick/MouseRayPick.h b/interface/src/raypick/MouseRayPick.h index e9eb3ccabf..6ce9e7d8d2 100644 --- a/interface/src/raypick/MouseRayPick.h +++ b/interface/src/raypick/MouseRayPick.h @@ -19,6 +19,8 @@ public: MouseRayPick(const PickFilter& filter, const float maxDistance = 0.0f, const bool enabled = false); const PickRay getMathematicalPick() const override; + + bool isMouse() const override { return true; } }; #endif // hifi_MouseRayPick_h diff --git a/interface/src/raypick/PickScriptingInterface.cpp b/interface/src/raypick/PickScriptingInterface.cpp index 445a7e9b69..05e4a2acb0 100644 --- a/interface/src/raypick/PickScriptingInterface.cpp +++ b/interface/src/raypick/PickScriptingInterface.cpp @@ -109,6 +109,18 @@ void PickScriptingInterface::setIncludeItems(unsigned int uid, const QScriptValu DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } +bool PickScriptingInterface::isLeftHand(unsigned int uid) { + return DependencyManager::get()->isLeftHand(uid); +} + +bool PickScriptingInterface::isRightHand(unsigned int uid) { + return DependencyManager::get()->isRightHand(uid); +} + +bool PickScriptingInterface::isMouse(unsigned int uid) { + return DependencyManager::get()->isMouse(uid); +} + QScriptValue pickTypesToScriptValue(QScriptEngine* engine, const PickQuery::PickType& pickType) { return pickType; } diff --git a/interface/src/raypick/PickScriptingInterface.h b/interface/src/raypick/PickScriptingInterface.h index 900fa2ef40..cd296993fb 100644 --- a/interface/src/raypick/PickScriptingInterface.h +++ b/interface/src/raypick/PickScriptingInterface.h @@ -37,7 +37,6 @@ public: void registerMetaTypes(QScriptEngine* engine); -public slots: Q_INVOKABLE unsigned int createPick(const PickQuery::PickType type, const QVariant& properties); Q_INVOKABLE void enablePick(unsigned int uid); Q_INVOKABLE void disablePick(unsigned int uid); @@ -48,6 +47,11 @@ public slots: Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities); Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities); + Q_INVOKABLE bool isLeftHand(unsigned int uid); + Q_INVOKABLE bool isRightHand(unsigned int uid); + Q_INVOKABLE bool isMouse(unsigned int uid); + +public slots: static constexpr unsigned int PICK_NOTHING() { return 0; } static constexpr unsigned int PICK_ENTITIES() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_ENTITIES); } static constexpr unsigned int PICK_OVERLAYS() { return PickFilter::getBitMask(PickFilter::FlagBit::PICK_OVERLAYS); } diff --git a/interface/src/raypick/PointerScriptingInterface.h b/interface/src/raypick/PointerScriptingInterface.h index 8ea9594fda..e34a9ef74d 100644 --- a/interface/src/raypick/PointerScriptingInterface.h +++ b/interface/src/raypick/PointerScriptingInterface.h @@ -21,7 +21,6 @@ class PointerScriptingInterface : public QObject, public Dependency { public: unsigned int createLaserPointer(const QVariant& properties) const; -public slots: Q_INVOKABLE unsigned int createPointer(const PickQuery::PickType& type, const QVariant& properties) const; Q_INVOKABLE void enablePointer(unsigned int uid) const { DependencyManager::get()->enablePointer(uid); } Q_INVOKABLE void disablePointer(unsigned int uid) const { DependencyManager::get()->disablePointer(uid); } @@ -37,6 +36,10 @@ public slots: Q_INVOKABLE void setLockEndUUID(unsigned int uid, const QUuid& objectID, bool isOverlay) const { DependencyManager::get()->setLockEndUUID(uid, objectID, isOverlay); } + Q_INVOKABLE bool isLeftHand(unsigned int uid) { return DependencyManager::get()->isLeftHand(uid); } + Q_INVOKABLE bool isRightHand(unsigned int uid) { return DependencyManager::get()->isRightHand(uid); } + Q_INVOKABLE bool isMouse(unsigned int uid) { return DependencyManager::get()->isMouse(uid); } + signals: void triggerBegin(const QUuid& id, const PointerEvent& pointerEvent); void triggerContinue(const QUuid& id, const PointerEvent& pointerEvent); diff --git a/interface/src/raypick/RayPickScriptingInterface.cpp b/interface/src/raypick/RayPickScriptingInterface.cpp index f9774dda31..6b0b8dc167 100644 --- a/interface/src/raypick/RayPickScriptingInterface.cpp +++ b/interface/src/raypick/RayPickScriptingInterface.cpp @@ -51,3 +51,15 @@ void RayPickScriptingInterface::setIgnoreItems(uint32_t uid, const QScriptValue& void RayPickScriptingInterface::setIncludeItems(uint32_t uid, const QScriptValue& includeItems) { DependencyManager::get()->setIncludeItems(uid, qVectorQUuidFromScriptValue(includeItems)); } + +bool RayPickScriptingInterface::isLeftHand(unsigned int uid) { + return DependencyManager::get()->isLeftHand(uid); +} + +bool RayPickScriptingInterface::isRightHand(unsigned int uid) { + return DependencyManager::get()->isRightHand(uid); +} + +bool RayPickScriptingInterface::isMouse(unsigned int uid) { + return DependencyManager::get()->isMouse(uid); +} \ No newline at end of file diff --git a/interface/src/raypick/RayPickScriptingInterface.h b/interface/src/raypick/RayPickScriptingInterface.h index 844add9012..65b0b2aced 100644 --- a/interface/src/raypick/RayPickScriptingInterface.h +++ b/interface/src/raypick/RayPickScriptingInterface.h @@ -36,7 +36,7 @@ class RayPickScriptingInterface : public QObject, public Dependency { Q_PROPERTY(unsigned int INTERSECTED_HUD READ INTERSECTED_HUD CONSTANT) SINGLETON_DEPENDENCY -public slots: +public: Q_INVOKABLE unsigned int createRayPick(const QVariant& properties); Q_INVOKABLE void enableRayPick(unsigned int uid); Q_INVOKABLE void disableRayPick(unsigned int uid); @@ -47,6 +47,11 @@ public slots: Q_INVOKABLE void setIgnoreItems(unsigned int uid, const QScriptValue& ignoreEntities); Q_INVOKABLE void setIncludeItems(unsigned int uid, const QScriptValue& includeEntities); + Q_INVOKABLE bool isLeftHand(unsigned int uid); + Q_INVOKABLE bool isRightHand(unsigned int uid); + Q_INVOKABLE bool isMouse(unsigned int uid); + +public slots: static unsigned int PICK_NOTHING() { return PickScriptingInterface::PICK_NOTHING(); } static unsigned int PICK_ENTITIES() { return PickScriptingInterface::PICK_ENTITIES(); } static unsigned int PICK_OVERLAYS() { return PickScriptingInterface::PICK_OVERLAYS(); } diff --git a/interface/src/ui/overlays/ContextOverlayInterface.cpp b/interface/src/ui/overlays/ContextOverlayInterface.cpp index b5af529f2b..fa02d4107c 100644 --- a/interface/src/ui/overlays/ContextOverlayInterface.cpp +++ b/interface/src/ui/overlays/ContextOverlayInterface.cpp @@ -19,6 +19,8 @@ #include #include +#include + #ifndef MIN #define MIN(a,b) ((a) < (b) ? (a) : (b)) #endif @@ -79,8 +81,6 @@ ContextOverlayInterface::ContextOverlayInterface() { _challengeOwnershipTimeoutTimer.setSingleShot(true); } -static const uint32_t MOUSE_HW_ID = 0; -static const uint32_t LEFT_HAND_HW_ID = 1; static const xColor CONTEXT_OVERLAY_COLOR = { 255, 255, 255 }; static const float CONTEXT_OVERLAY_INSIDE_DISTANCE = 1.0f; // in meters static const float CONTEXT_OVERLAY_SIZE = 0.09f; // in meters, same x and y dims @@ -100,7 +100,7 @@ void ContextOverlayInterface::setEnabled(bool enabled) { bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& entityItemID, const PointerEvent& event) { if (_enabled && event.getButton() == PointerEvent::SecondaryButton) { if (contextOverlayFilterPassed(entityItemID)) { - if (event.getID() == MOUSE_HW_ID) { + if (event.getID() == PointerManager::MOUSE_POINTER_ID || DependencyManager::get()->isMouse(event.getID())) { enableEntityHighlight(entityItemID); } @@ -151,7 +151,7 @@ bool ContextOverlayInterface::createOrDestroyContextOverlay(const EntityItemID& glm::vec3 normal; boundingBox.findRayIntersection(cameraPosition, direction, distance, face, normal); float offsetAngle = -CONTEXT_OVERLAY_OFFSET_ANGLE; - if (event.getID() == LEFT_HAND_HW_ID) { + if (DependencyManager::get()->isLeftHand(event.getID())) { offsetAngle *= -1.0f; } contextOverlayPosition = cameraPosition + @@ -253,13 +253,15 @@ void ContextOverlayInterface::contextOverlays_hoverLeaveOverlay(const OverlayID& } void ContextOverlayInterface::contextOverlays_hoverEnterEntity(const EntityItemID& entityID, const PointerEvent& event) { - if (contextOverlayFilterPassed(entityID) && _enabled && event.getID() != MOUSE_HW_ID) { + bool isMouse = event.getID() == PointerManager::MOUSE_POINTER_ID || DependencyManager::get()->isMouse(event.getID()); + if (contextOverlayFilterPassed(entityID) && _enabled && !isMouse) { enableEntityHighlight(entityID); } } void ContextOverlayInterface::contextOverlays_hoverLeaveEntity(const EntityItemID& entityID, const PointerEvent& event) { - if (_currentEntityWithContextOverlay != entityID && _enabled && event.getID() != MOUSE_HW_ID) { + bool isMouse = event.getID() == PointerManager::MOUSE_POINTER_ID || DependencyManager::get()->isMouse(event.getID()); + if (_currentEntityWithContextOverlay != entityID && _enabled && !isMouse) { disableEntityHighlight(entityID); } } diff --git a/interface/src/ui/overlays/Overlays.cpp b/interface/src/ui/overlays/Overlays.cpp index 5a7b1bd76c..05ff3e289b 100644 --- a/interface/src/ui/overlays/Overlays.cpp +++ b/interface/src/ui/overlays/Overlays.cpp @@ -784,8 +784,6 @@ float Overlays::height() { return offscreenUi->getWindow()->size().height(); } -static const uint32_t MOUSE_POINTER_ID = 0; - static glm::vec2 projectOntoOverlayXYPlane(glm::vec3 position, glm::quat rotation, glm::vec2 dimensions, const PickRay& pickRay, const RayToOverlayIntersectionResult& rayPickResult) { @@ -846,7 +844,7 @@ PointerEvent Overlays::calculateOverlayPointerEvent(OverlayID overlayID, PickRay glm::vec2 pos2D = projectOntoOverlayXYPlane(position, rotation, dimensions, ray, rayPickResult); - PointerEvent pointerEvent(eventType, MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, + PointerEvent pointerEvent(eventType, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, toPointerButton(*event), toPointerButtons(*event), event->modifiers()); return pointerEvent; diff --git a/interface/src/ui/overlays/Web3DOverlay.cpp b/interface/src/ui/overlays/Web3DOverlay.cpp index b8683b43e3..658b4edab8 100644 --- a/interface/src/ui/overlays/Web3DOverlay.cpp +++ b/interface/src/ui/overlays/Web3DOverlay.cpp @@ -56,6 +56,8 @@ #include "ui/Snapshot.h" #include "SoundCache.h" +#include "raypick/PointerScriptingInterface.h" + static const float DPI = 30.47f; static const float INCHES_TO_METERS = 1.0f / 39.3701f; static const float METERS_TO_INCHES = 39.3701f; @@ -216,6 +218,7 @@ void Web3DOverlay::setupQmlSurface() { _webSurface->getSurfaceContext()->setContextProperty("Settings", SettingsScriptingInterface::getInstance()); _webSurface->getSurfaceContext()->setContextProperty("Render", AbstractViewStateInterface::instance()->getRenderEngine()->getConfiguration().get()); _webSurface->getSurfaceContext()->setContextProperty("Controller", DependencyManager::get().data()); + _webSurface->getSurfaceContext()->setContextProperty("Pointers", DependencyManager::get().data()); _webSurface->getSurfaceContext()->setContextProperty("Web3DOverlay", this); _webSurface->getSurfaceContext()->setContextProperty("pathToFonts", "../../"); @@ -245,18 +248,12 @@ void Web3DOverlay::onResizeWebSurface() { _webSurface->resize(QSize(_resolution.x, _resolution.y)); } -const int INVALID_DEVICE_ID = -1; - -Q_INVOKABLE int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) { - auto mapped = _webSurface->getRootItem()->mapFromGlobal(QPoint(x, y)); - - for (auto pair : _activeTouchPoints) { - if (mapped.x() == (int)pair.second.pos().x() && mapped.y() == (int)pair.second.pos().y()) { - return pair.first; - } +unsigned int Web3DOverlay::deviceIdByTouchPoint(qreal x, qreal y) { + if (_webSurface) { + return _webSurface->deviceIdByTouchPoint(x, y); + } else { + return PointerEvent::INVALID_POINTER_ID; } - - return INVALID_DEVICE_ID; } void Web3DOverlay::render(RenderArgs* args) { @@ -341,13 +338,15 @@ void Web3DOverlay::setProxyWindow(QWindow* proxyWindow) { } void Web3DOverlay::hoverLeaveOverlay(const PointerEvent& event) { - if ((_pressed || (!_activeTouchPoints.empty() && _touchBeginAccepted)) && event.sendReleaseOnHoverLeave()) { + if (_inputMode == Mouse) { PointerEvent endEvent(PointerEvent::Release, event.getID(), event.getPos2D(), event.getPos3D(), event.getNormal(), event.getDirection(), event.getButton(), event.getButtons(), event.getKeyboardModifiers()); handlePointerEvent(endEvent); // QML onReleased is only triggered if a click has happened first. We need to send this "fake" mouse move event to properly trigger an onExited. PointerEvent endMoveEvent(PointerEvent::Move, event.getID()); handlePointerEvent(endMoveEvent); + } else if (_webSurface) { + _webSurface->hoverEndEvent(event, _touchDevice); } } @@ -366,93 +365,11 @@ void Web3DOverlay::handlePointerEvent(const PointerEvent& event) { } void Web3DOverlay::handlePointerEventAsTouch(const PointerEvent& event) { - if (!_webSurface) { - return; + if (_webSurface) { + PointerEvent webEvent = event; + webEvent.setPos2D(event.getPos2D() * (METERS_TO_INCHES * _dpi)); + _webSurface->handlePointerEvent(webEvent, _touchDevice); } - - glm::vec2 windowPos = event.getPos2D() * (METERS_TO_INCHES * _dpi); - QPointF windowPoint(windowPos.x, windowPos.y); - - Qt::TouchPointState state = Qt::TouchPointStationary; - if (event.getType() == PointerEvent::Press && event.getButton() == PointerEvent::PrimaryButton) { - state = Qt::TouchPointPressed; - } else if (event.getType() == PointerEvent::Release) { - state = Qt::TouchPointReleased; - } else if (_activeTouchPoints.count(event.getID()) && windowPoint != _activeTouchPoints[event.getID()].pos()) { - state = Qt::TouchPointMoved; - } - - QEvent::Type touchType = QEvent::TouchUpdate; - if (_activeTouchPoints.empty()) { - // If the first active touch point is being created, send a begin - touchType = QEvent::TouchBegin; - } if (state == Qt::TouchPointReleased && _activeTouchPoints.size() == 1 && _activeTouchPoints.count(event.getID())) { - // If the last active touch point is being released, send an end - touchType = QEvent::TouchEnd; - } - - { - QTouchEvent::TouchPoint point; - point.setId(event.getID()); - point.setState(state); - point.setPos(windowPoint); - point.setScreenPos(windowPoint); - _activeTouchPoints[event.getID()] = point; - } - - QTouchEvent touchEvent(touchType, &_touchDevice, event.getKeyboardModifiers()); - { - QList touchPoints; - Qt::TouchPointStates touchPointStates; - for (const auto& entry : _activeTouchPoints) { - touchPointStates |= entry.second.state(); - touchPoints.push_back(entry.second); - } - - touchEvent.setWindow(_webSurface->getWindow()); - touchEvent.setDevice(&_touchDevice); - touchEvent.setTarget(_webSurface->getRootItem()); - touchEvent.setTouchPoints(touchPoints); - touchEvent.setTouchPointStates(touchPointStates); - } - - // Send mouse events to the Web surface so that HTML dialog elements work with mouse press and hover. - // - // In Qt 5.9 mouse events must be sent before touch events to make sure some QtQuick components will - // receive mouse events - Qt::MouseButton button = Qt::NoButton; - Qt::MouseButtons buttons = Qt::NoButton; - if (event.getButton() == PointerEvent::PrimaryButton) { - button = Qt::LeftButton; - } - if (event.getButtons() & PointerEvent::PrimaryButton) { - buttons |= Qt::LeftButton; - } - -#if QT_VERSION >= QT_VERSION_CHECK(5, 9, 0) - if (event.getType() == PointerEvent::Move) { - QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); - QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); - } -#endif - - if (touchType == QEvent::TouchBegin) { - _touchBeginAccepted = QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent); - } else if (_touchBeginAccepted) { - QCoreApplication::sendEvent(_webSurface->getWindow(), &touchEvent); - } - - // If this was a release event, remove the point from the active touch points - if (state == Qt::TouchPointReleased) { - _activeTouchPoints.erase(event.getID()); - } - -#if QT_VERSION < QT_VERSION_CHECK(5, 9, 0) - if (event.getType() == PointerEvent::Move) { - QMouseEvent mouseEvent(QEvent::MouseMove, windowPoint, windowPoint, windowPoint, button, buttons, event.getKeyboardModifiers()); - QCoreApplication::sendEvent(_webSurface->getWindow(), &mouseEvent); - } -#endif } void Web3DOverlay::handlePointerEventAsMouse(const PointerEvent& event) { diff --git a/interface/src/ui/overlays/Web3DOverlay.h b/interface/src/ui/overlays/Web3DOverlay.h index 2fc63df76a..d2da1f7310 100644 --- a/interface/src/ui/overlays/Web3DOverlay.h +++ b/interface/src/ui/overlays/Web3DOverlay.h @@ -67,7 +67,7 @@ public: void destroyWebSurface(); void onResizeWebSurface(); - Q_INVOKABLE int deviceIdByTouchPoint(qreal x, qreal y); + Q_INVOKABLE unsigned int deviceIdByTouchPoint(qreal x, qreal y); public slots: void emitScriptEvent(const QVariant& scriptMessage); @@ -98,8 +98,6 @@ private: bool _showKeyboardFocusHighlight{ true }; bool _pressed{ false }; - bool _touchBeginAccepted { false }; - std::map _activeTouchPoints; QTouchDevice _touchDevice; uint8_t _desiredMaxFPS { 10 }; diff --git a/libraries/entities-renderer/src/EntityTreeRenderer.cpp b/libraries/entities-renderer/src/EntityTreeRenderer.cpp index ad1c617287..8493d73db3 100644 --- a/libraries/entities-renderer/src/EntityTreeRenderer.cpp +++ b/libraries/entities-renderer/src/EntityTreeRenderer.cpp @@ -571,8 +571,6 @@ static PointerEvent::Button toPointerButton(const QMouseEvent& event) { } } -static const uint32_t MOUSE_POINTER_ID = 0; - void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { // If we don't have a tree, or we're in the process of shutting down, then don't // process these events. @@ -593,7 +591,7 @@ void EntityTreeRenderer::mousePressEvent(QMouseEvent* event) { } glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); - PointerEvent pointerEvent(PointerEvent::Press, MOUSE_POINTER_ID, + PointerEvent pointerEvent(PointerEvent::Press, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, toPointerButton(*event), toPointerButtons(*event), @@ -625,7 +623,7 @@ void EntityTreeRenderer::mouseDoublePressEvent(QMouseEvent* event) { RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); if (rayPickResult.intersects && rayPickResult.entity) { glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); - PointerEvent pointerEvent(PointerEvent::Press, MOUSE_POINTER_ID, + PointerEvent pointerEvent(PointerEvent::Press, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, toPointerButton(*event), toPointerButtons(*event), Qt::NoModifier); @@ -657,7 +655,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { //qCDebug(entitiesrenderer) << "mouseReleaseEvent over entity:" << rayPickResult.entityID; glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); - PointerEvent pointerEvent(PointerEvent::Release, MOUSE_POINTER_ID, + PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, toPointerButton(*event), toPointerButtons(*event), @@ -673,7 +671,7 @@ void EntityTreeRenderer::mouseReleaseEvent(QMouseEvent* event) { // we're releasing the button, then this is considered a clickReleaseOn event if (!_currentClickingOnEntityID.isInvalidID()) { glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); - PointerEvent pointerEvent(PointerEvent::Release, MOUSE_POINTER_ID, + PointerEvent pointerEvent(PointerEvent::Release, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, toPointerButton(*event), toPointerButtons(*event), @@ -699,7 +697,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { RayToEntityIntersectionResult rayPickResult = _getPrevRayPickResultOperator(_mouseRayPickID); if (rayPickResult.intersects && rayPickResult.entity) { glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); - PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID, + PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, toPointerButton(*event), toPointerButtons(*event), @@ -713,7 +711,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // then we need to send the hover leave. if (!_currentHoverOverEntityID.isInvalidID() && rayPickResult.entityID != _currentHoverOverEntityID) { glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); - PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID, + PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, toPointerButton(*event), toPointerButtons(*event), @@ -744,7 +742,7 @@ void EntityTreeRenderer::mouseMoveEvent(QMouseEvent* event) { // send the hover leave for our previous entity if (!_currentHoverOverEntityID.isInvalidID()) { glm::vec2 pos2D = projectOntoEntityXYPlane(rayPickResult.entity, ray, rayPickResult); - PointerEvent pointerEvent(PointerEvent::Move, MOUSE_POINTER_ID, + PointerEvent pointerEvent(PointerEvent::Move, PointerManager::MOUSE_POINTER_ID, pos2D, rayPickResult.intersection, rayPickResult.surfaceNormal, ray.direction, toPointerButton(*event), toPointerButtons(*event), diff --git a/libraries/pointers/src/pointers/Pick.h b/libraries/pointers/src/pointers/Pick.h index e4ce5d3dce..b875285ad7 100644 --- a/libraries/pointers/src/pointers/Pick.h +++ b/libraries/pointers/src/pointers/Pick.h @@ -182,6 +182,10 @@ public: void setIgnoreItems(const QVector& items); void setIncludeItems(const QVector& items); + virtual bool isLeftHand() const { return false; } + virtual bool isRightHand() const { return false; } + virtual bool isMouse() const { return false; } + private: PickFilter _filter; const float _maxDistance; diff --git a/libraries/pointers/src/pointers/PickManager.cpp b/libraries/pointers/src/pointers/PickManager.cpp index cb9d715ba9..d601801d76 100644 --- a/libraries/pointers/src/pointers/PickManager.cpp +++ b/libraries/pointers/src/pointers/PickManager.cpp @@ -96,4 +96,28 @@ void PickManager::update() { bool shouldPickHUD = _shouldPickHUDOperator(); _rayPickCacheOptimizer.update(cachedPicks[PickQuery::Ray], shouldPickHUD); +} + +bool PickManager::isLeftHand(unsigned int uid) { + auto pick = findPick(uid); + if (pick) { + return pick->isLeftHand(); + } + return false; +} + +bool PickManager::isRightHand(unsigned int uid) { + auto pick = findPick(uid); + if (pick) { + return pick->isRightHand(); + } + return false; +} + +bool PickManager::isMouse(unsigned int uid) { + auto pick = findPick(uid); + if (pick) { + return pick->isMouse(); + } + return false; } \ No newline at end of file diff --git a/libraries/pointers/src/pointers/PickManager.h b/libraries/pointers/src/pointers/PickManager.h index 0794ac0a41..bdc3e16054 100644 --- a/libraries/pointers/src/pointers/PickManager.h +++ b/libraries/pointers/src/pointers/PickManager.h @@ -33,6 +33,10 @@ public: void setIgnoreItems(unsigned int uid, const QVector& ignore) const; void setIncludeItems(unsigned int uid, const QVector& include) const; + bool isLeftHand(unsigned int uid); + bool isRightHand(unsigned int uid); + bool isMouse(unsigned int uid); + void setShouldPickHUDOperator(std::function shouldPickHUDOperator) { _shouldPickHUDOperator = shouldPickHUDOperator; } void setCalculatePos2DFromHUDOperator(std::function calculatePos2DFromHUDOperator) { _calculatePos2DFromHUDOperator = calculatePos2DFromHUDOperator; } glm::vec2 calculatePos2DFromHUD(const glm::vec3& intersection) { return _calculatePos2DFromHUDOperator(intersection); } diff --git a/libraries/pointers/src/pointers/Pointer.cpp b/libraries/pointers/src/pointers/Pointer.cpp index 0fc5c54ad2..6e95b48280 100644 --- a/libraries/pointers/src/pointers/Pointer.cpp +++ b/libraries/pointers/src/pointers/Pointer.cpp @@ -46,6 +46,18 @@ void Pointer::setIncludeItems(const QVector& includeItems) const { DependencyManager::get()->setIncludeItems(_pickUID, includeItems); } +bool Pointer::isLeftHand() const { + return DependencyManager::get()->isLeftHand(_pickUID); +} + +bool Pointer::isRightHand() const { + return DependencyManager::get()->isRightHand(_pickUID); +} + +bool Pointer::isMouse() const { + return DependencyManager::get()->isMouse(_pickUID); +} + void Pointer::update(unsigned int pointerID) { // This only needs to be a read lock because update won't change any of the properties that can be modified from scripts withReadLock([&] { diff --git a/libraries/pointers/src/pointers/Pointer.h b/libraries/pointers/src/pointers/Pointer.h index d8661436b9..6d9c1a172b 100644 --- a/libraries/pointers/src/pointers/Pointer.h +++ b/libraries/pointers/src/pointers/Pointer.h @@ -53,6 +53,10 @@ public: virtual void setIgnoreItems(const QVector& ignoreItems) const; virtual void setIncludeItems(const QVector& includeItems) const; + bool isLeftHand() const; + bool isRightHand() const; + bool isMouse() const; + // Pointers can choose to implement these virtual void setLength(float length) {} virtual void setLockEndUUID(const QUuid& objectID, bool isOverlay) {} diff --git a/libraries/pointers/src/pointers/PointerManager.cpp b/libraries/pointers/src/pointers/PointerManager.cpp index b117b24770..de85e74da1 100644 --- a/libraries/pointers/src/pointers/PointerManager.cpp +++ b/libraries/pointers/src/pointers/PointerManager.cpp @@ -120,3 +120,27 @@ void PointerManager::setLockEndUUID(unsigned int uid, const QUuid& objectID, boo pointer->setLockEndUUID(objectID, isOverlay); } } + +bool PointerManager::isLeftHand(unsigned int uid) { + auto pointer = find(uid); + if (pointer) { + return pointer->isLeftHand(); + } + return false; +} + +bool PointerManager::isRightHand(unsigned int uid) { + auto pointer = find(uid); + if (pointer) { + return pointer->isRightHand(); + } + return false; +} + +bool PointerManager::isMouse(unsigned int uid) { + auto pointer = find(uid); + if (pointer) { + return pointer->isMouse(); + } + return false; +} \ No newline at end of file diff --git a/libraries/pointers/src/pointers/PointerManager.h b/libraries/pointers/src/pointers/PointerManager.h index a415c9dbd9..461d27959c 100644 --- a/libraries/pointers/src/pointers/PointerManager.h +++ b/libraries/pointers/src/pointers/PointerManager.h @@ -40,6 +40,10 @@ public: void update(); + bool isLeftHand(unsigned int uid); + bool isRightHand(unsigned int uid); + bool isMouse(unsigned int uid); + static const unsigned int MOUSE_POINTER_ID { PointerEvent::INVALID_POINTER_ID + 1 }; private: diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index e2675008f9..41c3da2a4f 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -927,6 +927,18 @@ bool OffscreenQmlSurface::eventFilter(QObject* originalDestination, QEvent* even return false; } +unsigned int OffscreenQmlSurface::deviceIdByTouchPoint(qreal x, qreal y) { + auto mapped = _rootItem->mapFromGlobal(QPoint(x, y)); + + for (auto pair : _activeTouchPoints) { + if (mapped.x() == (int)pair.second.pos().x() && mapped.y() == (int)pair.second.pos().y()) { + return pair.first; + } + } + + return PointerEvent::INVALID_POINTER_ID; +} + PointerEvent::EventType OffscreenQmlSurface::choosePointerEventType(QEvent::Type type) { switch (type) { case QEvent::MouseButtonDblClick: diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.h b/libraries/ui/src/ui/OffscreenQmlSurface.h index 1f1817faa2..8ddfbfe8cc 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.h +++ b/libraries/ui/src/ui/OffscreenQmlSurface.h @@ -100,6 +100,8 @@ public: PointerEvent::EventType choosePointerEventType(QEvent::Type type); + unsigned int deviceIdByTouchPoint(qreal x, qreal y); + signals: void focusObjectChanged(QObject* newFocus); void focusTextChanged(bool focusText);