diff --git a/assignment-client/src/Agent.cpp b/assignment-client/src/Agent.cpp index 15c6471b3d..f1bdaaad12 100644 --- a/assignment-client/src/Agent.cpp +++ b/assignment-client/src/Agent.cpp @@ -827,10 +827,6 @@ void Agent::processAgentAvatarAudio() { void Agent::aboutToFinish() { setIsAvatar(false);// will stop timers for sending identity packets - if (_scriptEngine) { - _scriptEngine->stop(); - } - // our entity tree is going to go away so tell that to the EntityScriptingInterface DependencyManager::get()->setEntityTree(nullptr); @@ -843,7 +839,6 @@ void Agent::aboutToFinish() { // destroy all other created dependencies DependencyManager::destroy(); - DependencyManager::destroy(); DependencyManager::destroy(); DependencyManager::destroy(); @@ -862,3 +857,11 @@ void Agent::aboutToFinish() { _encoder = nullptr; } } + +void Agent::stop() { + if (_scriptEngine) { + _scriptEngine->stop(); + } else { + setFinished(true); + } +} diff --git a/assignment-client/src/Agent.h b/assignment-client/src/Agent.h index b55dea89be..7d47c8e713 100644 --- a/assignment-client/src/Agent.h +++ b/assignment-client/src/Agent.h @@ -67,6 +67,8 @@ public slots: void setIsAvatar(bool isAvatar); bool isAvatar() const { return _isAvatar; } + Q_INVOKABLE virtual void stop() override; + private slots: void requestScript(); void scriptRequestFinished(); diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 07f1eb7e5e..5d1f725ab4 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -46,6 +46,14 @@ "default": "40102", "type": "int", "advanced": true + }, + { + "name": "enable_packet_verification", + "label": "Enable Packet Verification", + "help": "Enable secure checksums on communication that uses the High Fidelity protocol. Increases security with possibly a small performance penalty.", + "default": true, + "type": "checkbox", + "advanced": true } ] }, diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 86a9a58058..93db267617 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -630,6 +630,7 @@ bool DomainServer::isPacketVerified(const udt::Packet& packet) { void DomainServer::setupNodeListAndAssignments() { const QString CUSTOM_LOCAL_PORT_OPTION = "metaverse.local_port"; + static const QString ENABLE_PACKET_AUTHENTICATION = "metaverse.enable_packet_verification"; QVariant localPortValue = _settingsManager.valueOrDefaultValueForKeyPath(CUSTOM_LOCAL_PORT_OPTION); int domainServerPort = localPortValue.toInt(); @@ -696,6 +697,9 @@ void DomainServer::setupNodeListAndAssignments() { } } + bool isAuthEnabled = _settingsManager.valueOrDefaultValueForKeyPath(ENABLE_PACKET_AUTHENTICATION).toBool(); + nodeList->setAuthenticatePackets(isAuthEnabled); + connect(nodeList.data(), &LimitedNodeList::nodeAdded, this, &DomainServer::nodeAdded); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &DomainServer::nodeKilled); @@ -1133,7 +1137,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, const Hif extendedHeaderStream << node->getUUID(); extendedHeaderStream << node->getLocalID(); extendedHeaderStream << node->getPermissions(); - + extendedHeaderStream << limitedNodeList->getAuthenticatePackets(); auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader); // always send the node their own UUID back diff --git a/interface/resources/qml/QmlWebWindow.qml b/interface/resources/qml/QmlWebWindow.qml index d73a574081..8c4d6145ec 100644 --- a/interface/resources/qml/QmlWebWindow.qml +++ b/interface/resources/qml/QmlWebWindow.qml @@ -62,6 +62,7 @@ Windows.ScrollingWindow { url: "about:blank" anchors.fill: parent focus: true + profile: HFWebEngineProfile; property string userScriptUrl: "" diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index 0a45feb61f..135c1379e2 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -299,7 +299,7 @@ Item { anchors.fill: stackView id: controllerPrefereneces objectName: "TabletControllerPreferences" - showCategories: [( (HMD.active) ? "VR Movement" : "Movement"), "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"] + showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"] categoryProperties: { "VR Movement" : { "User real-world height (meters)" : { "anchors.right" : "undefined" }, diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index ba7c15ad33..9b2498138e 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -6619,11 +6619,12 @@ 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) - scriptEngine->registerFunction("OverlayWebWindow", QmlWebWindowClass::constructor); + scriptEngine->registerFunction("OverlayWebWindow", clientScript ? QmlWebWindowClass::constructor : QmlWebWindowClass::restricted_constructor); #endif - scriptEngine->registerFunction("OverlayWindow", QmlWindowClass::constructor); - scriptEngine->registerFunction("QmlFragment", QmlFragmentClass::constructor); + scriptEngine->registerFunction("QmlFragment", clientScript ? QmlFragmentClass::constructor : QmlFragmentClass::restricted_constructor); scriptEngine->registerGlobalObject("Menu", MenuScriptingInterface::getInstance()); scriptEngine->registerGlobalObject("DesktopPreviewProvider", DependencyManager::get().data()); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index fab512f787..dbafd06611 100644 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -412,9 +412,6 @@ void AvatarManager::clearOtherAvatars() { while (avatarIterator != _avatarHash.end()) { auto avatar = std::static_pointer_cast(avatarIterator.value()); if (avatar != _myAvatar) { - if (avatar->isInScene()) { - avatar->removeFromScene(avatar, scene, transaction); - } handleRemovedAvatar(avatar); avatarIterator = _avatarHash.erase(avatarIterator); } else { diff --git a/interface/src/avatar/AvatarMotionState.cpp b/interface/src/avatar/AvatarMotionState.cpp index 50c715b14a..07e6b3f6b0 100644 --- a/interface/src/avatar/AvatarMotionState.cpp +++ b/interface/src/avatar/AvatarMotionState.cpp @@ -19,6 +19,7 @@ AvatarMotionState::AvatarMotionState(AvatarSharedPointer avatar, const btCollisionShape* shape) : ObjectMotionState(shape), _avatar(avatar) { assert(_avatar); _type = MOTIONSTATE_TYPE_AVATAR; + cacheShapeDiameter(); } void AvatarMotionState::handleEasyChanges(uint32_t& flags) { @@ -57,9 +58,6 @@ PhysicsMotionType AvatarMotionState::computePhysicsMotionType() const { const btCollisionShape* AvatarMotionState::computeNewShape() { ShapeInfo shapeInfo; std::static_pointer_cast(_avatar)->computeShapeInfo(shapeInfo); - glm::vec3 halfExtents = shapeInfo.getHalfExtents(); - halfExtents.y = 0.0f; - _diameter = 2.0f * glm::length(halfExtents); return getShapeManager()->getShape(shapeInfo); } @@ -98,6 +96,10 @@ void AvatarMotionState::setWorldTransform(const btTransform& worldTrans) { btVector3 velocity = glmToBullet(getObjectLinearVelocity()) + (1.0f / SPRING_TIMESCALE) * offsetToTarget; _body->setLinearVelocity(velocity); _body->setAngularVelocity(glmToBullet(getObjectAngularVelocity())); + // slam its rotation + btTransform newTransform = worldTrans; + newTransform.setRotation(glmToBullet(getObjectRotation())); + _body->setWorldTransform(newTransform); } } @@ -141,7 +143,10 @@ glm::vec3 AvatarMotionState::getObjectLinearVelocity() const { // virtual glm::vec3 AvatarMotionState::getObjectAngularVelocity() const { - return _avatar->getWorldAngularVelocity(); + // HACK: avatars use a capusle collision shape and their angularVelocity in the local simulation is unimportant. + // Therefore, as optimization toward support for larger crowds we ignore it and return zero. + //return _avatar->getWorldAngularVelocity(); + return glm::vec3(0.0f); } // virtual @@ -174,3 +179,28 @@ float AvatarMotionState::getMass() const { return std::static_pointer_cast(_avatar)->computeMass(); } +void AvatarMotionState::cacheShapeDiameter() { + if (_shape) { + // shape is capsuleY + btVector3 aabbMin, aabbMax; + btTransform transform; + transform.setIdentity(); + _shape->getAabb(transform, aabbMin, aabbMax); + _diameter = (aabbMax - aabbMin).getX(); + } else { + _diameter = 0.0f; + } +} + +void AvatarMotionState::setRigidBody(btRigidBody* body) { + ObjectMotionState::setRigidBody(body); + if (_body) { + // remove angular dynamics from this body + _body->setAngularFactor(0.0f); + } +} + +void AvatarMotionState::setShape(const btCollisionShape* shape) { + ObjectMotionState::setShape(shape); + cacheShapeDiameter(); +} diff --git a/interface/src/avatar/AvatarMotionState.h b/interface/src/avatar/AvatarMotionState.h index 9228641b25..a458704b1a 100644 --- a/interface/src/avatar/AvatarMotionState.h +++ b/interface/src/avatar/AvatarMotionState.h @@ -74,6 +74,10 @@ public: friend class Avatar; protected: + void setRigidBody(btRigidBody* body) override; + void setShape(const btCollisionShape* shape) override; + void cacheShapeDiameter(); + // the dtor had been made protected to force the compiler to verify that it is only // ever called by the Avatar class dtor. ~AvatarMotionState(); diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index 98fbd8fea2..3f738ea4cb 100755 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -1135,7 +1135,6 @@ void MyAvatar::saveData() { settings.setValue("collisionSoundURL", _collisionSoundURL); settings.setValue("useSnapTurn", _useSnapTurn); settings.setValue("userHeight", getUserHeight()); - settings.setValue("flyingDesktop", getFlyingDesktopPref()); settings.setValue("flyingHMD", getFlyingHMDPref()); settings.endGroup(); @@ -1289,7 +1288,6 @@ void MyAvatar::loadData() { // Flying preferences must be loaded before calling setFlyingEnabled() Setting::Handle firstRunVal { Settings::firstRun, true }; - setFlyingDesktopPref(firstRunVal.get() ? true : settings.value("flyingDesktop").toBool()); setFlyingHMDPref(firstRunVal.get() ? false : settings.value("flyingHMD").toBool()); setFlyingEnabled(getFlyingEnabled()); diff --git a/interface/src/scripting/Audio.h b/interface/src/scripting/Audio.h index 8d16b06995..4b8eb6aabc 100644 --- a/interface/src/scripting/Audio.h +++ b/interface/src/scripting/Audio.h @@ -26,7 +26,7 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { SINGLETON_DEPENDENCY /**jsdoc - * The Audio API features tools to help control audio contexts and settings. + * The Audio API provides facilities to interact with audio inputs and outputs and to play sounds. * * @namespace Audio * @@ -35,14 +35,23 @@ class Audio : public AudioScriptingInterface, protected ReadWriteLockable { * @hifi-server-entity * @hifi-assignment-client * - * @property {boolean} muted - * @property {boolean} noiseReduction - * @property {number} inputVolume - * @property {number} inputLevel Read-only. - * @property {string} context Read-only. - * @property {} devices Read-only. + * @property {boolean} muted - true if the audio input is muted, otherwise false. + * @property {boolean} noiseReduction - true if noise reduction is enabled, otherwise false. When + * enabled, the input audio signal is blocked (fully attenuated) when it falls below an adaptive threshold set just + * above the noise floor. + * @property {number} inputLevel - The loudness of the audio input, range 0.0 (no sound) – + * 1.0 (the onset of clipping). Read-only. + * @property {number} inputVolume - Adjusts the volume of the input audio; range 0.01.0. + * If set to a value, the resulting value depends on the input device: for example, the volume can't be changed on some + * devices, and others might only support values of 0.0 and 1.0. + * @property {boolean} isStereoInput - true if the input audio is being used in stereo, otherwise + * false. Some devices do not support stereo, in which case the value is always false. + * @property {string} context - The current context of the audio: either "Desktop" or "HMD". + * Read-only. + * @property {object} devices Read-only. Deprecated: This property is deprecated and will be + * removed. */ - + Q_PROPERTY(bool muted READ isMuted WRITE setMuted NOTIFY mutedChanged) Q_PROPERTY(bool noiseReduction READ noiseReductionEnabled WRITE enableNoiseReduction NOTIFY noiseReductionChanged) Q_PROPERTY(float inputVolume READ getInputVolume WRITE setInputVolume NOTIFY inputVolumeChanged) @@ -69,45 +78,91 @@ public: /**jsdoc * @function Audio.setInputDevice - * @param {} device + * @param {object} device * @param {boolean} isHMD + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void setInputDevice(const QAudioDeviceInfo& device, bool isHMD); /**jsdoc * @function Audio.setOutputDevice - * @param {} device + * @param {object} device * @param {boolean} isHMD + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void setOutputDevice(const QAudioDeviceInfo& device, bool isHMD); /**jsdoc + * Enable or disable reverberation. Reverberation is done by the client, on the post-mix audio. The reverberation options + * come from either the domain's audio zone if used — configured on the server — or as scripted by + * {@link Audio.setReverbOptions|setReverbOptions}. * @function Audio.setReverb - * @param {boolean} enable - */ + * @param {boolean} enable - true to enable reverberation, false to disable. + * @example Enable reverberation for a short while. + * var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav"); + * var injector; + * var injectorOptions = { + * position: MyAvatar.position + * }; + * + * Script.setTimeout(function () { + * print("Reverb OFF"); + * Audio.setReverb(false); + * injector = Audio.playSound(sound, injectorOptions); + * }, 1000); + * + * Script.setTimeout(function () { + * var reverbOptions = new AudioEffectOptions(); + * reverbOptions.roomSize = 100; + * Audio.setReverbOptions(reverbOptions); + * print("Reverb ON"); + * Audio.setReverb(true); + * }, 4000); + * + * Script.setTimeout(function () { + * print("Reverb OFF"); + * Audio.setReverb(false); + * }, 8000); */ Q_INVOKABLE void setReverb(bool enable); /**jsdoc + * Configure reverberation options. Use {@link Audio.setReverb|setReverb} to enable or disable reverberation. * @function Audio.setReverbOptions - * @param {AudioEffectOptions} options + * @param {AudioEffectOptions} options - The reverberation options. */ Q_INVOKABLE void setReverbOptions(const AudioEffectOptions* options); /**jsdoc + * Starts making an audio recording of the audio being played in-world (i.e., not local-only audio) to a file in WAV format. * @function Audio.startRecording - * @param {string} filename - * @returns {boolean} + * @param {string} filename - The path and name of the file to make the recording in. Should have a .wav + * extension. The file is overwritten if it already exists. + * @returns {boolean} true if the specified file could be opened and audio recording has started, otherwise + * false. + * @example Make a 10 second audio recording. + * var filename = File.getTempDir() + "/audio.wav"; + * if (Audio.startRecording(filename)) { + * Script.setTimeout(function () { + * Audio.stopRecording(); + * print("Audio recording made in: " + filename); + * }, 10000); + * + * } else { + * print("Could not make an audio recording in: " + filename); + * } */ Q_INVOKABLE bool startRecording(const QString& filename); /**jsdoc + * Finish making an audio recording started with {@link Audio.startRecording|startRecording}. * @function Audio.stopRecording */ Q_INVOKABLE void stopRecording(); /**jsdoc + * Check whether an audio recording is currently being made. * @function Audio.getRecording - * @returns {boolean} + * @returns {boolean} true if an audio recording is currently being made, otherwise false. */ Q_INVOKABLE bool getRecording(); @@ -116,40 +171,54 @@ signals: /**jsdoc * @function Audio.nop * @returns {Signal} + * @deprecated This signal is deprecated and will be removed. */ void nop(); /**jsdoc + * Triggered when the audio input is muted or unmuted. * @function Audio.mutedChanged - * @param {boolean} isMuted + * @param {boolean} isMuted - true if the audio input is muted, otherwise false. * @returns {Signal} + * @example Report when audio input is muted or unmuted + * Audio.mutedChanged.connect(function (isMuted) { + * print("Audio muted: " + isMuted); + * }); */ void mutedChanged(bool isMuted); /**jsdoc + * Triggered when the audio input noise reduction is enabled or disabled. * @function Audio.noiseReductionChanged - * @param {boolean} isEnabled + * @param {boolean} isEnabled - true if audio input noise reduction is enabled, otherwise false. * @returns {Signal} */ void noiseReductionChanged(bool isEnabled); /**jsdoc + * Triggered when the input audio volume changes. * @function Audio.inputVolumeChanged - * @param {number} volume + * @param {number} volume - The requested volume to be applied to the audio input, range 0.0 – + * 1.0. The resulting value of Audio.inputVolume depends on the capabilities of the device: + * for example, the volume can't be changed on some devices, and others might only support values of 0.0 + * and 1.0. * @returns {Signal} */ void inputVolumeChanged(float volume); /**jsdoc + * Triggered when the input audio level changes. * @function Audio.inputLevelChanged - * @param {number} level + * @param {number} level - The loudness of the input audio, range 0.0 (no sound) – 1.0 (the + * onset of clipping). * @returns {Signal} */ void inputLevelChanged(float level); /**jsdoc + * Triggered when the current context of the audio changes. * @function Audio.contextChanged - * @param {string} context + * @param {string} context - The current context of the audio: either "Desktop" or "HMD". * @returns {Signal} */ void contextChanged(const QString& context); @@ -158,7 +227,7 @@ public slots: /**jsdoc * @function Audio.onContextChanged - * @returns {Signal} + * @deprecated This function is deprecated and will be removed. */ void onContextChanged(); diff --git a/interface/src/ui/PreferencesDialog.cpp b/interface/src/ui/PreferencesDialog.cpp index 50a4d17cae..682dad74e8 100644 --- a/interface/src/ui/PreferencesDialog.cpp +++ b/interface/src/ui/PreferencesDialog.cpp @@ -266,42 +266,6 @@ void setupPreferences() { preferences->addPreference(new SliderPreference(FACE_TRACKING, "Eye Deflection", getter, setter)); } - static const QString MOVEMENT{ "Movement" }; - { - - static const QString movementsControlChannel = QStringLiteral("Hifi-Advanced-Movement-Disabler"); - auto getter = [=]()->bool { return myAvatar->useAdvancedMovementControls(); }; - auto setter = [=](bool value) { myAvatar->setUseAdvancedMovementControls(value); }; - preferences->addPreference(new CheckPreference(MOVEMENT, - QStringLiteral("Advanced movement for hand controllers"), - getter, setter)); - } - { - auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; - auto setter = [=](int value) { myAvatar->setSnapTurn(value == 0); }; - auto preference = new RadioButtonsPreference(MOVEMENT, "Snap turn / Smooth turn", getter, setter); - QStringList items; - items << "Snap turn" << "Smooth turn"; - preference->setItems(items); - preferences->addPreference(preference); - } - { - auto getter = [=]()->float { return myAvatar->getUserHeight(); }; - auto setter = [=](float value) { myAvatar->setUserHeight(value); }; - auto preference = new SpinnerPreference(MOVEMENT, "User real-world height (meters)", getter, setter); - preference->setMin(1.0f); - preference->setMax(2.2f); - preference->setDecimals(3); - preference->setStep(0.001f); - preferences->addPreference(preference); - } - { - auto preference = new ButtonPreference(MOVEMENT, "RESET SENSORS", [] { - qApp->resetSensors(); - }); - preferences->addPreference(preference); - } - static const QString VR_MOVEMENT{ "VR Movement" }; { @@ -315,7 +279,7 @@ void setupPreferences() { { auto getter = [=]()->bool { return myAvatar->getFlyingHMDPref(); }; auto setter = [=](bool value) { myAvatar->setFlyingHMDPref(value); }; - preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping", getter, setter)); + preferences->addPreference(new CheckPreference(VR_MOVEMENT, "Flying & jumping (HMD)", getter, setter)); } { auto getter = [=]()->int { return myAvatar->getSnapTurn() ? 0 : 1; }; diff --git a/libraries/audio/src/AudioEffectOptions.cpp b/libraries/audio/src/AudioEffectOptions.cpp index edb0ff52ae..873ce7df87 100644 --- a/libraries/audio/src/AudioEffectOptions.cpp +++ b/libraries/audio/src/AudioEffectOptions.cpp @@ -59,28 +59,29 @@ static void setOption(QScriptValue arguments, const QString name, float defaultV } /**jsdoc + * Reverberation options that can be used to initialize an {@link AudioEffectOptions} object when created. * @typedef {object} AudioEffectOptions.ReverbOptions - * @property {number} bandwidth - * @property {number} preDelay - * @property {number} lateDelay - * @property {number} reverbTime - * @property {number} earlyDiffusion - * @property {number} lateDiffusion - * @property {number} roomSize - * @property {number} density - * @property {number} bassMult - * @property {number} bassFreq - * @property {number} highGain - * @property {number} highFreq - * @property {number} modRate - * @property {number} modDepth - * @property {number} earlyGain - * @property {number} lateGain - * @property {number} earlyMixLeft - * @property {number} earlyMixRight - * @property {number} lateMixLeft - * @property {number} lateMixRight - * @property {number} wetDryMix + * @property {number} bandwidth=10000 - The corner frequency (Hz) of the low-pass filter at reverb input. + * @property {number} preDelay=20 - The delay (milliseconds) between dry signal and the onset of early reflections. + * @property {number} lateDelay=0 - The delay (milliseconds) between early reflections and the onset of reverb tail. + * @property {number} reverbTime=2 - The time (seconds) for the reverb tail to decay by 60dB, also known as RT60. + * @property {number} earlyDiffusion=100 - Adjusts the buildup of echo density in the early reflections, normally 100%. + * @property {number} lateDiffusion=100 - Adjusts the buildup of echo density in the reverb tail, normally 100%. + * @property {number} roomSize=50 - The apparent room size, from small (0%) to large (100%). + * @property {number} density=100 - Adjusts the echo density in the reverb tail, normally 100%. + * @property {number} bassMult=1.5 - Adjusts the bass-frequency reverb time, as multiple of reverbTime. + * @property {number} bassFreq=250 - The crossover frequency (Hz) for the onset of bassMult. + * @property {number} highGain=-6 - Reduces the high-frequency reverb time, as attenuation (dB). + * @property {number} highFreq=3000 - The crossover frequency (Hz) for the onset of highGain. + * @property {number} modRate=2.3 - The rate of modulation (Hz) of the LFO-modulated delay lines. + * @property {number} modDepth=50 - The depth of modulation (percent) of the LFO-modulated delay lines. + * @property {number} earlyGain=0 - Adjusts the relative level (dB) of the early reflections. + * @property {number} lateGain=0 - Adjusts the relative level (dB) of the reverb tail. + * @property {number} earlyMixLeft=20 - The apparent distance of the source (percent) in the early reflections. + * @property {number} earlyMixRight=20 - The apparent distance of the source (percent) in the early reflections. + * @property {number} lateMixLeft=90 - The apparent distance of the source (percent) in the reverb tail. + * @property {number} lateMixRight=90 - The apparent distance of the source (percent) in the reverb tail. + * @property {number} wetDryMix=50 - Adjusts the wet/dry ratio, from completely dry (0%) to completely wet (100%). */ AudioEffectOptions::AudioEffectOptions(QScriptValue arguments) { setOption(arguments, BANDWIDTH_HANDLE, BANDWIDTH_DEFAULT, _bandwidth); diff --git a/libraries/audio/src/AudioEffectOptions.h b/libraries/audio/src/AudioEffectOptions.h index 1afd4e21be..4bc7957142 100644 --- a/libraries/audio/src/AudioEffectOptions.h +++ b/libraries/audio/src/AudioEffectOptions.h @@ -16,35 +16,39 @@ #include /**jsdoc + * Audio effect options used by the {@link Audio} API. + * + *

Create using new AudioEffectOptions(reverbOptions).

+ * * @class AudioEffectOptions - * @param {AudioEffectOptions.ReverbOptions} [reverbOptions=null] + * @param {AudioEffectOptions.ReverbOptions} [reverbOptions=null] - Reverberation options. * * @hifi-interface * @hifi-client-entity * @hifi-server-entity * @hifi-assignment-client * - * @property {number} bandwidth=10000 - * @property {number} preDelay=20 - * @property {number} lateDelay=0 - * @property {number} reverbTime=2 - * @property {number} earlyDiffusion=100 - * @property {number} lateDiffusion=100 - * @property {number} roomSize=50 - * @property {number} density=100 - * @property {number} bassMult=1.5 - * @property {number} bassFreq=250 - * @property {number} highGain=-6 - * @property {number} highFreq=3000 - * @property {number} modRate=2.3 - * @property {number} modDepth=50 - * @property {number} earlyGain=0 - * @property {number} lateGain=0 - * @property {number} earlyMixLeft=20 - * @property {number} earlyMixRight=20 - * @property {number} lateMixLeft=90 - * @property {number} lateMixRight=90 - * @property {number} wetDryMix=50 + * @property {number} bandwidth=10000 - The corner frequency (Hz) of the low-pass filter at reverb input. + * @property {number} preDelay=20 - The delay (milliseconds) between dry signal and the onset of early reflections. + * @property {number} lateDelay=0 - The delay (milliseconds) between early reflections and the onset of reverb tail. + * @property {number} reverbTime=2 - The time (seconds) for the reverb tail to decay by 60dB, also known as RT60. + * @property {number} earlyDiffusion=100 - Adjusts the buildup of echo density in the early reflections, normally 100%. + * @property {number} lateDiffusion=100 - Adjusts the buildup of echo density in the reverb tail, normally 100%. + * @property {number} roomSize=50 - The apparent room size, from small (0%) to large (100%). + * @property {number} density=100 - Adjusts the echo density in the reverb tail, normally 100%. + * @property {number} bassMult=1.5 - Adjusts the bass-frequency reverb time, as multiple of reverbTime. + * @property {number} bassFreq=250 - The crossover frequency (Hz) for the onset of bassMult. + * @property {number} highGain=-6 - Reduces the high-frequency reverb time, as attenuation (dB). + * @property {number} highFreq=3000 - The crossover frequency (Hz) for the onset of highGain. + * @property {number} modRate=2.3 - The rate of modulation (Hz) of the LFO-modulated delay lines. + * @property {number} modDepth=50 - The depth of modulation (percent) of the LFO-modulated delay lines. + * @property {number} earlyGain=0 - Adjusts the relative level (dB) of the early reflections. + * @property {number} lateGain=0 - Adjusts the relative level (dB) of the reverb tail. + * @property {number} earlyMixLeft=20 - The apparent distance of the source (percent) in the early reflections. + * @property {number} earlyMixRight=20 - The apparent distance of the source (percent) in the early reflections. + * @property {number} lateMixLeft=90 - The apparent distance of the source (percent) in the reverb tail. + * @property {number} lateMixRight=90 - The apparent distance of the source (percent) in the reverb tail. + * @property {number} wetDryMix=50 - Adjusts the wet/dry ratio, from completely dry (0%) to completely wet (100%). */ class AudioEffectOptions : public QObject { diff --git a/libraries/audio/src/AudioInjectorOptions.cpp b/libraries/audio/src/AudioInjectorOptions.cpp index 2f82cae137..0f4ab7ff42 100644 --- a/libraries/audio/src/AudioInjectorOptions.cpp +++ b/libraries/audio/src/AudioInjectorOptions.cpp @@ -45,6 +45,23 @@ QScriptValue injectorOptionsToScriptValue(QScriptEngine* engine, const AudioInje return obj; } +/**jsdoc + * Configures how an audio injector plays its audio. + * @typedef {object} AudioInjector.AudioInjectorOptions + * @property {Vec3} position=Vec3.ZERO - The position in the domain to play the sound. + * @property {Quat} orientation=Quat.IDENTITY - The orientation in the domain to play the sound in. + * @property {number} volume=1.0 - Playback volume, between 0.0 and 1.0. + * @property {number} pitch=1.0 - Alter the pitch of the sound, within +/- 2 octaves. The value is the relative sample rate to + * resample the sound at, range 0.062516.0. A value of 0.0625 lowers the + * pitch by 2 octaves; 1.0 is no change in pitch; 16.0 raises the pitch by 2 octaves. + * @property {boolean} loop=false - If true, the sound is played repeatedly until playback is stopped. + * @property {number} secondOffset=0 - Starts playback from a specified time (seconds) within the sound file, ≥ + * 0. + * @property {boolean} localOnly=false - IF true, the sound is played back locally on the client rather than to + * others via the audio mixer. + * @property {boolean} ignorePenumbra=false - Deprecated: This property is deprecated and will be + * removed. + */ void injectorOptionsFromScriptValue(const QScriptValue& object, AudioInjectorOptions& injectorOptions) { if (!object.isObject()) { qWarning() << "Audio injector options is not an object."; diff --git a/libraries/audio/src/Sound.h b/libraries/audio/src/Sound.h index 061c4a2417..348600e4ae 100644 --- a/libraries/audio/src/Sound.h +++ b/libraries/audio/src/Sound.h @@ -79,6 +79,14 @@ private: typedef QSharedPointer SharedSoundPointer; /**jsdoc + * An audio resource, created by {@link SoundCache.getSound}, to be played back using {@link Audio.playSound}. + *

Supported formats:

+ *
    + *
  • WAV: 16-bit uncompressed WAV at any sample rate, with 1 (mono), 2(stereo), or 4 (ambisonic) channels.
  • + *
  • MP3: Mono or stereo, at any sample rate.
  • + *
  • RAW: 48khz 16-bit mono or stereo. Filename must include ".stereo" to be interpreted as stereo.
  • + *
+ * * @class SoundObject * * @hifi-interface @@ -86,8 +94,9 @@ typedef QSharedPointer SharedSoundPointer; * @hifi-server-entity * @hifi-assignment-client * - * @property {boolean} downloaded - * @property {number} duration + * @property {boolean} downloaded - true if the sound has been downloaded and is ready to be played, otherwise + * false. + * @property {number} duration - The duration of the sound, in seconds. */ class SoundScriptingInterface : public QObject { Q_OBJECT @@ -103,6 +112,7 @@ public: float getDuration() { return _sound->getDuration(); } /**jsdoc + * Triggered when the sound has been downloaded and is ready to be played. * @function SoundObject.ready * @returns {Signal} */ diff --git a/libraries/audio/src/SoundCacheScriptingInterface.h b/libraries/audio/src/SoundCacheScriptingInterface.h index 1995cef026..c985e8c211 100644 --- a/libraries/audio/src/SoundCacheScriptingInterface.h +++ b/libraries/audio/src/SoundCacheScriptingInterface.h @@ -48,9 +48,11 @@ public: SoundCacheScriptingInterface(); /**jsdoc + * Loads the content of an audio file into a {@link SoundObject}, ready for playback by {@link Audio.playSound}. * @function SoundCache.getSound - * @param {string} url - * @returns {SoundObject} + * @param {string} url - The URL of the audio file to load — Web, ATP, or file. See {@link SoundObject} for supported + * formats. + * @returns {SoundObject} The sound ready for playback. */ Q_INVOKABLE SharedSoundPointer getSound(const QUrl& url); }; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 502874fbfb..b6b2369703 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -328,9 +328,10 @@ bool LimitedNodeList::packetSourceAndHashMatchAndTrackBandwidth(const udt::Packe if (sourceNode) { bool verifiedPacket = !PacketTypeEnum::getNonVerifiedPackets().contains(headerType); - bool ignoreVerification = isDomainServer() && PacketTypeEnum::getDomainIgnoredVerificationPackets().contains(headerType); + bool verificationEnabled = !(isDomainServer() && PacketTypeEnum::getDomainIgnoredVerificationPackets().contains(headerType)) + && _useAuthentication; - if (verifiedPacket && !ignoreVerification) { + if (verifiedPacket && verificationEnabled) { QByteArray packetHeaderHash = NLPacket::verificationHashInHeader(packet); QByteArray expectedHash; @@ -383,7 +384,7 @@ void LimitedNodeList::fillPacketHeader(const NLPacket& packet, HMACAuth* hmacAut packet.writeSourceID(getSessionLocalID()); } - if (hmacAuth + if (_useAuthentication && hmacAuth && !PacketTypeEnum::getNonSourcedPackets().contains(packet.getType()) && !PacketTypeEnum::getNonVerifiedPackets().contains(packet.getType())) { packet.writeVerificationHash(*hmacAuth); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index 05374bbfbb..cffc49521a 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -307,6 +307,8 @@ public: bool isPacketVerifiedWithSource(const udt::Packet& packet, Node* sourceNode = nullptr); bool isPacketVerified(const udt::Packet& packet) { return isPacketVerifiedWithSource(packet); } + void setAuthenticatePackets(bool useAuthentication) { _useAuthentication = useAuthentication; } + bool getAuthenticatePackets() const { return _useAuthentication; } static void makeSTUNRequestPacket(char* stunRequestPacket); @@ -394,6 +396,7 @@ protected: HifiSockAddr _publicSockAddr; HifiSockAddr _stunSockAddr { STUN_SERVER_HOSTNAME, STUN_SERVER_PORT }; bool _hasTCPCheckedLocalSocket { false }; + bool _useAuthentication { true }; PacketReceiver* _packetReceiver; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 2ce734dd26..dd351ef940 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -665,6 +665,10 @@ void NodeList::processDomainServerList(QSharedPointer message) NodePermissions newPermissions; packetStream >> newPermissions; setPermissions(newPermissions); + // Is packet authentication enabled? + bool isAuthenticated; + packetStream >> isAuthenticated; + setAuthenticatePackets(isAuthenticated); // pull each node in the packet while (packetStream.device()->pos() < message->getSize()) { diff --git a/libraries/networking/src/ThreadedAssignment.cpp b/libraries/networking/src/ThreadedAssignment.cpp index 9a69d9b3d8..13d4e0bf8b 100644 --- a/libraries/networking/src/ThreadedAssignment.cpp +++ b/libraries/networking/src/ThreadedAssignment.cpp @@ -120,7 +120,7 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() { if (_numQueuedCheckIns >= MAX_SILENT_DOMAIN_SERVER_CHECK_INS) { qCDebug(networking) << "At least" << MAX_SILENT_DOMAIN_SERVER_CHECK_INS << "have been queued without a response from domain-server" << "Stopping the current assignment"; - setFinished(true); + stop(); } else { auto nodeList = DependencyManager::get(); QMetaObject::invokeMethod(nodeList.data(), "sendDomainServerCheckIn"); @@ -132,5 +132,5 @@ void ThreadedAssignment::checkInWithDomainServerOrExit() { void ThreadedAssignment::domainSettingsRequestFailed() { qCDebug(networking) << "Failed to retreive settings object from domain-server. Bailing on assignment."; - setFinished(true); + stop(); } diff --git a/libraries/networking/src/ThreadedAssignment.h b/libraries/networking/src/ThreadedAssignment.h index a1737641c1..e76533b2a1 100644 --- a/libraries/networking/src/ThreadedAssignment.h +++ b/libraries/networking/src/ThreadedAssignment.h @@ -24,7 +24,6 @@ public: ThreadedAssignment(ReceivedMessage& message); ~ThreadedAssignment() { stop(); } - void setFinished(bool isFinished); virtual void aboutToFinish() { }; void addPacketStatsAndSendStatsPacket(QJsonObject statsObject); @@ -43,6 +42,7 @@ signals: protected: void commonInit(const QString& targetName, NodeType_t nodeType); + void setFinished(bool isFinished); bool _isFinished; QTimer _domainServerTimer; diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index d0bd7fc872..82e9820509 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -27,7 +27,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::StunResponse: return 17; case PacketType::DomainList: - return static_cast(DomainListVersion::GetMachineFingerprintFromUUIDSupport); + return static_cast(DomainListVersion::AuthenticationOptional); case PacketType::EntityAdd: case PacketType::EntityClone: case PacketType::EntityEdit: diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 6b252a8d49..64c5bfe534 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -315,7 +315,8 @@ enum class DomainListVersion : PacketVersion { PrePermissionsGrid = 18, PermissionsGrid, GetUsernameFromUUIDSupport, - GetMachineFingerprintFromUUIDSupport + GetMachineFingerprintFromUUIDSupport, + AuthenticationOptional }; enum class AudioVersion : PacketVersion { diff --git a/libraries/physics/src/ObjectMotionState.cpp b/libraries/physics/src/ObjectMotionState.cpp index 310cf7cec1..c814140930 100644 --- a/libraries/physics/src/ObjectMotionState.cpp +++ b/libraries/physics/src/ObjectMotionState.cpp @@ -64,9 +64,9 @@ ShapeManager* ObjectMotionState::getShapeManager() { } ObjectMotionState::ObjectMotionState(const btCollisionShape* shape) : - _shape(shape), _lastKinematicStep(worldSimulationStep) { + setShape(shape); } ObjectMotionState::~ObjectMotionState() { diff --git a/libraries/physics/src/ObjectMotionState.h b/libraries/physics/src/ObjectMotionState.h index 7439c1c38d..74173c3f47 100644 --- a/libraries/physics/src/ObjectMotionState.h +++ b/libraries/physics/src/ObjectMotionState.h @@ -175,13 +175,13 @@ protected: virtual void setMotionType(PhysicsMotionType motionType); void updateCCDConfiguration(); - void setRigidBody(btRigidBody* body); + virtual void setRigidBody(btRigidBody* body); virtual void setShape(const btCollisionShape* shape); MotionStateType _type { MOTIONSTATE_TYPE_INVALID }; // type of MotionState PhysicsMotionType _motionType { MOTION_TYPE_STATIC }; // type of motion: KINEMATIC, DYNAMIC, or STATIC - const btCollisionShape* _shape; + const btCollisionShape* _shape { nullptr }; btRigidBody* _body { nullptr }; float _density { 1.0f }; diff --git a/libraries/qml/src/qml/OffscreenSurface.cpp b/libraries/qml/src/qml/OffscreenSurface.cpp index ea6f1ce324..bbffee2407 100644 --- a/libraries/qml/src/qml/OffscreenSurface.cpp +++ b/libraries/qml/src/qml/OffscreenSurface.cpp @@ -40,7 +40,8 @@ static QSize clampSize(const QSize& qsize, uint32_t maxDimension) { return fromGlm(clampSize(toGlm(qsize), maxDimension)); } -const QmlContextObjectCallback OffscreenSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*, QQuickItem*) {}; +const QmlContextObjectCallback OffscreenSurface::DEFAULT_CONTEXT_OBJECT_CALLBACK = [](QQmlContext*, QQuickItem*) {}; +const QmlContextCallback OffscreenSurface::DEFAULT_CONTEXT_CALLBACK = [](QQmlContext*) {}; void OffscreenSurface::initializeEngine(QQmlEngine* engine) { } @@ -266,8 +267,8 @@ void OffscreenSurface::load(const QUrl& qmlSource, bool createNewContext, const loadInternal(qmlSource, createNewContext, nullptr, callback); } -void OffscreenSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback) { - load(qmlSource, true, callback); +void OffscreenSurface::loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback, const QmlContextCallback& contextCallback) { + loadInternal(qmlSource, true, nullptr, callback, contextCallback); } void OffscreenSurface::load(const QUrl& qmlSource, const QmlContextObjectCallback& callback) { @@ -281,7 +282,8 @@ void OffscreenSurface::load(const QString& qmlSourceFile, const QmlContextObject void OffscreenSurface::loadInternal(const QUrl& qmlSource, bool createNewContext, QQuickItem* parent, - const QmlContextObjectCallback& callback) { + const QmlContextObjectCallback& callback, + const QmlContextCallback& contextCallback) { PROFILE_RANGE_EX(app, "OffscreenSurface::loadInternal", 0xffff00ff, 0, { std::make_pair("url", qmlSource.toDisplayString()) }); if (QThread::currentThread() != thread()) { qFatal("Called load on a non-surface thread"); @@ -310,6 +312,7 @@ void OffscreenSurface::loadInternal(const QUrl& qmlSource, } auto targetContext = contextForUrl(finalQmlSource, parent, createNewContext); + contextCallback(targetContext); QQmlComponent* qmlComponent; { PROFILE_RANGE(app, "new QQmlComponent"); diff --git a/libraries/qml/src/qml/OffscreenSurface.h b/libraries/qml/src/qml/OffscreenSurface.h index 555f2ee6a4..b3539e7709 100644 --- a/libraries/qml/src/qml/OffscreenSurface.h +++ b/libraries/qml/src/qml/OffscreenSurface.h @@ -37,13 +37,15 @@ namespace impl { class SharedObject; } +using QmlContextCallback = ::std::function; using QmlContextObjectCallback = ::std::function; class OffscreenSurface : public QObject { Q_OBJECT public: - static const QmlContextObjectCallback DEFAULT_CONTEXT_CALLBACK; + static const QmlContextObjectCallback DEFAULT_CONTEXT_OBJECT_CALLBACK; + static const QmlContextCallback DEFAULT_CONTEXT_CALLBACK; using TextureAndFence = std::pair; using MouseTranslator = std::function; @@ -85,10 +87,15 @@ public: Q_INVOKABLE void load(const QUrl& qmlSource, QQuickItem* parent, const QJSValue& callback); // For use from C++ - Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void load(const QUrl& qmlSource, bool createNewContext, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void load(const QString& qmlSourceFile, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); - Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_CALLBACK); + Q_INVOKABLE void load(const QUrl& qmlSource, const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK); + Q_INVOKABLE void load(const QUrl& qmlSource, + bool createNewContext, + const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK); + Q_INVOKABLE void load(const QString& qmlSourceFile, + const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK); + Q_INVOKABLE void loadInNewContext(const QUrl& qmlSource, + const QmlContextObjectCallback& callback = DEFAULT_CONTEXT_OBJECT_CALLBACK, + const QmlContextCallback& contextCallback = DEFAULT_CONTEXT_CALLBACK); public slots: virtual void onFocusObjectChanged(QObject* newFocus) {} @@ -103,19 +110,21 @@ protected: virtual void initializeEngine(QQmlEngine* engine); virtual void loadInternal(const QUrl& qmlSource, - bool createNewContext, - QQuickItem* parent, - const QmlContextObjectCallback& callback) final; + bool createNewContext, + QQuickItem* parent, + const QmlContextObjectCallback& callback, + const QmlContextCallback& contextCallback = DEFAULT_CONTEXT_CALLBACK) final; virtual void finishQmlLoad(QQmlComponent* qmlComponent, - QQmlContext* qmlContext, - QQuickItem* parent, - const QmlContextObjectCallback& onQmlLoadedCallback) final; + QQmlContext* qmlContext, + QQuickItem* parent, + const QmlContextObjectCallback& onQmlLoadedCallback) final; virtual void onRootCreated() {} virtual void onItemCreated(QQmlContext* context, QQuickItem* newItem) {} virtual void onRootContextCreated(QQmlContext* qmlContext) {} virtual QQmlContext* contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext); + private: MouseTranslator _mouseTranslator{ [](const QPointF& p) { return p.toPoint(); } }; friend class hifi::qml::impl::SharedObject; diff --git a/libraries/script-engine/src/AudioScriptingInterface.h b/libraries/script-engine/src/AudioScriptingInterface.h index 20ca977da1..1220a9b769 100644 --- a/libraries/script-engine/src/AudioScriptingInterface.h +++ b/libraries/script-engine/src/AudioScriptingInterface.h @@ -23,6 +23,7 @@ class AudioScriptingInterface : public QObject, public Dependency { Q_OBJECT SINGLETON_DEPENDENCY + // JSDoc for property is in Audio.h. Q_PROPERTY(bool isStereoInput READ isStereoInput WRITE setStereoInput NOTIFY isStereoInputChanged) public: @@ -35,91 +36,121 @@ protected: // these methods are protected to stop C++ callers from calling, but invokable from script /**jsdoc + * Starts playing — "injecting" — the content of an audio file. The sound is played globally (sent to the audio + * mixer) so that everyone hears it, unless the injectorOptions has localOnly set to + * true in which case only the client hears the sound played. No sound is played if sent to the audio mixer + * but the client is not connected to an audio mixer. The {@link AudioInjector} object returned by the function can be used + * to control the playback and get information about its current state. * @function Audio.playSound - * @param {} sound - * @param {} [injectorOptions=null] - * @returns {object} + * @param {SoundObject} sound - The content of an audio file, loaded using {@link SoundCache.getSound}. See + * {@link SoundObject} for supported formats. + * @param {AudioInjector.AudioInjectorOptions} [injectorOptions={}] - Audio injector configuration. + * @returns {AudioInjector} The audio injector that plays the audio file. + * @example Play a sound. + * var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav"); + * var injector; + * var injectorOptions = { + * position: MyAvatar.position + * }; + * + * Script.setTimeout(function () { // Give the sound time to load. + * injector = Audio.playSound(sound, injectorOptions); + * }, 1000); */ Q_INVOKABLE ScriptAudioInjector* playSound(SharedSoundPointer sound, const AudioInjectorOptions& injectorOptions = AudioInjectorOptions()); /**jsdoc + * Start playing the content of an audio file, locally (isn't sent to the audio mixer). This is the same as calling + * {@link Audio.playSound} with {@link AudioInjector.AudioInjectorOptions} localOnly set true and + * the specified position. * @function Audio.playSystemSound - * @param {} sound - * @param {} position - * @returns {object} + * @param {SoundObject} sound - The content of an audio file, loaded using {@link SoundCache.getSound}. See + * {@link SoundObject} for supported formats. + * @param {Vec3} position - The position in the domain to play the sound. + * @returns {AudioInjector} The audio injector that plays the audio file. */ // FIXME: there is no way to play a positionless sound Q_INVOKABLE ScriptAudioInjector* playSystemSound(SharedSoundPointer sound, const QVector3D& position); /**jsdoc + * Set whether or not the audio input should be used in stereo. If the audio input does not support stereo then setting a + * value of true has no effect. * @function Audio.setStereoInput - * @param {boolean} stereo + * @param {boolean} stereo - true if the audio input should be used in stereo, otherwise false. */ Q_INVOKABLE void setStereoInput(bool stereo); /**jsdoc + * Get whether or not the audio input is used in stereo. * @function Audio.isStereoInput - * @returns {boolean} + * @returns {boolean} true if the audio input is used in stereo, otherwise false. */ Q_INVOKABLE bool isStereoInput(); signals: /**jsdoc - * The client has been muted by the mixer. + * Triggered when the client is muted by the mixer because their loudness value for the noise background has reached the + * threshold set for the domain in the server settings. * @function Audio.mutedByMixer * @returns {Signal} */ void mutedByMixer(); /**jsdoc - * The entire environment has been muted by the mixer. + * Triggered when the client is muted by the mixer because they're within a certain radius (50m) of someone who requested + * the mute through Developer > Audio > Mute Environment. * @function Audio.environmentMuted * @returns {Signal} */ void environmentMuted(); /**jsdoc - * The client has received its first packet from the audio mixer. + * Triggered when the client receives its first packet from the audio mixer. * @function Audio.receivedFirstPacket * @returns {Signal} */ void receivedFirstPacket(); /**jsdoc - * The client has been disconnected from the audio mixer. + * Triggered when the client is disconnected from the audio mixer. * @function Audio.disconnected * @returns {Signal} */ void disconnected(); /**jsdoc - * The noise gate has opened. + * Triggered when the noise gate is opened: the input audio signal is no longer blocked (fully attenuated) because it has + * risen above an adaptive threshold set just above the noise floor. Only occurs if Audio.noiseReduction is + * true. * @function Audio.noiseGateOpened * @returns {Signal} */ void noiseGateOpened(); /**jsdoc - * The noise gate has closed. + * Triggered when the noise gate is closed: the input audio signal is blocked (fully attenuated) because it has fallen + * below an adaptive threshold set just above the noise floor. Only occurs if Audio.noiseReduction is + * true. * @function Audio.noiseGateClosed * @returns {Signal} */ void noiseGateClosed(); /**jsdoc - * A frame of mic input audio has been received and processed. + * Triggered when a frame of audio input is processed. * @function Audio.inputReceived - * @param {} inputSamples + * @param {Int16Array} inputSamples - The audio input processed. * @returns {Signal} */ void inputReceived(const QByteArray& inputSamples); /**jsdoc - * @function Audio.isStereoInputChanged - * @param {boolean} isStereo - * @returns {Signal} - */ + * Triggered when the input audio use changes between mono and stereo. + * @function Audio.isStereoInputChanged + * @param {boolean} isStereo - true if the input audio is stereo, otherwise false. + * @returns {Signal} + */ void isStereoInputChanged(bool isStereo); private: diff --git a/libraries/script-engine/src/ScriptAudioInjector.h b/libraries/script-engine/src/ScriptAudioInjector.h index 4c2871dd34..2c88d618e1 100644 --- a/libraries/script-engine/src/ScriptAudioInjector.h +++ b/libraries/script-engine/src/ScriptAudioInjector.h @@ -16,6 +16,22 @@ #include +/**jsdoc + * Plays — "injects" — the content of an audio file. Used in the {@link Audio} API. + * + * @class AudioInjector + * + * @hifi-interface + * @hifi-client-entity + * @hifi-server-entity + * @hifi-assignment-client + * + * @property {boolean} playing - true if the audio is currently playing, otherwise false. + * Read-only. + * @property {number} loudness - The loudness in the last frame of audio, range 0.01.0. + * Read-only. + * @property {AudioInjector.AudioInjectorOptions} options - Configures how the injector plays the audio. + */ class ScriptAudioInjector : public QObject { Q_OBJECT @@ -26,19 +42,103 @@ public: ScriptAudioInjector(const AudioInjectorPointer& injector); ~ScriptAudioInjector(); public slots: + + /**jsdoc + * Stop current playback, if any, and start playing from the beginning. + * @function AudioInjector.restart + */ void restart() { _injector->restart(); } + + /**jsdoc + * Stop audio playback. + * @function AudioInjector.stop + * @example Stop playing a sound before it finishes. + * var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav"); + * var injector; + * var injectorOptions = { + * position: MyAvatar.position + * }; + * + * Script.setTimeout(function () { // Give the sound time to load. + * injector = Audio.playSound(sound, injectorOptions); + * }, 1000); + * + * Script.setTimeout(function () { + * injector.stop(); + * }, 2000); + */ void stop() { _injector->stop(); } + /**jsdoc + * Get the current configuration of the audio injector. + * @function AudioInjector.getOptions + * @returns {AudioInjector.AudioInjectorOptions} Configuration of how the injector plays the audio. + */ const AudioInjectorOptions& getOptions() const { return _injector->getOptions(); } + + /**jsdoc + * Configure how the injector plays the audio. + * @function AudioInjector.setOptions + * @param {AudioInjector.AudioInjectorOptions} options - Configuration of how the injector plays the audio. + */ void setOptions(const AudioInjectorOptions& options) { _injector->setOptions(options); } + /**jsdoc + * Get the loudness of the most recent frame of audio played. + * @function AudioInjector.getLoudness + * @returns {number} The loudness of the most recent frame of audio played, range 0.01.0. + */ float getLoudness() const { return _injector->getLoudness(); } + + /**jsdoc + * Get whether or not the audio is currently playing. + * @function AudioInjector.isPlaying + * @returns {boolean} true if the audio is currently playing, otherwise false. + * @example See if a sound is playing. + * var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav"); + * var injector; + * var injectorOptions = { + * position: MyAvatar.position + * }; + * + * Script.setTimeout(function () { // Give the sound time to load. + * injector = Audio.playSound(sound, injectorOptions); + * }, 1000); + * + * Script.setTimeout(function () { + * print("Sound is playing: " + injector.isPlaying()); + * }, 2000); + */ bool isPlaying() const { return _injector->isPlaying(); } signals: + + /**jsdoc + * Triggered when the audio has finished playing. + * @function AudioInjector.finished + * @returns {Signal} + * @example Report when a sound has finished playing. + * var sound = SoundCache.getSound(Script.resourcesPath() + "sounds/sample.wav"); + * var injector; + * var injectorOptions = { + * position: MyAvatar.position + * }; + * + * Script.setTimeout(function () { // Give the sound time to load. + * injector = Audio.playSound(sound, injectorOptions); + * injector.finished.connect(function () { + * print("Finished playing sound"); + * }); + * }, 1000); + */ void finished(); protected slots: + + /**jsdoc + * Stop audio playback. (Synonym of {@link AudioInjector.stop|stop}.) + * @function AudioInjector.stopInjectorImmediately + */ void stopInjectorImmediately(); private: AudioInjectorPointer _injector; diff --git a/libraries/shared/src/PhysicsCollisionGroups.h b/libraries/shared/src/PhysicsCollisionGroups.h index 9d99ec3532..ef18cb0b0e 100644 --- a/libraries/shared/src/PhysicsCollisionGroups.h +++ b/libraries/shared/src/PhysicsCollisionGroups.h @@ -59,7 +59,11 @@ const int32_t BULLET_COLLISION_MASK_KINEMATIC = BULLET_COLLISION_MASK_STATIC; // MY_AVATAR does not collide with itself const int32_t BULLET_COLLISION_MASK_MY_AVATAR = ~(BULLET_COLLISION_GROUP_COLLISIONLESS | BULLET_COLLISION_GROUP_MY_AVATAR); -const int32_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_MASK_DEFAULT; +// OTHER_AVATARs are dynamic, but are slammed to whatever the avatar-mixer says, which means +// their motion can't actually be affected by the local physics simulation -- we rely on the remote simulation +// to move its avatar around correctly and to communicate its motion through the avatar-mixer. +// Therefore, they only need to collide against things that can be affected by their motion: dynamic and MyAvatar +const int32_t BULLET_COLLISION_MASK_OTHER_AVATAR = BULLET_COLLISION_GROUP_DYNAMIC | BULLET_COLLISION_GROUP_MY_AVATAR; // COLLISIONLESS gets an empty mask. const int32_t BULLET_COLLISION_MASK_COLLISIONLESS = 0; diff --git a/libraries/ui/src/QmlFragmentClass.cpp b/libraries/ui/src/QmlFragmentClass.cpp index c4cac73b2d..13e3527ded 100644 --- a/libraries/ui/src/QmlFragmentClass.cpp +++ b/libraries/ui/src/QmlFragmentClass.cpp @@ -20,10 +20,10 @@ std::mutex QmlFragmentClass::_mutex; std::map QmlFragmentClass::_fragments; -QmlFragmentClass::QmlFragmentClass(QString id) : qml(id) { } +QmlFragmentClass::QmlFragmentClass(bool restricted, QString id) : QmlWindowClass(restricted), qml(id) { } // Method called by Qt scripts to create a new bottom menu bar in Android -QScriptValue QmlFragmentClass::constructor(QScriptContext* context, QScriptEngine* engine) { +QScriptValue QmlFragmentClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { std::lock_guard guard(_mutex); auto qml = context->argument(0).toVariant().toMap().value("qml"); @@ -41,7 +41,7 @@ QScriptValue QmlFragmentClass::constructor(QScriptContext* context, QScriptEngin auto properties = parseArguments(context); auto offscreenUi = DependencyManager::get(); - QmlFragmentClass* retVal = new QmlFragmentClass(qml.toString()); + QmlFragmentClass* retVal = new QmlFragmentClass(restricted, qml.toString()); Q_ASSERT(retVal); if (QThread::currentThread() != qApp->thread()) { retVal->moveToThread(qApp->thread()); diff --git a/libraries/ui/src/QmlFragmentClass.h b/libraries/ui/src/QmlFragmentClass.h index 8a8d0e1732..ea80b2bd13 100644 --- a/libraries/ui/src/QmlFragmentClass.h +++ b/libraries/ui/src/QmlFragmentClass.h @@ -13,9 +13,19 @@ class QmlFragmentClass : public QmlWindowClass { Q_OBJECT + +private: + static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted); public: - QmlFragmentClass(QString id); - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) { + return internal_constructor(context, engine, false); + } + + static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){ + return internal_constructor(context, engine, true); + } + + QmlFragmentClass(bool restricted, QString id); /**jsdoc * Creates a new button, adds it to this and returns it. diff --git a/libraries/ui/src/QmlWebWindowClass.cpp b/libraries/ui/src/QmlWebWindowClass.cpp index 44a0af7787..282161497a 100644 --- a/libraries/ui/src/QmlWebWindowClass.cpp +++ b/libraries/ui/src/QmlWebWindowClass.cpp @@ -20,10 +20,10 @@ static const char* const URL_PROPERTY = "source"; static const char* const SCRIPT_PROPERTY = "scriptUrl"; // Method called by Qt scripts to create a new web window in the overlay -QScriptValue QmlWebWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { +QScriptValue QmlWebWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { auto properties = parseArguments(context); auto offscreenUi = DependencyManager::get(); - QmlWebWindowClass* retVal = new QmlWebWindowClass(); + QmlWebWindowClass* retVal = new QmlWebWindowClass(restricted); Q_ASSERT(retVal); if (QThread::currentThread() != qApp->thread()) { retVal->moveToThread(qApp->thread()); diff --git a/libraries/ui/src/QmlWebWindowClass.h b/libraries/ui/src/QmlWebWindowClass.h index 8cf77e4286..e3aea22e3d 100644 --- a/libraries/ui/src/QmlWebWindowClass.h +++ b/libraries/ui/src/QmlWebWindowClass.h @@ -57,8 +57,18 @@ class QmlWebWindowClass : public QmlWindowClass { Q_OBJECT Q_PROPERTY(QString url READ getURL CONSTANT) +private: + static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted); public: - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); + QmlWebWindowClass(bool restricted) : QmlWindowClass(restricted) {} + + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) { + return internal_constructor(context, engine, false); + } + + static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){ + return internal_constructor(context, engine, true); + } public slots: diff --git a/libraries/ui/src/QmlWindowClass.cpp b/libraries/ui/src/QmlWindowClass.cpp index 64fa27c8c6..0182e3adc3 100644 --- a/libraries/ui/src/QmlWindowClass.cpp +++ b/libraries/ui/src/QmlWindowClass.cpp @@ -25,6 +25,8 @@ #include #include "OffscreenUi.h" +#include "ui/types/HFWebEngineProfile.h" +#include "ui/types/FileTypeProfile.h" static const char* const SOURCE_PROPERTY = "source"; static const char* const TITLE_PROPERTY = "title"; @@ -68,10 +70,10 @@ QVariantMap QmlWindowClass::parseArguments(QScriptContext* context) { // Method called by Qt scripts to create a new web window in the overlay -QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* engine) { +QScriptValue QmlWindowClass::internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted) { auto properties = parseArguments(context); auto offscreenUi = DependencyManager::get(); - QmlWindowClass* retVal = new QmlWindowClass(); + QmlWindowClass* retVal = new QmlWindowClass(restricted); Q_ASSERT(retVal); if (QThread::currentThread() != qApp->thread()) { retVal->moveToThread(qApp->thread()); @@ -83,7 +85,7 @@ QScriptValue QmlWindowClass::constructor(QScriptContext* context, QScriptEngine* return engine->newQObject(retVal); } -QmlWindowClass::QmlWindowClass() { +QmlWindowClass::QmlWindowClass(bool restricted) : _restricted(restricted) { } @@ -99,8 +101,7 @@ void QmlWindowClass::initQml(QVariantMap properties) { auto offscreenUi = DependencyManager::get(); _source = properties[SOURCE_PROPERTY].toString(); - // Build the event bridge and wrapper on the main thread - offscreenUi->loadInNewContext(qmlSource(), [&](QQmlContext* context, QObject* object) { + auto objectInitLambda = [&](QQmlContext* context, QObject* object) { _qmlWindow = object; context->setContextProperty(EVENT_BRIDGE_PROPERTY, this); context->engine()->setObjectOwnership(this, QQmlEngine::CppOwnership); @@ -128,7 +129,24 @@ void QmlWindowClass::initQml(QVariantMap properties) { if (metaObject->indexOfSignal("moved") >= 0) connect(_qmlWindow, SIGNAL(moved(QVector2D)), this, SLOT(hasMoved(QVector2D)), Qt::QueuedConnection); connect(_qmlWindow, SIGNAL(windowClosed()), this, SLOT(hasClosed()), Qt::QueuedConnection); - }); + }; + + auto contextInitLambda = [&](QQmlContext* context) { +#if !defined(Q_OS_ANDROID) + // If the restricted flag is on, override the FileTypeProfile and HFWebEngineProfile objects in the + // QML surface root context with local ones + qDebug() << "Context initialization lambda"; + if (_restricted) { + qDebug() << "Restricting web content"; + ContextAwareProfile::restrictContext(context); + FileTypeProfile::registerWithContext(context); + HFWebEngineProfile::registerWithContext(context); + } +#endif + }; + + // Build the event bridge and wrapper on the main thread + offscreenUi->loadInNewContext(qmlSource(), objectInitLambda, contextInitLambda); Q_ASSERT(_qmlWindow); Q_ASSERT(dynamic_cast(_qmlWindow.data())); diff --git a/libraries/ui/src/QmlWindowClass.h b/libraries/ui/src/QmlWindowClass.h index 2b01c028ea..18ee1fedd5 100644 --- a/libraries/ui/src/QmlWindowClass.h +++ b/libraries/ui/src/QmlWindowClass.h @@ -38,9 +38,18 @@ class QmlWindowClass : public QObject { Q_PROPERTY(glm::vec2 size READ getSize WRITE setSize NOTIFY sizeChanged) Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) +private: + static QScriptValue internal_constructor(QScriptContext* context, QScriptEngine* engine, bool restricted); public: - static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine); - QmlWindowClass(); + static QScriptValue constructor(QScriptContext* context, QScriptEngine* engine) { + return internal_constructor(context, engine, false); + } + + static QScriptValue restricted_constructor(QScriptContext* context, QScriptEngine* engine ){ + return internal_constructor(context, engine, true); + } + + QmlWindowClass(bool restricted); ~QmlWindowClass(); /**jsdoc @@ -51,6 +60,8 @@ public: QQuickItem* asQuickItem() const; + + public slots: /**jsdoc @@ -250,10 +261,12 @@ protected: QPointer _qmlWindow; QString _source; + const bool _restricted; private: // QmlWindow content may include WebView requiring EventBridge. void setKeyboardRaised(QObject* object, bool raised, bool numeric = false); + }; #endif diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 48e778c063..2c947aea20 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -265,19 +265,6 @@ void OffscreenQmlSurface::initializeEngine(QQmlEngine* engine) { if (!javaScriptToInject.isEmpty()) { rootContext->setContextProperty("eventBridgeJavaScriptToInject", QVariant(javaScriptToInject)); } -#if !defined(Q_OS_ANDROID) - rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); - rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); - { - PROFILE_RANGE(startup, "FileTypeProfile"); - rootContext->setContextProperty("FileTypeProfile", new FileTypeProfile(rootContext)); - } - { - PROFILE_RANGE(startup, "HFWebEngineProfile"); - rootContext->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(rootContext)); - - } -#endif rootContext->setContextProperty("Paths", DependencyManager::get().data()); rootContext->setContextProperty("Tablet", DependencyManager::get().data()); rootContext->setContextProperty("Toolbars", DependencyManager::get().data()); @@ -300,6 +287,17 @@ void OffscreenQmlSurface::onRootContextCreated(QQmlContext* qmlContext) { // FIXME Compatibility mechanism for existing HTML and JS that uses eventBridgeWrapper // Find a way to flag older scripts using this mechanism and wanr that this is deprecated qmlContext->setContextProperty("eventBridgeWrapper", new EventBridgeWrapper(this, qmlContext)); +#if !defined(Q_OS_ANDROID) + { + PROFILE_RANGE(startup, "FileTypeProfile"); + FileTypeProfile::registerWithContext(qmlContext); + } + { + PROFILE_RANGE(startup, "HFWebEngineProfile"); + HFWebEngineProfile::registerWithContext(qmlContext); + + } +#endif } QQmlContext* OffscreenQmlSurface::contextForUrl(const QUrl& qmlSource, QQuickItem* parent, bool forceNewContext) { diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index 6f00e046af..4e920e430b 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -334,6 +334,8 @@ static const char* VRMENU_SOURCE_URL = "hifi/tablet/TabletMenu.qml"; class TabletRootWindow : public QmlWindowClass { virtual QString qmlSource() const override { return "hifi/tablet/WindowRoot.qml"; } +public: + TabletRootWindow() : QmlWindowClass(false) {} }; TabletProxy::TabletProxy(QObject* parent, const QString& name) : QObject(parent), _name(name) { diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.cpp b/libraries/ui/src/ui/types/ContextAwareProfile.cpp new file mode 100644 index 0000000000..98cc94ec10 --- /dev/null +++ b/libraries/ui/src/ui/types/ContextAwareProfile.cpp @@ -0,0 +1,32 @@ +// +// FileTypeProfile.cpp +// interface/src/networking +// +// Created by Kunal Gosar on 2017-03-10. +// Copyright 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 "ContextAwareProfile.h" + +#if !defined(Q_OS_ANDROID) + +#include + +static const QString RESTRICTED_FLAG_PROPERTY = "RestrictFileAccess"; + +ContextAwareProfile::ContextAwareProfile(QQmlContext* parent) : + QQuickWebEngineProfile(parent), _context(parent) { } + + +void ContextAwareProfile::restrictContext(QQmlContext* context) { + context->setContextProperty(RESTRICTED_FLAG_PROPERTY, true); +} + +bool ContextAwareProfile::isRestricted(QQmlContext* context) { + return context->contextProperty(RESTRICTED_FLAG_PROPERTY).toBool(); +} + +#endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/ContextAwareProfile.h b/libraries/ui/src/ui/types/ContextAwareProfile.h new file mode 100644 index 0000000000..8fa5b98878 --- /dev/null +++ b/libraries/ui/src/ui/types/ContextAwareProfile.h @@ -0,0 +1,42 @@ +// +// Created by Bradley Austin Davis on 2018/07/27 +// Copyright 2013-2018 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 + +#ifndef hifi_ContextAwareProfile_h +#define hifi_ContextAwareProfile_h + +#include + +#if !defined(Q_OS_ANDROID) +#include +#include + +class QQmlContext; + +class ContextAwareProfile : public QQuickWebEngineProfile { +public: + static void restrictContext(QQmlContext* context); + static bool isRestricted(QQmlContext* context); + QQmlContext* getContext() const { return _context; } +protected: + + class RequestInterceptor : public QWebEngineUrlRequestInterceptor { + public: + RequestInterceptor(ContextAwareProfile* parent) : QWebEngineUrlRequestInterceptor(parent), _profile(parent) {} + QQmlContext* getContext() const { return _profile->getContext(); } + protected: + ContextAwareProfile* _profile; + }; + + ContextAwareProfile(QQmlContext* parent); + QQmlContext* _context; +}; +#endif + +#endif // hifi_FileTypeProfile_h diff --git a/libraries/ui/src/ui/types/FileTypeProfile.cpp b/libraries/ui/src/ui/types/FileTypeProfile.cpp index 90a2c6ba18..073460903e 100644 --- a/libraries/ui/src/ui/types/FileTypeProfile.cpp +++ b/libraries/ui/src/ui/types/FileTypeProfile.cpp @@ -11,18 +11,31 @@ #include "FileTypeProfile.h" -#include "FileTypeRequestInterceptor.h" +#include + +#include "RequestFilters.h" #if !defined(Q_OS_ANDROID) static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; -FileTypeProfile::FileTypeProfile(QObject* parent) : - QQuickWebEngineProfile(parent) +FileTypeProfile::FileTypeProfile(QQmlContext* parent) : + ContextAwareProfile(parent) { static const QString WEB_ENGINE_USER_AGENT = "Chrome/48.0 (HighFidelityInterface)"; setHttpUserAgent(WEB_ENGINE_USER_AGENT); - auto requestInterceptor = new FileTypeRequestInterceptor(this); + auto requestInterceptor = new RequestInterceptor(this); setRequestInterceptor(requestInterceptor); } + +void FileTypeProfile::RequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { + RequestFilters::interceptHFWebEngineRequest(info, getContext()); + RequestFilters::interceptFileType(info, getContext()); +} + +void FileTypeProfile::registerWithContext(QQmlContext* context) { + context->setContextProperty("FileTypeProfile", new FileTypeProfile(context)); +} + + #endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/FileTypeProfile.h b/libraries/ui/src/ui/types/FileTypeProfile.h index c7d07cd822..7ddfdd0aed 100644 --- a/libraries/ui/src/ui/types/FileTypeProfile.h +++ b/libraries/ui/src/ui/types/FileTypeProfile.h @@ -17,12 +17,23 @@ #include #if !defined(Q_OS_ANDROID) -#include +#include "ContextAwareProfile.h" + +class FileTypeProfile : public ContextAwareProfile { + using Parent = ContextAwareProfile; -class FileTypeProfile : public QQuickWebEngineProfile { public: - FileTypeProfile(QObject* parent = Q_NULLPTR); + static void registerWithContext(QQmlContext* parent); + +protected: + FileTypeProfile(QQmlContext* parent); + class RequestInterceptor : public Parent::RequestInterceptor { + public: + RequestInterceptor(ContextAwareProfile* parent) : Parent::RequestInterceptor(parent) {} + void interceptRequest(QWebEngineUrlRequestInfo& info) override; + }; }; + #endif #endif // hifi_FileTypeProfile_h diff --git a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.cpp b/libraries/ui/src/ui/types/FileTypeRequestInterceptor.cpp deleted file mode 100644 index 25866ad395..0000000000 --- a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// -// FileTypeRequestInterceptor.cpp -// interface/src/networking -// -// Created by Kunal Gosar on 2017-03-10. -// Copyright 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 "FileTypeRequestInterceptor.h" - -#include - -#include "RequestFilters.h" - -#if !defined(Q_OS_ANDROID) - -void FileTypeRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { - RequestFilters::interceptHFWebEngineRequest(info); - RequestFilters::interceptFileType(info); -} - -#endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.h b/libraries/ui/src/ui/types/FileTypeRequestInterceptor.h deleted file mode 100644 index b8a01a53fa..0000000000 --- a/libraries/ui/src/ui/types/FileTypeRequestInterceptor.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// FileTypeRequestInterceptor.h -// interface/src/networking -// -// Created by Kunal Gosar on 2017-03-10. -// Copyright 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 -// - -#pragma once - -#ifndef hifi_FileTypeRequestInterceptor_h -#define hifi_FileTypeRequestInterceptor_h - -#include - -#if !defined(Q_OS_ANDROID) -#include - -class FileTypeRequestInterceptor : public QWebEngineUrlRequestInterceptor { -public: - FileTypeRequestInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent) {}; - - virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override; -}; -#endif - -#endif // hifi_FileTypeRequestInterceptor_h diff --git a/libraries/ui/src/ui/types/HFTabletWebEngineProfile.h b/libraries/ui/src/ui/types/HFTabletWebEngineProfile.h deleted file mode 100644 index 406cb1a19a..0000000000 --- a/libraries/ui/src/ui/types/HFTabletWebEngineProfile.h +++ /dev/null @@ -1,23 +0,0 @@ -// -// HFTabletWebEngineProfile.h -// interface/src/networking -// -// Created by Dante Ruiz on 2017-03-31. -// Copyright 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_HFTabletWebEngineProfile_h -#define hifi_HFTabletWebEngineProfile_h - -#include - -class HFTabletWebEngineProfile : public QQuickWebEngineProfile { -public: - HFTabletWebEngineProfile(QObject* parent = Q_NULLPTR); -}; - -#endif // hifi_HFTabletWebEngineProfile_h diff --git a/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.h b/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.h deleted file mode 100644 index 8be2974782..0000000000 --- a/libraries/ui/src/ui/types/HFTabletWebEngineRequestInterceptor.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// HFTabletWebEngineRequestInterceptor.h -// interface/src/networking -// -// Created by Dante Ruiz on 2017-3-31. -// Copyright 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_HFTabletWebEngineRequestInterceptor_h -#define hifi_HFTabletWebEngineRequestInterceptor_h -#if !defined(Q_OS_ANDROID) -#include - -#include - -class HFTabletWebEngineRequestInterceptor - : public QWebEngineUrlRequestInterceptor -{ -public: - HFTabletWebEngineRequestInterceptor(QObject* parent) - : QWebEngineUrlRequestInterceptor(parent) - {}; - virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override; -}; -#endif - -#endif // hifi_HFWebEngineRequestInterceptor_h diff --git a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp index 381bdb10bd..ef1d009f09 100644 --- a/libraries/ui/src/ui/types/HFWebEngineProfile.cpp +++ b/libraries/ui/src/ui/types/HFWebEngineProfile.cpp @@ -11,20 +11,28 @@ #include "HFWebEngineProfile.h" -#include "HFWebEngineRequestInterceptor.h" +#include + +#include "RequestFilters.h" #if !defined(Q_OS_ANDROID) static const QString QML_WEB_ENGINE_STORAGE_NAME = "qmlWebEngine"; -HFWebEngineProfile::HFWebEngineProfile(QObject* parent) : - QQuickWebEngineProfile(parent) +HFWebEngineProfile::HFWebEngineProfile(QQmlContext* parent) : Parent(parent) { setStorageName(QML_WEB_ENGINE_STORAGE_NAME); // we use the HFWebEngineRequestInterceptor to make sure that web requests are authenticated for the interface user - auto requestInterceptor = new HFWebEngineRequestInterceptor(this); - setRequestInterceptor(requestInterceptor); + setRequestInterceptor(new RequestInterceptor(this)); } -#endif \ No newline at end of file +void HFWebEngineProfile::RequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { + RequestFilters::interceptHFWebEngineRequest(info, getContext()); +} + +void HFWebEngineProfile::registerWithContext(QQmlContext* context) { + context->setContextProperty("HFWebEngineProfile", new HFWebEngineProfile(context)); +} + +#endif diff --git a/libraries/ui/src/ui/types/HFWebEngineProfile.h b/libraries/ui/src/ui/types/HFWebEngineProfile.h index 30da489c92..6b84ad6f80 100644 --- a/libraries/ui/src/ui/types/HFWebEngineProfile.h +++ b/libraries/ui/src/ui/types/HFWebEngineProfile.h @@ -14,15 +14,24 @@ #ifndef hifi_HFWebEngineProfile_h #define hifi_HFWebEngineProfile_h -#include +#include "ContextAwareProfile.h" #if !defined(Q_OS_ANDROID) -#include -class HFWebEngineProfile : public QQuickWebEngineProfile { +class HFWebEngineProfile : public ContextAwareProfile { + using Parent = ContextAwareProfile; public: - HFWebEngineProfile(QObject* parent = Q_NULLPTR); + static void registerWithContext(QQmlContext* parent); + +protected: + HFWebEngineProfile(QQmlContext* parent); + class RequestInterceptor : public Parent::RequestInterceptor { + public: + RequestInterceptor(ContextAwareProfile* parent) : Parent::RequestInterceptor(parent) {} + void interceptRequest(QWebEngineUrlRequestInfo& info) override; + }; }; + #endif #endif // hifi_HFWebEngineProfile_h diff --git a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.cpp b/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.cpp deleted file mode 100644 index 5a11c32efa..0000000000 --- a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// -// HFWebEngineRequestInterceptor.cpp -// interface/src/networking -// -// Created by Stephen Birarda on 2016-10-14. -// Copyright 2016 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 "HFWebEngineRequestInterceptor.h" - -#include - -#include "AccountManager.h" -#include "RequestFilters.h" - -#if !defined(Q_OS_ANDROID) - -void HFWebEngineRequestInterceptor::interceptRequest(QWebEngineUrlRequestInfo& info) { - RequestFilters::interceptHFWebEngineRequest(info); -} - -#endif \ No newline at end of file diff --git a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.h b/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.h deleted file mode 100644 index b5521a106e..0000000000 --- a/libraries/ui/src/ui/types/HFWebEngineRequestInterceptor.h +++ /dev/null @@ -1,30 +0,0 @@ -// -// HFWebEngineRequestInterceptor.h -// interface/src/networking -// -// Created by Stephen Birarda on 2016-10-14. -// Copyright 2016 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 - -#ifndef hifi_HFWebEngineRequestInterceptor_h -#define hifi_HFWebEngineRequestInterceptor_h - -#include - -#if !defined(Q_OS_ANDROID) -#include - -class HFWebEngineRequestInterceptor : public QWebEngineUrlRequestInterceptor { -public: - HFWebEngineRequestInterceptor(QObject* parent) : QWebEngineUrlRequestInterceptor(parent) {}; - - virtual void interceptRequest(QWebEngineUrlRequestInfo& info) override; -}; -#endif - -#endif // hifi_HFWebEngineRequestInterceptor_h diff --git a/libraries/ui/src/ui/types/RequestFilters.cpp b/libraries/ui/src/ui/types/RequestFilters.cpp index 4cd51c6d98..7f192d6e52 100644 --- a/libraries/ui/src/ui/types/RequestFilters.cpp +++ b/libraries/ui/src/ui/types/RequestFilters.cpp @@ -10,12 +10,15 @@ // #include "RequestFilters.h" -#include "NetworkingConstants.h" #include -#include +#include -#include "AccountManager.h" +#include +#include +#include + +#include "ContextAwareProfile.h" #if !defined(Q_OS_ANDROID) @@ -42,9 +45,29 @@ namespace { return filename.endsWith(".json", Qt::CaseInsensitive); } + bool blockLocalFiles(QWebEngineUrlRequestInfo& info) { + auto requestUrl = info.requestUrl(); + if (!requestUrl.isLocalFile()) { + // Not a local file, do not block + return false; + } + + // We can potentially add whitelisting logic or development environment variables that + // will allow people to override this setting on a per-client basis here. + QString targetFilePath = QFileInfo(requestUrl.toLocalFile()).canonicalFilePath(); + + // If we get here, we've determined it's a local file and we have no reason not to block it + qWarning() << "Blocking web access to local file path" << targetFilePath; + info.block(true); + return true; + } } -void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info) { +void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info, QQmlContext* context) { + if (ContextAwareProfile::isRestricted(context) && blockLocalFiles(info)) { + return; + } + // check if this is a request to a highfidelity URL bool isAuthable = isAuthableHighFidelityURL(info.requestUrl()); if (isAuthable) { @@ -71,7 +94,7 @@ void RequestFilters::interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info) info.setHttpHeader(USER_AGENT.toLocal8Bit(), tokenString.toLocal8Bit()); } -void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info) { +void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info, QQmlContext* context) { QString filename = info.requestUrl().fileName(); if (isScript(filename) || isJSON(filename)) { static const QString CONTENT_HEADER = "Accept"; @@ -79,4 +102,4 @@ void RequestFilters::interceptFileType(QWebEngineUrlRequestInfo& info) { info.setHttpHeader(CONTENT_HEADER.toLocal8Bit(), TYPE_VALUE.toLocal8Bit()); } } -#endif \ No newline at end of file +#endif diff --git a/libraries/ui/src/ui/types/RequestFilters.h b/libraries/ui/src/ui/types/RequestFilters.h index ccab6a6ee3..8fde94a1b4 100644 --- a/libraries/ui/src/ui/types/RequestFilters.h +++ b/libraries/ui/src/ui/types/RequestFilters.h @@ -20,10 +20,12 @@ #include #include +class QQmlContext; + class RequestFilters : public QObject { public: - static void interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info); - static void interceptFileType(QWebEngineUrlRequestInfo& info); + static void interceptHFWebEngineRequest(QWebEngineUrlRequestInfo& info, QQmlContext* context); + static void interceptFileType(QWebEngineUrlRequestInfo& info, QQmlContext* context); }; #endif