diff --git a/android/docker/Dockerfile b/android/docker/Dockerfile index 105bcb7cb0..f6c0e7b2e5 100644 --- a/android/docker/Dockerfile +++ b/android/docker/Dockerfile @@ -71,9 +71,13 @@ RUN mkdir "$HIFI_BASE" && \ mkdir "$HIFI_VCPKG_BASE" && \ mkdir "$HIFI_ANDROID_PRECOMPILED" -RUN git clone https://github.com/jherico/hifi.git && \ +# Checkout a relatively recent commit from the main repository and use it to cache the +# gradle and vcpkg dependencies +# This commit ID should be updated whenever someone changes the dependency list +# in cmake/ports +RUN git clone https://github.com/highfidelity/hifi.git && \ cd ~/hifi && \ - git checkout quest/build + git checkout 796bfb5d6715ff14c2e60f3ee8fac1465b7578c6 WORKDIR /home/jenkins/hifi diff --git a/assignment-client/src/avatars/AvatarMixer.cpp b/assignment-client/src/avatars/AvatarMixer.cpp index 2174d7ff05..45511cf6cd 100644 --- a/assignment-client/src/avatars/AvatarMixer.cpp +++ b/assignment-client/src/avatars/AvatarMixer.cpp @@ -83,7 +83,7 @@ AvatarMixer::AvatarMixer(ReceivedMessage& message) : packetReceiver.registerListener(PacketType::BulkAvatarTraitsAck, this, "queueIncomingPacket"); packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase }, this, "handleOctreePacket"); - packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "handleChallengeOwnership"); + packetReceiver.registerListener(PacketType::ChallengeOwnership, this, "queueIncomingPacket"); packetReceiver.registerListenerForTypes({ PacketType::ReplicatedAvatarIdentity, @@ -1136,16 +1136,6 @@ void AvatarMixer::entityChange() { _dirtyHeroStatus = true; } -void AvatarMixer::handleChallengeOwnership(QSharedPointer message, SharedNodePointer senderNode) { - if (senderNode->getType() == NodeType::Agent && senderNode->getLinkedData()) { - auto clientData = static_cast(senderNode->getLinkedData()); - auto avatar = clientData->getAvatarSharedPointer(); - if (avatar) { - avatar->handleChallengeResponse(message.data()); - } - } -} - void AvatarMixer::aboutToFinish() { DependencyManager::destroy(); DependencyManager::destroy(); diff --git a/assignment-client/src/avatars/AvatarMixer.h b/assignment-client/src/avatars/AvatarMixer.h index 93dc755f51..10dff5e8a4 100644 --- a/assignment-client/src/avatars/AvatarMixer.h +++ b/assignment-client/src/avatars/AvatarMixer.h @@ -65,7 +65,6 @@ private slots: void domainSettingsRequestComplete(); void handlePacketVersionMismatch(PacketType type, const HifiSockAddr& senderSockAddr, const QUuid& senderUUID); void handleOctreePacket(QSharedPointer message, SharedNodePointer senderNode); - void handleChallengeOwnership(QSharedPointer message, SharedNodePointer senderNode); void start(); private: diff --git a/assignment-client/src/avatars/AvatarMixerClientData.cpp b/assignment-client/src/avatars/AvatarMixerClientData.cpp index 8081c77ee8..1195f0d801 100644 --- a/assignment-client/src/avatars/AvatarMixerClientData.cpp +++ b/assignment-client/src/avatars/AvatarMixerClientData.cpp @@ -74,6 +74,9 @@ int AvatarMixerClientData::processPackets(const SlaveSharedData& slaveSharedData case PacketType::BulkAvatarTraitsAck: processBulkAvatarTraitsAckMessage(*packet); break; + case PacketType::ChallengeOwnership: + _avatar->processChallengeResponse(*packet); + break; default: Q_UNREACHABLE(); } diff --git a/assignment-client/src/avatars/MixerAvatar.cpp b/assignment-client/src/avatars/MixerAvatar.cpp index 6d6358ddf5..29f7e9ebd2 100644 --- a/assignment-client/src/avatars/MixerAvatar.cpp +++ b/assignment-client/src/avatars/MixerAvatar.cpp @@ -27,13 +27,33 @@ #include "ClientTraitsHandler.h" #include "AvatarLogging.h" -MixerAvatar::~MixerAvatar() { - if (_challengeTimeout) { - _challengeTimeout->deleteLater(); - } +MixerAvatar::MixerAvatar() { + static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s + + _challengeTimer.setSingleShot(true); + _challengeTimer.setInterval(CHALLENGE_TIMEOUT_MS); + + _challengeTimer.callOnTimeout([this]() { + if (_verifyState == challengeClient) { + _pendingEvent = false; + _verifyState = verificationFailed; + _needsIdentityUpdate = true; + qCDebug(avatars) << "Dynamic verification TIMED-OUT for " << getDisplayName() << getSessionUUID(); + } else { + qCDebug(avatars) << "Ignoring timeout of avatar challenge"; + } + }); + +} + +const char* MixerAvatar::stateToName(VerifyState state) { + return QMetaEnum::fromType().valueToKey(state); } void MixerAvatar::fetchAvatarFST() { + if (_verifyState >= requestingFST && _verifyState <= challengeClient) { + qCDebug(avatars) << "WARNING: Avatar verification restarted; old state:" << stateToName(_verifyState); + } _verifyState = nonCertified; _pendingEvent = false; @@ -80,7 +100,7 @@ void MixerAvatar::fetchAvatarFST() { void MixerAvatar::fstRequestComplete() { ResourceRequest* fstRequest = static_cast(QObject::sender()); QMutexLocker certifyLocker(&_avatarCertifyLock); - if (fstRequest == _avatarRequest) { + if (_verifyState == requestingFST && fstRequest == _avatarRequest) { auto result = fstRequest->getResult(); if (result != ResourceRequest::Success) { _verifyState = error; @@ -90,11 +110,11 @@ void MixerAvatar::fstRequestComplete() { _verifyState = receivedFST; _pendingEvent = true; } - _avatarRequest->deleteLater(); _avatarRequest = nullptr; } else { - qCDebug(avatars) << "Incorrect request for" << getDisplayName(); + qCDebug(avatars) << "Incorrect or outdated FST request for" << getDisplayName(); } + fstRequest->deleteLater(); } bool MixerAvatar::generateFSTHash() { @@ -108,7 +128,7 @@ bool MixerAvatar::generateFSTHash() { return true; } -bool MixerAvatar::validateFSTHash(const QString& publicKey) { +bool MixerAvatar::validateFSTHash(const QString& publicKey) const { // Guess we should refactor this stuff into a Authorization namespace ... return EntityItemProperties::verifySignature(publicKey, _certificateHash, QByteArray::fromBase64(_certificateIdFromFST.toUtf8())); @@ -171,7 +191,9 @@ void MixerAvatar::ownerRequestComplete() { QMutexLocker certifyLocker(&_avatarCertifyLock); QNetworkReply* networkReply = static_cast(QObject::sender()); - if (networkReply->error() == QNetworkReply::NoError) { + if (_verifyState != requestingOwner) { + qCDebug(avatars) << "WARNING: outdated avatar-owner information received in state" << stateToName(_verifyState); + } else if (networkReply->error() == QNetworkReply::NoError) { _dynamicMarketResponse = networkReply->readAll(); _verifyState = ownerResponse; _pendingEvent = true; @@ -259,7 +281,6 @@ void MixerAvatar::processCertifyEvents() { } sendOwnerChallenge(); _verifyState = challengeClient; - _pendingEvent = true; } else { _verifyState = error; qCDebug(avatars) << "Get owner status - couldn't parse response for" << getSessionUUID() @@ -273,46 +294,14 @@ void MixerAvatar::processCertifyEvents() { break; } - case challengeResponse: - { - if (_challengeResponse.length() < 8) { - _verifyState = error; - _pendingEvent = false; - break; - } - - int avatarIDLength; - int signedNonceLength; - { - QDataStream responseStream(_challengeResponse); - responseStream.setByteOrder(QDataStream::LittleEndian); - responseStream >> avatarIDLength >> signedNonceLength; - } - QByteArray avatarID(_challengeResponse.data() + 2 * sizeof(int), avatarIDLength); - QByteArray signedNonce(_challengeResponse.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength); - - bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash, - QByteArray::fromBase64(signedNonce)); - _verifyState = challengeResult ? verificationSucceeded : verificationFailed; - _needsIdentityUpdate = true; - if (_verifyState == verificationFailed) { - qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID(); - } else { - qCDebug(avatars) << "Dynamic verification SUCCEEDED for " << getDisplayName() << getSessionUUID(); - } - _pendingEvent = false; - break; - } - case requestingOwner: - case challengeClient: { // Qt networking done on this thread: QCoreApplication::processEvents(); break; } default: - qCDebug(avatars) << "Unexpected verify state" << _verifyState; + qCDebug(avatars) << "Unexpected verify state" << stateToName(_verifyState); break; } // close switch @@ -334,32 +323,45 @@ void MixerAvatar::sendOwnerChallenge() { QCryptographicHash nonceHash(QCryptographicHash::Sha256); nonceHash.addData(nonce); _challengeNonceHash = nonceHash.result(); - - static constexpr int CHALLENGE_TIMEOUT_MS = 10 * 1000; // 10 s - if (_challengeTimeout) { - _challengeTimeout->deleteLater(); - } - _challengeTimeout = new QTimer(); - _challengeTimeout->setInterval(CHALLENGE_TIMEOUT_MS); - _challengeTimeout->setSingleShot(true); - _challengeTimeout->connect(_challengeTimeout, &QTimer::timeout, this, [this]() { - if (_verifyState == challengeClient) { - _pendingEvent = false; - _verifyState = verificationFailed; - _needsIdentityUpdate = true; - qCDebug(avatars) << "Dynamic verification TIMED-OUT for " << getDisplayName() << getSessionUUID(); - } - }); - _challengeTimeout->start(); + _pendingEvent = false; + + // QTimer::start is a set of overloaded functions. + QMetaObject::invokeMethod(&_challengeTimer, static_cast(&QTimer::start)); } -void MixerAvatar::handleChallengeResponse(ReceivedMessage* response) { +void MixerAvatar::processChallengeResponse(ReceivedMessage& response) { QByteArray avatarID; - QByteArray encryptedNonce; QMutexLocker certifyLocker(&_avatarCertifyLock); + QMetaObject::invokeMethod(&_challengeTimer, &QTimer::stop); if (_verifyState == challengeClient) { - _challengeResponse = response->readAll(); - _verifyState = challengeResponse; - _pendingEvent = true; + QByteArray responseData = response.readAll(); + if (responseData.length() < 8) { + _verifyState = error; + qCDebug(avatars) << "Avatar challenge response packet too small, length:" << responseData.length(); + return; + } + + int avatarIDLength; + int signedNonceLength; + { + QDataStream responseStream(responseData); + responseStream.setByteOrder(QDataStream::LittleEndian); + responseStream >> avatarIDLength >> signedNonceLength; + } + QByteArray avatarID(responseData.data() + 2 * sizeof(int), avatarIDLength); + QByteArray signedNonce(responseData.data() + 2 * sizeof(int) + avatarIDLength, signedNonceLength); + + bool challengeResult = EntityItemProperties::verifySignature(_ownerPublicKey, _challengeNonceHash, + QByteArray::fromBase64(signedNonce)); + _verifyState = challengeResult ? verificationSucceeded : verificationFailed; + _needsIdentityUpdate = true; + if (_verifyState == verificationFailed) { + qCDebug(avatars) << "Dynamic verification FAILED for " << getDisplayName() << getSessionUUID(); + } else { + qCDebug(avatars) << "Dynamic verification SUCCEEDED for" << getDisplayName() << getSessionUUID(); + } + + } else { + qCDebug(avatars) << "WARNING: Unexpected avatar challenge-response in state" << stateToName(_verifyState); } } diff --git a/assignment-client/src/avatars/MixerAvatar.h b/assignment-client/src/avatars/MixerAvatar.h index e8d9c959db..2ef4d16dc4 100644 --- a/assignment-client/src/avatars/MixerAvatar.h +++ b/assignment-client/src/avatars/MixerAvatar.h @@ -9,8 +9,7 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // -// Avatar class for use within the avatar mixer - encapsulates data required only for -// sorting priorities within the mixer. +// Avatar class for use within the avatar mixer - includes avatar-verification state. #ifndef hifi_MixerAvatar_h #define hifi_MixerAvatar_h @@ -20,8 +19,10 @@ class ResourceRequest; class MixerAvatar : public AvatarData { + Q_OBJECT public: - ~MixerAvatar(); + MixerAvatar(); + bool getNeedsHeroCheck() const { return _needsHeroCheck; } void setNeedsHeroCheck(bool needsHeroCheck = true) { _needsHeroCheck = needsHeroCheck; } @@ -31,15 +32,18 @@ public: void setNeedsIdentityUpdate(bool value = true) { _needsIdentityUpdate = value; } void processCertifyEvents(); - void handleChallengeResponse(ReceivedMessage* response); + void processChallengeResponse(ReceivedMessage& response); + + // Avatar certification/verification: + enum VerifyState { + nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse, + challengeClient, verified, verificationFailed, verificationSucceeded, error + }; + Q_ENUM(VerifyState) private: bool _needsHeroCheck { false }; - - // Avatar certification/verification: - enum VerifyState { nonCertified, requestingFST, receivedFST, staticValidation, requestingOwner, ownerResponse, - challengeClient, challengeResponse, verified, verificationFailed, verificationSucceeded, error }; - Q_ENUM(VerifyState); + static const char* stateToName(VerifyState state); VerifyState _verifyState { nonCertified }; std::atomic _pendingEvent { false }; QMutex _avatarCertifyLock; @@ -53,12 +57,11 @@ private: QString _dynamicMarketResponse; QString _ownerPublicKey; QByteArray _challengeNonceHash; - QByteArray _challengeResponse; - QTimer* _challengeTimeout { nullptr }; + QTimer _challengeTimer; bool _needsIdentityUpdate { false }; bool generateFSTHash(); - bool validateFSTHash(const QString& publicKey); + bool validateFSTHash(const QString& publicKey) const; QByteArray canonicalJson(const QString fstFile); void sendOwnerChallenge(); diff --git a/cmake/compiler.cmake b/cmake/compiler.cmake index aaca33ab1c..45cef27727 100644 --- a/cmake/compiler.cmake +++ b/cmake/compiler.cmake @@ -34,7 +34,7 @@ if (WIN32) list(APPEND CMAKE_PREFIX_PATH "${WINDOW_SDK_PATH}") # /wd4351 disables warning C4351: new behavior: elements of array will be default initialized - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP /wd4351") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP${HIFI_MAX_BUILD_CORES} /wd4351") # /LARGEADDRESSAWARE enables 32-bit apps to use more than 2GB of memory. # Caveats: http://stackoverflow.com/questions/2288728/drawbacks-of-using-largeaddressaware-for-32-bit-windows-executables # TODO: Remove when building 64-bit. diff --git a/cmake/macros/SetPackagingParameters.cmake b/cmake/macros/SetPackagingParameters.cmake index f8cff9f773..e3369e3afb 100644 --- a/cmake/macros/SetPackagingParameters.cmake +++ b/cmake/macros/SetPackagingParameters.cmake @@ -192,12 +192,12 @@ macro(SET_PACKAGING_PARAMETERS) # shortcut names if (PRODUCTION_BUILD) - set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface") + set(INTERFACE_SHORTCUT_NAME "High Fidelity") set(CONSOLE_SHORTCUT_NAME "Console") set(SANDBOX_SHORTCUT_NAME "Sandbox") set(APP_USER_MODEL_ID "com.highfidelity.console") else () - set(INTERFACE_SHORTCUT_NAME "High Fidelity Interface - ${BUILD_VERSION_NO_SHA}") + set(INTERFACE_SHORTCUT_NAME "High Fidelity - ${BUILD_VERSION_NO_SHA}") set(CONSOLE_SHORTCUT_NAME "Console - ${BUILD_VERSION_NO_SHA}") set(SANDBOX_SHORTCUT_NAME "Sandbox - ${BUILD_VERSION_NO_SHA}") endif () diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 09438b31bc..74ad014b53 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1746,8 +1746,8 @@ void DomainServer::nodePingMonitor() { } void DomainServer::processOctreeDataPersistMessage(QSharedPointer message) { - qDebug() << "Received octree data persist message"; auto data = message->readAll(); + qDebug() << "Received octree data persist message" << (data.size() / 1000) << "kbytes."; auto filePath = getEntitiesFilePath(); QDir dir(getEntitiesDirPath()); @@ -1759,12 +1759,16 @@ void DomainServer::processOctreeDataPersistMessage(QSharedPointer + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml index c9857afcec..ffb4f58355 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml @@ -148,6 +148,7 @@ Rectangle { } } + Item { id: tabViewContainers anchors.top: tabContainer.bottom @@ -155,7 +156,6 @@ Rectangle { anchors.right: parent.right anchors.bottom: parent.bottom - GeneralSettings.General { id: generalTabViewContainer visible: activeTabView === "generalTabView" @@ -163,8 +163,10 @@ Rectangle { onSendNameTagInfo: { sendToScript(message); } + onSendEmoteVisible: { + sendToScript(message); + } } - AudioSettings.Audio { id: audioTabViewContainer diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml index a2bf26161e..9e58a0aa98 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml @@ -112,6 +112,49 @@ Flickable { } } + ColumnLayout { + id: emoteContainer + Layout.preferredWidth: parent.width + spacing: 0 + + HifiStylesUit.GraphikSemiBold { + id: emoteTitle + text: "Emote UI" + Layout.maximumWidth: parent.width + height: paintedHeight + size: 22 + color: simplifiedUI.colors.text.white + } + + ColumnLayout { + id: emoteSwitchGroup + Layout.preferredWidth: parent.width + Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin + + SimplifiedControls.Switch { + id: emoteSwitch + Layout.preferredHeight: 18 + Layout.preferredWidth: parent.width + labelTextOn: "Show Emote UI" + checked: Settings.getValue("simplifiedUI/emoteIndicatorVisible", true) + onClicked: { + var currentSetting = Settings.getValue("simplifiedUI/emoteIndicatorVisible", true); + Settings.setValue("simplifiedUI/emoteIndicatorVisible", !currentSetting); + } + + Connections { + target: Settings + + onValueChanged: { + if (setting === "simplifiedUI/emoteIndicatorVisible") { + emoteSwitch.checked = value; + } + } + } + } + } + } + ColumnLayout { id: performanceContainer Layout.preferredWidth: parent.width @@ -197,7 +240,7 @@ Flickable { } } - Connections { + Connections { target: Camera onModeUpdated: { @@ -207,7 +250,7 @@ Flickable { thirdPerson.checked = true } } - } + } } } @@ -248,4 +291,5 @@ Flickable { } signal sendNameTagInfo(var message); + signal sendEmoteVisible(var message); } diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml index 88a2f46de8..9fff49442e 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml @@ -230,7 +230,7 @@ QtObject { readonly property int textSize: 14 } readonly property QtObject textField: QtObject { - readonly property int editPencilPadding: 6 + readonly property int rightGlyphPadding: 6 } readonly property QtObject scrollBar: QtObject { readonly property int backgroundWidth: 9 diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/qmldir b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/qmldir new file mode 100644 index 0000000000..a2b5c292b2 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/qmldir @@ -0,0 +1,2 @@ +module hifi.simplifiedUI.simplifiedConstants +SimplifiedConstants 1.0 SimplifiedConstants.qml \ No newline at end of file diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/ProgressCircle.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/ProgressCircle.qml new file mode 100644 index 0000000000..77df295395 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/ProgressCircle.qml @@ -0,0 +1,97 @@ +// From http://www.bytebau.com/progress-circle-with-qml-and-javascript/ +// ByteBau (Jörn Buchholz) @bytebau.com + +import QtQuick 2.0 +import QtQml 2.2 + +Item { + id: root + + width: size + height: size + + property int size: 200 // The size of the circle in pixel + property real arcBegin: 0 // start arc angle in degree + property real arcEnd: 360 // end arc angle in degree + property real arcOffset: 0 // rotation + property bool isPie: false // paint a pie instead of an arc + property bool showBackground: false // a full circle as a background of the arc + property real lineWidth: 20 // width of the line + property string colorCircle: "#CC3333" + property string colorBackground: "#779933" + + property alias beginAnimation: animationArcBegin.enabled + property alias endAnimation: animationArcEnd.enabled + + property int animationDuration: 200 + + onArcBeginChanged: canvas.requestPaint() + onArcEndChanged: canvas.requestPaint() + + Behavior on arcBegin { + id: animationArcBegin + enabled: true + NumberAnimation { + duration: root.animationDuration + easing.type: Easing.InOutCubic + } + } + + Behavior on arcEnd { + id: animationArcEnd + enabled: true + NumberAnimation { + duration: root.animationDuration + easing.type: Easing.OutQuad + } + } + + Canvas { + id: canvas + anchors.fill: parent + rotation: -90 + parent.arcOffset + + onPaint: { + var ctx = getContext("2d") + var x = width / 2 + var y = height / 2 + var start = Math.PI * (parent.arcBegin / 180) + var end = Math.PI * (parent.arcEnd / 180) + ctx.reset() + + if (root.isPie) { + if (root.showBackground) { + ctx.beginPath() + ctx.fillStyle = root.colorBackground + ctx.moveTo(x, y) + ctx.arc(x, y, width / 2, 0, Math.PI * 2, false) + ctx.lineTo(x, y) + ctx.fill() + } + ctx.beginPath() + ctx.fillStyle = root.colorCircle + ctx.moveTo(x, y) + // Using `width` instead of `width/2` as the argument here ensures + // that the ProgressCircle mask will cover the entirety of non-circular emoji. + ctx.arc(x, y, width, start, end, false) + ctx.lineTo(x, y) + ctx.fill() + } else { + if (root.showBackground) { + ctx.beginPath(); + ctx.arc(x, y, (width / 2) - parent.lineWidth / 2, 0, Math.PI * 2, false) + ctx.lineWidth = root.lineWidth + ctx.strokeStyle = root.colorBackground + ctx.stroke() + } + ctx.beginPath(); + ctx.arc(x, y, (width / 2) - parent.lineWidth / 2, start, end, false) + ctx.lineWidth = root.lineWidth + ctx.strokeStyle = root.colorCircle + ctx.stroke() + } + + ctx.scale(0.1, 0.2); + } + } +} \ No newline at end of file diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml index 20d45b3361..bd2b6cf538 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/TextField.qml @@ -21,6 +21,8 @@ TextField { id: simplifiedUI } + property string rightGlyph: "" + color: simplifiedUI.colors.text.white font.family: "Graphik Medium" font.pixelSize: 22 @@ -31,7 +33,7 @@ TextField { autoScroll: false hoverEnabled: true leftPadding: 0 - rightPadding: editPencil.implicitWidth + simplifiedUI.sizes.controls.textField.editPencilPadding + rightPadding: root.rightGlyph === "" ? 0 : rightGlyphItem.implicitWidth + simplifiedUI.sizes.controls.textField.rightGlyphPadding onFocusChanged: { if (focus) { @@ -59,8 +61,9 @@ TextField { } HifiStylesUit.HiFiGlyphs { - id: editPencil - text: simplifiedUI.glyphs.pencil + id: rightGlyphItem + text: root.rightGlyph + visible: rightGlyphItem.text !== "" // Text Size size: root.font.pixelSize * 1.5 // Anchors diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/qmldir b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/qmldir new file mode 100644 index 0000000000..49fadf2f21 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedControls/qmldir @@ -0,0 +1,10 @@ +module hifi.simplifiedUI.simplifiedControls +Button 1.0 Button.qml +CheckBox 1.0 CheckBox.qml +InputPeak 1.0 InputPeak.qml +ProgressCircle 1.0 ProgressCircle.qml +RadioButton 1.0 RadioButton.qml +Slider 1.0 Slider.qml +Switch 1.0 Switch.qml +TextField 1.0 TextField.qml +VerticalScrollBar 1.0 VerticalScrollBar.qml \ No newline at end of file diff --git a/interface/resources/qml/hifi/tablet/DynamicWebview.qml b/interface/resources/qml/hifi/tablet/DynamicWebview.qml new file mode 100644 index 0000000000..4343e71e64 --- /dev/null +++ b/interface/resources/qml/hifi/tablet/DynamicWebview.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import "../../controls" as Controls + +Controls.WebView { + id: root + function fromScript(message) { + root.url = message.url; + } +} + + diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 2a310df7f5..b318f07d8f 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -58,6 +58,7 @@ #include #include +#include #include #include #include @@ -73,6 +74,7 @@ #include #include #include +#include #include #include #include @@ -137,6 +139,7 @@ #include #include #include +#include #include #include #include @@ -257,10 +260,6 @@ extern "C" { } #endif -#ifdef Q_OS_MAC -#include "MacHelper.h" -#endif - #if defined(Q_OS_ANDROID) #include #include "AndroidHelper.h" @@ -552,13 +551,6 @@ public: return true; } - if (message->message == WM_POWERBROADCAST) { - if (message->wParam == PBT_APMRESUMEAUTOMATIC) { - qCInfo(interfaceapp) << "Waking up from sleep or hybernation."; - QMetaObject::invokeMethod(DependencyManager::get().data(), "noteAwakening", Qt::QueuedConnection); - } - } - if (message->message == WM_COPYDATA) { COPYDATASTRUCT* pcds = (COPYDATASTRUCT*)(message->lParam); QUrl url = QUrl((const char*)(pcds->lpData)); @@ -964,9 +956,12 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); DependencyManager::set(); -#ifdef Q_OS_MAC - DependencyManager::set(); -#endif + PlatformHelper::setup(); + + QObject::connect(PlatformHelper::instance(), &PlatformHelper::systemWillWake, [] { + QMetaObject::invokeMethod(DependencyManager::get().data(), "noteAwakening", Qt::QueuedConnection); + }); + QString setBookmarkValue = getCmdOption(argc, constArgv, "--setBookmark"); if (!setBookmarkValue.isEmpty()) { @@ -1149,7 +1144,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Graphik-SemiBold.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Graphik-Regular.ttf"); QFontDatabase::addApplicationFont(PathUtils::resourcesPath() + "fonts/Graphik-Medium.ttf"); - _window->setWindowTitle("High Fidelity Interface"); + _window->setWindowTitle("High Fidelity"); Model::setAbstractViewStateInterface(this); // The model class will sometimes need to know view state details from us @@ -1172,6 +1167,17 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo deadlockWatchdogThread->setMainThreadID(QThread::currentThreadId()); deadlockWatchdogThread->start(); + // Pause the deadlock watchdog when we sleep, or it might + // trigger a false positive when we wake back up + auto platformHelper = PlatformHelper::instance(); + + connect(platformHelper, &PlatformHelper::systemWillSleep, [] { + DeadlockWatchdogThread::pause(); + }); + + connect(platformHelper, &PlatformHelper::systemWillWake, [] { + DeadlockWatchdogThread::resume(); + }); // Main thread timer to keep the watchdog updated QTimer* watchdogUpdateTimer = new QTimer(this); @@ -2675,6 +2681,8 @@ void Application::updateHeartbeat() const { } void Application::onAboutToQuit() { + setCrashAnnotation("shutdown", "1"); + // quickly save AvatarEntityData before the EntityTree is dismantled getMyAvatar()->saveAvatarEntityDataToSettings(); @@ -2713,6 +2721,11 @@ void Application::onAboutToQuit() { cleanupBeforeQuit(); + if (_crashOnShutdown) { + // triggered by crash menu + crash::nullDeref(); + } + getRefreshRateManager().setRefreshRateRegime(RefreshRateManager::RefreshRateRegime::SHUTDOWN); } @@ -2868,9 +2881,7 @@ Application::~Application() { _gameWorkload.shutdown(); DependencyManager::destroy(); -#ifdef Q_OS_MAC - DependencyManager::destroy(); -#endif + PlatformHelper::shutdown(); _entityClipboard->eraseAllOctreeElements(); _entityClipboard.reset(); @@ -3140,7 +3151,7 @@ void Application::showLoginScreen() { QJsonObject loginData = {}; loginData["action"] = "login dialog popped up"; UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData); - _window->setWindowTitle("High Fidelity Interface"); + _window->setWindowTitle("High Fidelity"); } else { resumeAfterLoginDialogActionTaken(); } @@ -3275,6 +3286,9 @@ void Application::initializeUi() { } return result.toPoint(); }); + + // BUGZ-1365 - the root context should explicitly default to being unable to load local HTML content + ContextAwareProfile::restrictContext(offscreenUi->getSurfaceContext(), true); offscreenUi->resume(); #endif connect(_window, &MainWindow::windowGeometryChanged, [this](const QRect& r){ @@ -3562,6 +3576,9 @@ void Application::setupQmlSurface(QQmlContext* surfaceContext, bool setAdditiona surfaceContext->setContextProperty("WalletScriptingInterface", DependencyManager::get().data()); surfaceContext->setContextProperty("ResourceRequestObserver", DependencyManager::get().data()); surfaceContext->setContextProperty("PlatformInfo", PlatformInfoScriptingInterface::getInstance()); + // This `module` context property is blank for the QML scripting interface so that we don't get log errors when importing + // certain JS files from both scripts (in the JS context) and QML (in the QML context). + surfaceContext->setContextProperty("module", ""); } } @@ -6282,11 +6299,13 @@ void Application::tryToEnablePhysics() { // process octree stats packets are sent in between full sends of a scene (this isn't currently true). // We keep physics disabled until we've received a full scene and everything near the avatar in that // scene is ready to compute its collision shape. - if (getMyAvatar()->isReadyForPhysics()) { + auto myAvatar = getMyAvatar(); + if (myAvatar->isReadyForPhysics()) { + myAvatar->getCharacterController()->setPhysicsEngine(_physicsEngine); _octreeProcessor.resetSafeLanding(); _physicsEnabled = true; setIsInterstitialMode(false); - getMyAvatar()->updateMotionBehaviorFromMenu(); + myAvatar->updateMotionBehaviorFromMenu(); } } } @@ -6577,7 +6596,7 @@ void Application::update(float deltaTime) { avatarManager->handleProcessedPhysicsTransaction(transaction); myAvatar->prepareForPhysicsSimulation(); - _physicsEngine->enableGlobalContactAddedCallback(myAvatar->isFlying()); + myAvatar->getCharacterController()->preSimulation(); } } @@ -6630,6 +6649,7 @@ void Application::update(float deltaTime) { { PROFILE_RANGE(simulation_physics, "MyAvatar"); + myAvatar->getCharacterController()->postSimulation(); myAvatar->harvestResultsFromPhysicsSimulation(deltaTime); } @@ -7441,7 +7461,7 @@ void Application::addingEntityWithCertificate(const QString& certificateID, cons ledger->updateLocation(certificateID, placeName); } -void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointer scriptEngine) { +void Application::registerScriptEngineWithApplicationServices(const ScriptEnginePointer& scriptEngine) { scriptEngine->setEmitScriptUpdatesFunction([this]() { SharedNodePointer entityServerNode = DependencyManager::get()->soloNodeOfType(NodeType::EntityServer); @@ -7480,9 +7500,19 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe qScriptRegisterMetaType(scriptEngine.data(), RayToOverlayIntersectionResultToScriptValue, RayToOverlayIntersectionResultFromScriptValue); + bool clientScript = scriptEngine->isClientScript(); + #if !defined(DISABLE_QML) scriptEngine->registerGlobalObject("OffscreenFlags", getOffscreenUI()->getFlags()); - scriptEngine->registerGlobalObject("Desktop", DependencyManager::get().data()); + if (clientScript) { + scriptEngine->registerGlobalObject("Desktop", DependencyManager::get().data()); + } else { + auto desktopScriptingInterface = new DesktopScriptingInterface(nullptr, true); + scriptEngine->registerGlobalObject("Desktop", desktopScriptingInterface); + if (QThread::currentThread() != thread()) { + desktopScriptingInterface->moveToThread(thread()); + } + } #endif qScriptRegisterMetaType(scriptEngine.data(), wrapperToScriptValue, wrapperFromScriptValue); @@ -7507,7 +7537,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe scriptEngine->registerGetterSetter("location", LocationScriptingInterface::locationGetter, LocationScriptingInterface::locationSetter); - bool clientScript = scriptEngine->isClientScript(); scriptEngine->registerFunction("OverlayWindow", clientScript ? QmlWindowClass::constructor : QmlWindowClass::restricted_constructor); #if !defined(Q_OS_ANDROID) && !defined(DISABLE_QML) scriptEngine->registerFunction("OverlayWebWindow", clientScript ? QmlWebWindowClass::constructor : QmlWebWindowClass::restricted_constructor); @@ -9519,6 +9548,14 @@ void Application::showUrlHandler(const QUrl& url) { } }); } + +// used to test "shutdown" crash annotation. +void Application::crashOnShutdown() { + qDebug() << "crashOnShutdown(), ON PURPOSE!"; + _crashOnShutdown = true; + quit(); +} + void Application::overrideEntry(){ _overrideEntry = true; } diff --git a/interface/src/Application.h b/interface/src/Application.h index 913671473d..cd867598c0 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -252,7 +252,7 @@ public: NodeToOctreeSceneStats* getOcteeSceneStats() { return &_octreeServerSceneStats; } virtual controller::ScriptingInterface* getControllerScriptingInterface() { return _controllerScriptingInterface; } - virtual void registerScriptEngineWithApplicationServices(ScriptEnginePointer scriptEngine) override; + virtual void registerScriptEngineWithApplicationServices(const ScriptEnginePointer& scriptEngine) override; virtual void copyCurrentViewFrustum(ViewFrustum& viewOut) const override { copyDisplayViewFrustum(viewOut); } virtual QThread* getMainThread() override { return thread(); } @@ -497,6 +497,9 @@ public slots: bool gpuTextureMemSizeStable(); void showUrlHandler(const QUrl& url); + // used to test "shutdown" crash annotation. + void crashOnShutdown(); + private slots: void onDesktopRootItemCreated(QQuickItem* qmlContext); void onDesktopRootContextCreated(QQmlContext* qmlContext); @@ -844,5 +847,7 @@ private: bool _overrideEntry { false }; VisionSqueeze _visionSqueeze; + + bool _crashOnShutdown { false }; }; #endif // hifi_Application_h diff --git a/interface/src/MacHelper.cpp b/interface/src/MacHelper.cpp deleted file mode 100755 index 8527f02918..0000000000 --- a/interface/src/MacHelper.cpp +++ /dev/null @@ -1,58 +0,0 @@ -// -// MacHelper.h -// interface/src -// -// Created by Howard Stearns -// Copyright 2019 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#include "InterfaceLogging.h" -#include "MacHelper.h" -#include - -#ifdef Q_OS_MAC -#include -#include - -// The type definitions in these variables come from IOKit, which includes a definition of Duration that conflicts with ours. -// So... we include these definitions here rather than in the .h, as the .h is included in Application.cpp which -// uses Duration. -static io_connect_t root_port; -static IONotificationPortRef notifyPortRef; -static io_object_t notifierObject; -static void* refCon; - -static void sleepHandler(void* refCon, io_service_t service, natural_t messageType, void* messageArgument) { - if (messageType == kIOMessageSystemHasPoweredOn) { - qCInfo(interfaceapp) << "Waking up from sleep or hybernation."; - QMetaObject::invokeMethod(DependencyManager::get().data(), "noteAwakening", Qt::QueuedConnection); - } -} -#endif - -MacHelper::MacHelper() { -#ifdef Q_OS_MAC - root_port = IORegisterForSystemPower(refCon, ¬ifyPortRef, sleepHandler, ¬ifierObject); - if (root_port == 0) { - qCWarning(interfaceapp) << "IORegisterForSystemPower failed"; - return; - } - CFRunLoopAddSource(CFRunLoopGetCurrent(), - IONotificationPortGetRunLoopSource(notifyPortRef), - kCFRunLoopCommonModes); -#endif -} - -MacHelper::~MacHelper() { -#ifdef Q_OS_MAC - CFRunLoopRemoveSource(CFRunLoopGetCurrent(), - IONotificationPortGetRunLoopSource(notifyPortRef), - kCFRunLoopCommonModes); - IODeregisterForSystemPower(¬ifierObject); - IOServiceClose(root_port); - IONotificationPortDestroy(notifyPortRef); -#endif -} diff --git a/interface/src/MacHelper.h b/interface/src/MacHelper.h deleted file mode 100755 index 52ad4d3e55..0000000000 --- a/interface/src/MacHelper.h +++ /dev/null @@ -1,21 +0,0 @@ -// -// MacHelper.h -// interface/src -// -// Created by Howard Stearns -// Copyright 2019 High Fidelity, Inc. -// -// Distributed under the Apache License, Version 2.0. -// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html -// - -#pragma once - -#include "DependencyManager.h" - -class MacHelper : public Dependency { -public: - MacHelper(); - ~MacHelper(); -}; - diff --git a/interface/src/Menu.cpp b/interface/src/Menu.cpp index 4cf78c23ee..8c6292681b 100644 --- a/interface/src/Menu.cpp +++ b/interface/src/Menu.cpp @@ -763,6 +763,8 @@ Menu::Menu() { action = addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashNewFaultThreaded); connect(action, &QAction::triggered, qApp, []() { std::thread(crash::newFault).join(); }); + addActionToQMenuAndActionHash(crashMenu, MenuOption::CrashOnShutdown, 0, qApp, SLOT(crashOnShutdown())); + // Developer > Show Statistics addCheckableActionToQMenuAndActionHash(developerMenu, MenuOption::Stats, 0, true); diff --git a/interface/src/Menu.h b/interface/src/Menu.h index 70687786a9..7dff264adc 100644 --- a/interface/src/Menu.h +++ b/interface/src/Menu.h @@ -72,6 +72,7 @@ namespace MenuOption { const QString CrashNullDereferenceThreaded = "Null Dereference (threaded)"; const QString CrashAbort = "Abort"; const QString CrashAbortThreaded = "Abort (threaded)"; + const QString CrashOnShutdown = "Crash During Shutdown"; const QString CrashOutOfBoundsVectorAccess = "Out of Bounds Vector Access"; const QString CrashOutOfBoundsVectorAccessThreaded = "Out of Bounds Vector Access (threaded)"; const QString CrashNewFault = "New Fault"; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 0ff2e055b7..4d1c20010c 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -733,7 +733,7 @@ void MyAvatar::update(float deltaTime) { // When needed and ready, arrange to check and fix. _physicsSafetyPending = false; if (_goToSafe) { - safeLanding(_goToPosition); // no-op if already safe + safeLanding(_goToPosition); // no-op if safeLanding logic determines already safe } } @@ -2733,24 +2733,22 @@ void MyAvatar::nextAttitude(glm::vec3 position, glm::quat orientation) { void MyAvatar::harvestResultsFromPhysicsSimulation(float deltaTime) { glm::vec3 position; glm::quat orientation; - if (_characterController.isEnabledAndReady()) { + if (_characterController.isEnabledAndReady() && !(_characterController.needsSafeLandingSupport() || _goToPending)) { _characterController.getPositionAndOrientation(position, orientation); + setWorldVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); } else { position = getWorldPosition(); orientation = getWorldOrientation(); + if (_characterController.needsSafeLandingSupport() && !_goToPending) { + _characterController.resetStuckCounter(); + _physicsSafetyPending = true; + _goToSafe = true; + _goToPosition = position; + } + setWorldVelocity(getWorldVelocity() + _characterController.getFollowVelocity()); } nextAttitude(position, orientation); _bodySensorMatrix = _follow.postPhysicsUpdate(*this, _bodySensorMatrix); - - if (_characterController.isEnabledAndReady()) { - setWorldVelocity(_characterController.getLinearVelocity() + _characterController.getFollowVelocity()); - if (_characterController.isStuck()) { - _physicsSafetyPending = true; - _goToPosition = getWorldPosition(); - } - } else { - setWorldVelocity(getWorldVelocity() + _characterController.getFollowVelocity()); - } } QString MyAvatar::getScriptedMotorFrame() const { @@ -3588,6 +3586,8 @@ void MyAvatar::updateActionMotor(float deltaTime) { float speedGrowthTimescale = 2.0f; float speedIncreaseFactor = 1.8f * _walkSpeedScalar; motorSpeed *= 1.0f + glm::clamp(deltaTime / speedGrowthTimescale, 0.0f, 1.0f) * speedIncreaseFactor; + // use feedback from CharacterController to prevent tunneling under high motorspeed + motorSpeed *= _characterController.getCollisionBrakeAttenuationFactor(); const float maxBoostSpeed = sensorToWorldScale * MAX_BOOST_SPEED; if (_isPushing) { @@ -5865,8 +5865,13 @@ bool MyAvatar::endReaction(QString reactionName) { int reactionIndex = beginEndReactionNameToIndex(reactionName); if (reactionIndex >= 0 && reactionIndex < (int)NUM_AVATAR_BEGIN_END_REACTIONS) { std::lock_guard guard(_reactionLock); - _reactionEnabledRefCounts[reactionIndex]--; - return true; + if (_reactionEnabledRefCounts[reactionIndex] > 0) { + _reactionEnabledRefCounts[reactionIndex]--; + return true; + } else { + _reactionEnabledRefCounts[reactionIndex] = 0; + return false; + } } return false; } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index bb6f036533..0108fb5eda 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -154,6 +154,7 @@ class MyAvatar : public Avatar { * * @property {Vec3} qmlPosition - A synonym for position for use by QML. * + * @property {Vec3} feetPosition - The position of the avatar's feet. * @property {boolean} shouldRenderLocally=true - If true then your avatar is rendered for you in Interface, * otherwise it is not rendered for you (but it is still rendered for other users). * @property {Vec3} motorVelocity=Vec3.ZERO - The target velocity of your avatar to be achieved by a scripted motor. @@ -340,6 +341,7 @@ class MyAvatar : public Avatar { Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition) QVector3D getQmlPosition() { auto p = getWorldPosition(); return QVector3D(p.x, p.y, p.z); } + Q_PROPERTY(glm::vec3 feetPosition READ getWorldFeetPosition WRITE goToFeetLocation) Q_PROPERTY(bool shouldRenderLocally READ getShouldRenderLocally WRITE setShouldRenderLocally) Q_PROPERTY(glm::vec3 motorVelocity READ getScriptedMotorVelocity WRITE setScriptedMotorVelocity) Q_PROPERTY(float motorTimescale READ getScriptedMotorTimescale WRITE setScriptedMotorTimescale) @@ -1938,9 +1940,8 @@ public slots: * @param {boolean} [shouldFaceLocation=false] - Set to true to position the avatar a short distance away from * the new position and orientate the avatar to face the position. */ - void goToFeetLocation(const glm::vec3& newPosition, - bool hasOrientation, const glm::quat& newOrientation, - bool shouldFaceLocation); + void goToFeetLocation(const glm::vec3& newPosition, bool hasOrientation = false, + const glm::quat& newOrientation = glm::quat(), bool shouldFaceLocation = false); /**jsdoc * Moves the avatar to a new position and/or orientation in the domain. diff --git a/interface/src/avatar/MyCharacterController.cpp b/interface/src/avatar/MyCharacterController.cpp index 2f59b70592..3a25721528 100755 --- a/interface/src/avatar/MyCharacterController.cpp +++ b/interface/src/avatar/MyCharacterController.cpp @@ -36,10 +36,10 @@ MyCharacterController::MyCharacterController(std::shared_ptr avatar) { MyCharacterController::~MyCharacterController() { } -void MyCharacterController::setDynamicsWorld(btDynamicsWorld* world) { - CharacterController::setDynamicsWorld(world); - if (world && _rigidBody) { - initRayShotgun(world); +void MyCharacterController::addToWorld() { + CharacterController::addToWorld(); + if (_rigidBody) { + initRayShotgun(_physicsEngine->getDynamicsWorld()); } } @@ -204,7 +204,7 @@ bool MyCharacterController::testRayShotgun(const glm::vec3& position, const glm: } int32_t MyCharacterController::computeCollisionMask() const { - int32_t collisionMask = BULLET_COLLISION_MASK_MY_AVATAR; + int32_t collisionMask = BULLET_COLLISION_MASK_MY_AVATAR; if (_collisionless && _collisionlessAllowed) { collisionMask = BULLET_COLLISION_MASK_COLLISIONLESS; } else if (!_collideWithOtherAvatars) { @@ -216,16 +216,17 @@ int32_t MyCharacterController::computeCollisionMask() const { void MyCharacterController::handleChangedCollisionMask() { if (_pendingFlags & PENDING_FLAG_UPDATE_COLLISION_MASK) { // ATM the easiest way to update collision groups/masks is to remove/re-add the RigidBody - if (_dynamicsWorld) { - _dynamicsWorld->removeRigidBody(_rigidBody); - int32_t collisionMask = computeCollisionMask(); - _dynamicsWorld->addRigidBody(_rigidBody, BULLET_COLLISION_GROUP_MY_AVATAR, collisionMask); - } + // but we don't do it here. Instead we set some flags to remind us to do it later. + _pendingFlags |= (PENDING_FLAG_REMOVE_FROM_SIMULATION | PENDING_FLAG_ADD_TO_SIMULATION); _pendingFlags &= ~PENDING_FLAG_UPDATE_COLLISION_MASK; updateCurrentGravity(); } } +bool MyCharacterController::needsSafeLandingSupport() const { + return _isStuck && _numStuckSubsteps >= NUM_SUBSTEPS_FOR_SAFE_LANDING_RETRY; +} + btConvexHullShape* MyCharacterController::computeShape() const { // HACK: the avatar collides using convex hull with a collision margin equal to // the old capsule radius. Two points define a capsule and additional points are @@ -447,12 +448,12 @@ public: std::vector MyCharacterController::rayTest(const btVector3& origin, const btVector3& direction, const btScalar& length, const QVector& jointsToExclude) const { std::vector foundAvatars; - if (_dynamicsWorld) { + if (_physicsEngine) { btVector3 end = origin + length * direction; DetailedRayResultCallback rayCallback = DetailedRayResultCallback(); rayCallback.m_flags |= btTriangleRaycastCallback::kF_KeepUnflippedNormal; rayCallback.m_flags |= btTriangleRaycastCallback::kF_UseSubSimplexConvexCastRaytest; - _dynamicsWorld->rayTest(origin, end, rayCallback); + _physicsEngine->getDynamicsWorld()->rayTest(origin, end, rayCallback); if (rayCallback.m_hitFractions.size() > 0) { foundAvatars.reserve(rayCallback.m_hitFractions.size()); for (int32_t i = 0; i < rayCallback.m_hitFractions.size(); i++) { diff --git a/interface/src/avatar/MyCharacterController.h b/interface/src/avatar/MyCharacterController.h index 7ddcf94f67..eefcc92637 100644 --- a/interface/src/avatar/MyCharacterController.h +++ b/interface/src/avatar/MyCharacterController.h @@ -26,7 +26,7 @@ public: explicit MyCharacterController(std::shared_ptr avatar); ~MyCharacterController (); - void setDynamicsWorld(btDynamicsWorld* world) override; + void addToWorld() override; void updateShapeIfNecessary() override; // Sweeping a convex shape through the physics simulation can be expensive when the obstacles are too @@ -69,9 +69,10 @@ public: int32_t computeCollisionMask() const override; void handleChangedCollisionMask() override; - bool _collideWithOtherAvatars{ true }; void setCollideWithOtherAvatars(bool collideWithOtherAvatars) { _collideWithOtherAvatars = collideWithOtherAvatars; } + bool needsSafeLandingSupport() const; + protected: void initRayShotgun(const btCollisionWorld* world); void updateMassProperties() override; @@ -88,6 +89,7 @@ protected: btScalar _density { 1.0f }; std::vector _detailedMotionStates; + bool _collideWithOtherAvatars { true }; }; #endif // hifi_MyCharacterController_h diff --git a/interface/src/avatar/MySkeletonModel.cpp b/interface/src/avatar/MySkeletonModel.cpp index 6080911dd9..9d5693da33 100755 --- a/interface/src/avatar/MySkeletonModel.cpp +++ b/interface/src/avatar/MySkeletonModel.cpp @@ -337,10 +337,7 @@ void MySkeletonModel::updateRig(float deltaTime, glm::mat4 parentTransform) { eyeParams.modelTranslation = getTranslation(); eyeParams.leftEyeJointIndex = _rig.indexOfJoint("LeftEye"); eyeParams.rightEyeJointIndex = _rig.indexOfJoint("RightEye"); - - if (_owningAvatar->getHasProceduralEyeFaceMovement()) { - _rig.updateFromEyeParameters(eyeParams); - } + _rig.updateFromEyeParameters(eyeParams); updateFingers(); } diff --git a/interface/src/main.cpp b/interface/src/main.cpp index f6ce1ec8ad..a8710159b4 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -70,7 +70,7 @@ int main(int argc, const char* argv[]) { } QCommandLineParser parser; - parser.setApplicationDescription("High Fidelity Interface"); + parser.setApplicationDescription("High Fidelity"); QCommandLineOption versionOption = parser.addVersionOption(); QCommandLineOption helpOption = parser.addHelpOption(); diff --git a/interface/src/scripting/Audio.cpp b/interface/src/scripting/Audio.cpp index e3e6138744..ba392f0cd1 100644 --- a/interface/src/scripting/Audio.cpp +++ b/interface/src/scripting/Audio.cpp @@ -101,6 +101,10 @@ void Audio::setMutedDesktop(bool isMuted) { } } }); + if (!isMuted && _settingsLoaded && !_pushingToTalk) { + // If the user is not pushing to talk and muted is changed to false, disable Push-To-Talk. Settings also need to be loaded. + setPTTDesktop(isMuted); + } if (changed) { if (!isHMD) { emit mutedChanged(isMuted); @@ -128,6 +132,10 @@ void Audio::setMutedHMD(bool isMuted) { } } }); + if (!isMuted && _settingsLoaded && !_pushingToTalk) { + // If the user is not pushing to talk and muted is changed to false, disable Push-To-Talk. Settings also need to be loaded. + setPTTHMD(isMuted); + } if (changed) { if (isHMD) { emit mutedChanged(isMuted); @@ -187,9 +195,9 @@ void Audio::setPTTDesktop(bool enabled) { _pttDesktop = enabled; } }); - if (enabled || _settingsLoaded) { + if (enabled && _settingsLoaded) { // Set to default behavior (muted for Desktop) on Push-To-Talk disable or when enabled. Settings also need to be loaded. - setMutedDesktop(true); + setMutedDesktop(enabled); } if (changed) { emit pushToTalkChanged(enabled); @@ -211,7 +219,7 @@ void Audio::setPTTHMD(bool enabled) { _pttHMD = enabled; } }); - if (enabled || _settingsLoaded) { + if (enabled && _settingsLoaded) { // Set to default behavior (unmuted for HMD) on Push-To-Talk disable or muted for when PTT is enabled. setMutedHMD(enabled); } diff --git a/interface/src/scripting/DesktopScriptingInterface.cpp b/interface/src/scripting/DesktopScriptingInterface.cpp index a1c8e4fc6c..874b3fa42d 100644 --- a/interface/src/scripting/DesktopScriptingInterface.cpp +++ b/interface/src/scripting/DesktopScriptingInterface.cpp @@ -52,6 +52,9 @@ static const QVariantMap DOCK_AREA { { "RIGHT", DockArea::RIGHT } }; +DesktopScriptingInterface::DesktopScriptingInterface(QObject* parent, bool restricted) + : QObject(parent), _restricted(restricted) { } + int DesktopScriptingInterface::getWidth() { QSize size = qApp->getWindow()->windowHandle()->screen()->virtualSize(); return size.width(); @@ -128,7 +131,7 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindow(const QString& return nullptr; } - return new InteractiveWindow(sourceUrl, properties); + return new InteractiveWindow(sourceUrl, properties, _restricted); } InteractiveWindowPointer DesktopScriptingInterface::createWindowOnThread(const QString& sourceUrl, const QVariantMap& properties, QThread* targetThread) { @@ -139,7 +142,7 @@ InteractiveWindowPointer DesktopScriptingInterface::createWindowOnThread(const Q if (!urlValidator(sourceUrl)) { return nullptr; } - InteractiveWindowPointer window = new InteractiveWindow(sourceUrl, properties); + InteractiveWindowPointer window = new InteractiveWindow(sourceUrl, properties, _restricted); window->moveToThread(targetThread); return window; } diff --git a/interface/src/scripting/DesktopScriptingInterface.h b/interface/src/scripting/DesktopScriptingInterface.h index 525fd7c803..e562a32543 100644 --- a/interface/src/scripting/DesktopScriptingInterface.h +++ b/interface/src/scripting/DesktopScriptingInterface.h @@ -54,6 +54,8 @@ class DesktopScriptingInterface : public QObject, public Dependency { Q_PROPERTY(int CLOSE_BUTTON_HIDES READ flagCloseButtonHides CONSTANT FINAL) public: + DesktopScriptingInterface(QObject* parent= nullptr, bool restricted = false); + /**jsdoc * Sets the opacity of the HUD surface. * @function Desktop.setHUDAlpha @@ -106,6 +108,7 @@ private: static QVariantMap getDockArea(); Q_INVOKABLE static QVariantMap getPresentationMode(); + const bool _restricted; }; diff --git a/interface/src/scripting/SettingsScriptingInterface.cpp b/interface/src/scripting/SettingsScriptingInterface.cpp index afafe1a350..c47d8e1da1 100644 --- a/interface/src/scripting/SettingsScriptingInterface.cpp +++ b/interface/src/scripting/SettingsScriptingInterface.cpp @@ -35,8 +35,12 @@ QVariant SettingsScriptingInterface::getValue(const QString& setting, const QVar } void SettingsScriptingInterface::setValue(const QString& setting, const QVariant& value) { + if (getValue(setting) == value) { + return; + } // Make a deep-copy of the string. // Dangling pointers can occur with QStrings that are implicitly shared from a QScriptEngine. QString deepCopy = QString::fromUtf16(setting.utf16()); Setting::Handle(deepCopy).set(value); + emit valueChanged(setting, value); } diff --git a/interface/src/scripting/SettingsScriptingInterface.h b/interface/src/scripting/SettingsScriptingInterface.h index 25a8b627cb..ff00299fd0 100644 --- a/interface/src/scripting/SettingsScriptingInterface.h +++ b/interface/src/scripting/SettingsScriptingInterface.h @@ -64,6 +64,9 @@ public slots: * print("Value: " + (typeof value) + " " + JSON.stringify(value)); // object {"x":0,"y":10,"z":0} */ void setValue(const QString& setting, const QVariant& value); + +signals: + void valueChanged(const QString& setting, const QVariant& value); }; #endif // hifi_SettingsScriptingInterface_h diff --git a/interface/src/scripting/WindowScriptingInterface.cpp b/interface/src/scripting/WindowScriptingInterface.cpp index 80c0479bd7..f4aa36e2f4 100644 --- a/interface/src/scripting/WindowScriptingInterface.cpp +++ b/interface/src/scripting/WindowScriptingInterface.cpp @@ -56,6 +56,7 @@ WindowScriptingInterface::WindowScriptingInterface() { }); connect(qApp->getWindow(), &MainWindow::windowGeometryChanged, this, &WindowScriptingInterface::onWindowGeometryChanged); + connect(qApp->getWindow(), &MainWindow::windowMinimizedChanged, this, &WindowScriptingInterface::minimizedChanged); connect(qApp, &Application::interstitialModeChanged, [this] (bool interstitialMode) { emit interstitialModeChanged(interstitialMode); }); diff --git a/interface/src/scripting/WindowScriptingInterface.h b/interface/src/scripting/WindowScriptingInterface.h index 010f3bcca2..6207b22cb8 100644 --- a/interface/src/scripting/WindowScriptingInterface.h +++ b/interface/src/scripting/WindowScriptingInterface.h @@ -814,6 +814,17 @@ signals: */ void geometryChanged(QRect geometry); + + /**jsdoc + * Triggered when "minimized" state of the Interface window changes. + * @function Window.minimizedChanged + * @param {bool} isMinimized - true if the Interface window is now minimized; false otherwise. + * @returns {Signal} + * + * Window.minimizedChanged.connect(onWindowMinimizedChanged); + */ + void minimizedChanged(bool isMinimized); + private: QString getPreviousBrowseLocation() const; void setPreviousBrowseLocation(const QString& location); diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index c0f3e82b29..e63c392a47 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -18,6 +18,9 @@ #include #include +#include +#include +#include #include #include #include @@ -109,6 +112,10 @@ void InteractiveWindow::forwardKeyReleaseEvent(int key, int modifiers) { QCoreApplication::postEvent(QCoreApplication::instance(), event); } +void InteractiveWindow::emitMainWindowResizeEvent() { + emit qApp->getWindow()->windowGeometryChanged(qApp->getWindow()->geometry()); +} + /**jsdoc * A set of properties used when creating an InteractiveWindow. * @typedef {object} InteractiveWindow.Properties @@ -130,7 +137,7 @@ void InteractiveWindow::forwardKeyReleaseEvent(int key, int modifiers) { * Set at window creation. Possible flag values are provided as {@link Desktop|Desktop.ALWAYS_ON_TOP} and {@link Desktop|Desktop.CLOSE_BUTTON_HIDES}. * Additional flag values can be found on Qt's website at https://doc.qt.io/qt-5/qt.html#WindowType-enum. */ -InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties) { +InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties, bool restricted) { InteractiveWindowPresentationMode presentationMode = InteractiveWindowPresentationMode::Native; if (properties.contains(PRESENTATION_MODE_PROPERTY)) { @@ -215,16 +222,25 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap Qt::QueuedConnection); QObject::connect(rootItem, SIGNAL(keyReleaseEvent(int, int)), this, SLOT(forwardKeyReleaseEvent(int, int)), Qt::QueuedConnection); - emit mainWindow->windowGeometryChanged(qApp->getWindow()->geometry()); } }); + QObject::connect(_dockWidget.get(), SIGNAL(onResizeEvent()), this, SLOT(emitMainWindowResizeEvent())); + _dockWidget->setSource(QUrl(sourceUrl)); + _dockWidget->setObjectName("DockedWidget"); mainWindow->addDockWidget(dockArea, _dockWidget.get()); } else { - auto offscreenUi = DependencyManager::get(); - // Build the event bridge and wrapper on the main thread - offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, [&](QQmlContext* context, QObject* object) { + auto contextInitLambda = [&](QQmlContext* context) { + // If the restricted flag is on, the web content will not be able to access local files + ContextAwareProfile::restrictContext(context, restricted); +#if !defined(Q_OS_ANDROID) + FileTypeProfile::registerWithContext(context); + HFWebEngineProfile::registerWithContext(context); +#endif + }; + + auto objectInitLambda = [&](QQmlContext* context, QObject* object) { _qmlWindowProxy = std::shared_ptr(new QmlWindowProxy(object, nullptr), qmlWindowProxyDeleter); context->setContextProperty(EVENT_BRIDGE_PROPERTY, _interactiveWindowProxy.get()); if (properties.contains(ADDITIONAL_FLAGS_PROPERTY)) { @@ -278,8 +294,13 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap if (!KNOWN_SCHEMES.contains(sourceURL.scheme(), Qt::CaseInsensitive)) { sourceURL = QUrl::fromLocalFile(sourceURL.toString()).toString(); } + object->setObjectName("InteractiveWindow"); object->setProperty(SOURCE_PROPERTY, sourceURL); - }); + }; + auto offscreenUi = DependencyManager::get(); + + // Build the event bridge and wrapper on the main thread + offscreenUi->loadInNewContext(CONTENT_WINDOW_QML, objectInitLambda, contextInitLambda); } } @@ -296,7 +317,9 @@ void InteractiveWindow::sendToQml(const QVariant& message) { QMetaObject::invokeMethod(rootItem, "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); } } else { - QMetaObject::invokeMethod(_qmlWindowProxy->getQmlWindow(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); + if (_qmlWindowProxy) { + QMetaObject::invokeMethod(_qmlWindowProxy->getQmlWindow(), "fromScript", Qt::QueuedConnection, Q_ARG(QVariant, message)); + } } } diff --git a/interface/src/ui/InteractiveWindow.h b/interface/src/ui/InteractiveWindow.h index 67f0a9ea2e..70077a27b0 100644 --- a/interface/src/ui/InteractiveWindow.h +++ b/interface/src/ui/InteractiveWindow.h @@ -126,7 +126,7 @@ class InteractiveWindow : public QObject { Q_PROPERTY(int presentationMode READ getPresentationMode WRITE setPresentationMode) public: - InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties); + InteractiveWindow(const QString& sourceUrl, const QVariantMap& properties, bool restricted); ~InteractiveWindow(); private: @@ -319,6 +319,7 @@ protected slots: void forwardKeyPressEvent(int key, int modifiers); void forwardKeyReleaseEvent(int key, int modifiers); + void emitMainWindowResizeEvent(); private: std::shared_ptr _qmlWindowProxy; diff --git a/launchers/darwin/CMakeLists.txt b/launchers/darwin/CMakeLists.txt index f71976960e..b495893b97 100644 --- a/launchers/darwin/CMakeLists.txt +++ b/launchers/darwin/CMakeLists.txt @@ -45,6 +45,8 @@ set(src_files src/CustomUI.m src/NSTask+NSTaskExecveAdditions.h src/NSTask+NSTaskExecveAdditions.m + src/HQDefaults.h + src/HQDefaults.m src/main.mm nib/Window.xib nib/SplashScreen.xib @@ -69,7 +71,7 @@ set_target_properties(${this_target} PROPERTIES set(MACOSX_BUNDLE_ICON_FILE "interface.icns") function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE) - if (NOT DEFINED ${_RESULT_NAME}) + if (NOT DEFINED ${_RESULT_NAME}) if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "") set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE) else() @@ -89,8 +91,14 @@ if ("${LAUNCHER_HMAC_SECRET}" STREQUAL "") message(FATAL_ERROR "LAUNCHER_HMAC_SECRET is not set") endif() +# Development environments don't set BUILD_VERSION. Let 0 mean a development version. +if(NOT BUILD_VERSION) + set(BUILD_VERSION 0) +endif() + target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_HMAC_SECRET="${LAUNCHER_HMAC_SECRET}") target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}") +target_compile_definitions(${PROJECT_NAME} PRIVATE USER_AGENT_STRING="HQLauncher/${BUILD_VERSION} \(macOS\)") file(GLOB NIB_FILES "nib/*.xib") @@ -112,6 +120,10 @@ add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CMAKE_SOURCE_DIR}/images "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/") +add_custom_command(TARGET ${PROJECT_NAME} PRE_BUILD + COMMAND ${CMAKE_COMMAND} -E copy + ${CMAKE_SOURCE_DIR}/data/HQDefaults.plist "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/") + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND updater COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/updater" "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/") @@ -140,3 +152,17 @@ set(DMG_SUBFOLDER_ICON "${CMAKE_SOURCE_DIR}/cmake/installer/install-folder.rsrc" set(CPACK_GENERATOR "DragNDrop") include(CPack) + +include(FindXCTest) + +include_directories(${CMAKE_SOURCE_DIR}/src) + +xctest_add_bundle(HQLauncherTests HQLauncher + ${CMAKE_SOURCE_DIR}/src/HQDefaults.m + ${CMAKE_SOURCE_DIR}/tests/HQDefaultsTests.m + ${CMAKE_SOURCE_DIR}/tests/Info.plist +) + +set_target_properties(HQLauncherTests PROPERTIES + MACOSX_BUNDLE_INFO_PLIST ${CMAKE_SOURCE_DIR}/tests/Info.plist + ) diff --git a/launchers/darwin/data/HQDefaults.plist b/launchers/darwin/data/HQDefaults.plist new file mode 100644 index 0000000000..98a29664c9 --- /dev/null +++ b/launchers/darwin/data/HQDefaults.plist @@ -0,0 +1,14 @@ + + + + + + name + thunderURL + defaultValue + https://thunder.highfidelity.com + environmentVariable + HIFI_THUNDER_URL + + + diff --git a/launchers/darwin/nib/DisplayNameScreen.xib b/launchers/darwin/nib/DisplayNameScreen.xib index 9862c3773b..77cc3b2222 100644 --- a/launchers/darwin/nib/DisplayNameScreen.xib +++ b/launchers/darwin/nib/DisplayNameScreen.xib @@ -81,7 +81,7 @@