Showing the changes to interface/src/scripting/ScreenshareScriptingInterface.cpp (1 of 2 files). Show all
Unified · Split
| @@ -9,37 +9,27 @@ · 8 unmodified lines | ||
| 9 | 9 | // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html |
| 10 | 10 | // |
| 11 | 11 | |
| 12 | −#include <QCoreApplication> | |
| 13 | −#include <QDesktopServices> | |
| 14 | 12 | #include <QJsonDocument> |
| 15 | 13 | #include <QJsonObject> |
| 14 | +#include <QNetworkAccessManager> | |
| 16 | 15 | #include <QThread> |
| 17 | 16 | #include <QUrl> |
| 18 | −#include <QObject> | |
| 19 | −#include <QNetworkAccessManager> | |
| 20 | −#include <QNetworkReply> | |
| 21 | −#include <QNetworkRequest> | |
| 22 | −#include <QProcessEnvironment> | |
| 23 | 17 | |
| 24 | 18 | #include <AccountManager.h> |
| 25 | 19 | #include <AddressManager.h> |
| 26 | −#include <EntityTreeRenderer.h> | |
| 27 | −#include <EntityTree.h> | |
| 20 | +#include <DependencyManager.h> | |
| 28 | 21 | #include <UUID.h> |
| 29 | 22 | |
| 30 | 23 | #include "EntityScriptingInterface.h" |
| 31 | 24 | #include "ScreenshareScriptingInterface.h" |
| 32 | 25 | |
| 33 | −#include <RenderableEntityItem.h> | |
| 34 | −#include <RenderableTextEntityItem.h> | |
| 35 | −#include <RenderableWebEntityItem.h> | |
| 36 | − | |
| 37 | 26 | ScreenshareScriptingInterface::ScreenshareScriptingInterface() { |
| 38 | 27 | auto esi = DependencyManager::get<EntityScriptingInterface>(); |
| 39 | 28 | if (!esi) { |
| 40 | 29 | return; |
| 41 | 30 | } |
| 42 | 31 | |
| 32 | + // This signal/slot connection is used when the screen share local web entity sends an event bridge message. | |
| 43 | 33 | QObject::connect(esi.data(), &EntityScriptingInterface::webEventReceived, this, &ScreenshareScriptingInterface::onWebEventReceived); |
| 44 | 34 | }; |
| 45 | 35 | |
| @@ -55,27 +45,35 @@ static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_LOCAL_POSITION(0.0f, -0.0862 · 9 unmodified lines | ||
| 55 | 45 | static const glm::vec3 LOCAL_SCREENSHARE_WEB_ENTITY_DIMENSIONS(4.0419f, 2.2735f, 0.0100f); |
| 56 | 46 | static const QString LOCAL_SCREENSHARE_WEB_ENTITY_URL = |
| 57 | 47 | "https://hifi-content.s3.amazonaws.com/Experiences/Releases/usefulUtilities/smartBoard/screenshareViewer/screenshareClient.html"; |
| 48 | +static const QString LOCAL_SCREENSHARE_WEB_ENTITY_HOST_TYPE ="local"; | |
| 58 | 49 | void ScreenshareScriptingInterface::startScreenshare(const QUuid& screenshareZoneID, |
| 59 | 50 | const QUuid& smartboardEntityID, |
| 60 | 51 | const bool& isPresenter) { |
| 52 | + // We must start a new QProcess from the main thread. | |
| 61 | 53 | if (QThread::currentThread() != thread()) { |
| 62 | − // We must start a new QProcess from the main thread. | |
| 63 | 54 | QMetaObject::invokeMethod(this, "startScreenshare", Q_ARG(const QUuid&, screenshareZoneID), |
| 64 | 55 | Q_ARG(const QUuid&, smartboardEntityID), Q_ARG(const bool&, isPresenter)); |
| 65 | 56 | return; |
| 66 | 57 | } |
| 67 | 58 | |
| 59 | + // These three private member variables are set now so that they may be used later during asynchronous | |
| 60 | + // callbacks. | |
| 68 | 61 | _screenshareZoneID = screenshareZoneID; |
| 69 | 62 | _smartboardEntityID = smartboardEntityID; |
| 70 | 63 | _isPresenter = isPresenter; |
| 71 | 64 | |
| 65 | + // If we are presenting, and the screenshare process is already running, don't do anything else here. | |
| 72 | 66 | if (_isPresenter && _screenshareProcess && _screenshareProcess->state() != QProcess::NotRunning) { |
| 73 | 67 | return; |
| 74 | 68 | } |
| 75 | 69 | |
| 70 | + // If we're presenting... | |
| 76 | 71 | if (_isPresenter) { |
| 72 | + // ...make sure we first reset this `std::unique_ptr`. | |
| 77 | 73 | _screenshareProcess.reset(new QProcess(this)); |
| 78 | 74 | |
| 75 | + // Ensure that the screenshare executable exists where we expect it to. | |
| 76 | + // Error out and reset the screen share state machine if the executable doesn't exist. | |
| 79 | 77 | QFileInfo screenshareExecutable(SCREENSHARE_EXE_PATH); |
| 80 | 78 | if (!screenshareExecutable.exists() || !screenshareExecutable.isFile()) { |
| 81 | 79 | qDebug() << "Screenshare executable doesn't exist at" << SCREENSHARE_EXE_PATH; |
| @@ -85,6 +83,7 @@ void ScreenshareScriptingInterface::startScreenshare(const QUuid& screenshareZon · 3 unmodified lines | ||
| 85 | 83 | } |
| 86 | 84 | } |
| 87 | 85 | |
| 86 | + // Don't continue with any more of this logic if we can't get the `AccountManager` or `AddressManager`. | |
| 88 | 87 | auto accountManager = DependencyManager::get<AccountManager>(); |
| 89 | 88 | if (!accountManager) { |
| 90 | 89 | return; |
| @@ -94,9 +93,17 @@ void ScreenshareScriptingInterface::startScreenshare(const QUuid& screenshareZon · 3 unmodified lines | ||
| 94 | 93 | return; |
| 95 | 94 | } |
| 96 | 95 | |
| 96 | + // Construct and send a request to the Metaverse to obtain the information | |
| 97 | + // necessary to start the screen sharing process. | |
| 98 | + // This request requires: | |
| 99 | + // 1. The domain ID of the domain in which the user's avatar is present | |
| 100 | + // 2. User authentication information that is automatically included when `sendRequest()` is passed | |
| 101 | + // with the `AccountManagerAuth::Required` argument. | |
| 102 | + // Note that this request will only return successfully if the Domain Server has already registered | |
| 103 | + // the user paired with the current domain with the Metaverse. | |
| 104 | + // See `DomainServer::screensharePresence()` for more info about that. | |
| 97 | 105 | QString currentDomainID = uuidStringWithoutCurlyBraces(addressManager->getDomainID()); |
| 98 | 106 | QString requestURLPath = "api/v1/domains/%1/screenshare"; |
| 99 | − | |
| 100 | 107 | JSONCallbackParameters callbackParams; |
| 101 | 108 | callbackParams.callbackReceiver = this; |
| 102 | 109 | callbackParams.jsonCallbackMethod = "handleSuccessfulScreenshareInfoGet"; |
| @@ -110,22 +117,29 @@ void ScreenshareScriptingInterface::startScreenshare(const QUuid& screenshareZon · 7 unmodified lines | ||
| 110 | 117 | } |
| 111 | 118 | |
| 112 | 119 | void ScreenshareScriptingInterface::stopScreenshare() { |
| 120 | + // We can only deal with our Screen Share `QProcess` on the main thread. | |
| 113 | 121 | if (QThread::currentThread() != thread()) { |
| 114 | 122 | QMetaObject::invokeMethod(this, "stopScreenshare"); |
| 115 | 123 | return; |
| 116 | 124 | } |
| 117 | 125 | |
| 126 | + // If the Screen Share process is running... | |
| 118 | 127 | if (_screenshareProcess && _screenshareProcess->state() != QProcess::NotRunning) { |
| 128 | + //...terminate it and make sure that scripts know we terminated it by emitting | |
| 129 | + // `screenshareProcessTerminated()`. | |
| 119 | 130 | _screenshareProcess->terminate(); |
| 120 | 131 | emit screenshareProcessTerminated(); |
| 121 | 132 | } |
| 122 | 133 | |
| 134 | + // Delete the local web entity if we know about it here. | |
| 123 | 135 | if (!_screenshareViewerLocalWebEntityUUID.isNull()) { |
| 124 | 136 | auto esi = DependencyManager::get<EntityScriptingInterface>(); |
| 125 | 137 | if (esi) { |
| 126 | 138 | esi->deleteEntity(_screenshareViewerLocalWebEntityUUID); |
| 127 | 139 | } |
| 128 | 140 | } |
| 141 | + | |
| 142 | + // Reset all private member variables related to screen share here. | |
| 129 | 143 | _screenshareViewerLocalWebEntityUUID = "{00000000-0000-0000-0000-000000000000}"; |
| 130 | 144 | _token = ""; |
| 131 | 145 | _projectAPIKey = ""; |
| @@ -133,12 +147,14 @@ void ScreenshareScriptingInterface::stopScreenshare() { · 1 unmodified line | ||
| 133 | 147 | _isPresenter = false; |
| 134 | 148 | } |
| 135 | 149 | |
| 150 | +// Called when the Metaverse returns the information necessary to start/view a screen share. | |
| 136 | 151 | void ScreenshareScriptingInterface::handleSuccessfulScreenshareInfoGet(QNetworkReply* reply) { |
| 152 | + // Read the reply and get it into a format we understand. | |
| 137 | 153 | QString answer = reply->readAll(); |
| 138 | − | |
| 139 | 154 | QByteArray answerByteArray = answer.toUtf8(); |
| 140 | 155 | QJsonDocument answerJSONObject = QJsonDocument::fromJson(answerByteArray); |
| 141 | 156 | |
| 157 | + // This Metaverse endpoint will always return a status key/value pair of "success" if things went well. | |
| 142 | 158 | QString status = answerJSONObject["status"].toString(); |
| 143 | 159 | if (status != "success") { |
| 144 | 160 | qDebug() << "Error when retrieving screenshare info via HTTP. Error:" << reply->errorString(); |
| @@ -147,10 +163,12 @@ void ScreenshareScriptingInterface::handleSuccessfulScreenshareInfoGet(QNetworkR · 2 unmodified lines | ||
| 147 | 163 | return; |
| 148 | 164 | } |
| 149 | 165 | |
| 166 | + // Store the information necessary to start/view a screen share in these private member variables. | |
| 150 | 167 | _token = answerJSONObject["token"].toString(); |
| 151 | 168 | _projectAPIKey = answerJSONObject["projectApiKey"].toString(); |
| 152 | 169 | _sessionID = answerJSONObject["sessionID"].toString(); |
| 153 | 170 | |
| 171 | + // Make sure we have all of the info that we need. | |
| 154 | 172 | if (_token.isEmpty() || _projectAPIKey.isEmpty() || _sessionID.isEmpty()) { |
| 155 | 173 | qDebug() << "Not all Screen Share information was retrieved from the backend. Stopping..."; |
| 156 | 174 | stopScreenshare(); |
| @@ -158,6 +176,10 @@ void ScreenshareScriptingInterface::handleSuccessfulScreenshareInfoGet(QNetworkR · 1 unmodified line | ||
| 158 | 176 | return; |
| 159 | 177 | } |
| 160 | 178 | |
| 179 | + // If we're presenting: | |
| 180 | + // 1. Build a list of arguments that we're going to pass to the screen share Electron app. | |
| 181 | + // 2. Make sure we connect a signal/slot to know when the user quits the Electron app. | |
| 182 | + // 3. Start the screen share Electron app with the list of args from (1). | |
| 161 | 183 | if (_isPresenter) { |
| 162 | 184 | QStringList arguments; |
| 163 | 185 | arguments << " "; |
| @@ -174,12 +196,7 @@ void ScreenshareScriptingInterface::handleSuccessfulScreenshareInfoGet(QNetworkR · 10 unmodified lines | ||
| 174 | 196 | _screenshareProcess->start(SCREENSHARE_EXE_PATH, arguments); |
| 175 | 197 | } |
| 176 | 198 | |
| 177 | − if (!_screenshareViewerLocalWebEntityUUID.isNull()) { | |
| 178 | − stopScreenshare(); | |
| 179 | − emit screenshareError(); | |
| 180 | − return; | |
| 181 | − } | |
| 182 | − | |
| 199 | + // Make sure we can grab the entity scripting interface. Error out if we can't. | |
| 183 | 200 | auto esi = DependencyManager::get<EntityScriptingInterface>(); |
| 184 | 201 | if (!esi) { |
| 185 | 202 | stopScreenshare(); |
| @@ -187,6 +204,12 @@ void ScreenshareScriptingInterface::handleSuccessfulScreenshareInfoGet(QNetworkR · 1 unmodified line | ||
| 187 | 204 | return; |
| 188 | 205 | } |
| 189 | 206 | |
| 207 | + // If, for some reason, we already have a record of a screen share local Web entity, delete it. | |
| 208 | + if (!_screenshareViewerLocalWebEntityUUID.isNull()) { | |
| 209 | + esi->deleteEntity(_screenshareViewerLocalWebEntityUUID); | |
| 210 | + } | |
| 211 | + | |
| 212 | + // Set up the entity properties associated with the screen share local Web entity. | |
| 190 | 213 | EntityItemProperties localScreenshareWebEntityProps; |
| 191 | 214 | localScreenshareWebEntityProps.setType(LOCAL_SCREENSHARE_WEB_ENTITY_TYPE); |
| 192 | 215 | localScreenshareWebEntityProps.setMaxFPS(LOCAL_SCREENSHARE_WEB_ENTITY_FPS); |
| @@ -195,14 +218,16 @@ void ScreenshareScriptingInterface::handleSuccessfulScreenshareInfoGet(QNetworkR · 2 unmodified lines | ||
| 195 | 218 | localScreenshareWebEntityProps.setParentID(_smartboardEntityID); |
| 196 | 219 | localScreenshareWebEntityProps.setDimensions(LOCAL_SCREENSHARE_WEB_ENTITY_DIMENSIONS); |
| 197 | 220 | |
| 198 | − // The lines below will be used when writing the feature to ensure that the smartboard can be of any arbitrary size. | |
| 221 | + // The lines below will be used when writing the feature to support scaling the Smartboard entity to any arbitrary size. | |
| 199 | 222 | //EntityPropertyFlags desiredSmartboardProperties; |
| 200 | 223 | //desiredSmartboardProperties += PROP_POSITION; |
| 201 | 224 | //desiredSmartboardProperties += PROP_DIMENSIONS; |
| 202 | − //EntityItemProperties smartboardProps = esi->getEntityProperties(smartboardEntityID, desiredSmartboardProperties); | |
| 203 | − | |
| 204 | − QString hostType = "local"; | |
| 205 | − _screenshareViewerLocalWebEntityUUID = esi->addEntity(localScreenshareWebEntityProps, hostType); | |
| 225 | + //EntityItemProperties smartboardProps = esi->getEntityProperties(_smartboardEntityID, desiredSmartboardProperties); | |
| 226 | + | |
| 227 | + // Add the screen share local Web entity to Interface's entity tree. | |
| 228 | + // When the Web entity loads the page specified by `LOCAL_SCREENSHARE_WEB_ENTITY_URL`, it will broadcast an Event Bridge | |
| 229 | + // message, which we will consume inside `ScreenshareScriptingInterface::onWebEventReceived()`. | |
| 230 | + _screenshareViewerLocalWebEntityUUID = esi->addEntity(localScreenshareWebEntityProps, LOCAL_SCREENSHARE_WEB_ENTITY_HOST_TYPE); | |
| 206 | 231 | } |
| 207 | 232 | |
| 208 | 233 | void ScreenshareScriptingInterface::handleFailedScreenshareInfoGet(QNetworkReply* reply) { |
| @@ -211,21 +236,31 @@ void ScreenshareScriptingInterface::handleFailedScreenshareInfoGet(QNetworkReply · 2 unmodified lines | ||
| 211 | 236 | emit screenshareError(); |
| 212 | 237 | } |
| 213 | 238 | |
| 239 | +// This function will handle _all_ web events received via `EntityScriptingInterface::webEventReceived()`, including | |
| 240 | +// those not related to screen sharing. | |
| 214 | 241 | void ScreenshareScriptingInterface::onWebEventReceived(const QUuid& entityID, const QVariant& message) { |
| 242 | + // Bail early if the entity that sent the Web event isn't the one we care about. | |
| 215 | 243 | if (entityID == _screenshareViewerLocalWebEntityUUID) { |
| 244 | + // Bail early if we can't grab the Entity Scripting Interface. | |
| 216 | 245 | auto esi = DependencyManager::get<EntityScriptingInterface>(); |
| 217 | 246 | if (!esi) { |
| 218 | 247 | return; |
| 219 | 248 | } |
| 220 | 249 | |
| 250 | + // Web events received from the screen share Web JS will always be in stringified JSON format. | |
| 221 | 251 | QByteArray jsonByteArray = QVariant(message).toString().toUtf8(); |
| 222 | 252 | QJsonDocument jsonObject = QJsonDocument::fromJson(jsonByteArray); |
| 223 | 253 | |
| 254 | + // It should never happen where the screen share Web JS sends a message without the `app` key's value | |
| 255 | + // set to "screenshare". | |
| 224 | 256 | if (jsonObject["app"] != "screenshare") { |
| 225 | 257 | return; |
| 226 | 258 | } |
| 227 | 259 | |
| 260 | + // The screen share Web JS only sends a message with one method: "eventBridgeReady". Handle it here. | |
| 228 | 261 | if (jsonObject["method"] == "eventBridgeReady") { |
| 262 | + // Stuff a JSON object full of information necessary for the screen share local Web entity | |
| 263 | + // to connect to the screen share session associated with the room in which the user's avatar is standing. | |
| 229 | 264 | QJsonObject responseObject; |
| 230 | 265 | responseObject.insert("app", "screenshare"); |
| 231 | 266 | responseObject.insert("method", "receiveConnectionInfo"); |
| @@ -235,7 +270,6 @@ void ScreenshareScriptingInterface::onWebEventReceived(const QUuid& entityID, co · 3 unmodified lines | ||
| 235 | 270 | responseObjectData.insert("sessionID", _sessionID); |
| 236 | 271 | responseObject.insert("data", responseObjectData); |
| 237 | 272 | |
| 238 | − auto esi = DependencyManager::get<EntityScriptingInterface>(); | |
| 239 | 273 | esi->emitScriptEvent(_screenshareViewerLocalWebEntityUUID, responseObject.toVariantMap()); |
| 240 | 274 | } |
| 241 | 275 | } |