From 60048a231f1409e21857c66a0651bdb397613a47 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Fri, 2 Jun 2017 10:26:54 -0700 Subject: [PATCH 01/24] god view controller --- scripts/system/controllers/godView.js | 117 ++++++++++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 scripts/system/controllers/godView.js diff --git a/scripts/system/controllers/godView.js b/scripts/system/controllers/godView.js new file mode 100644 index 0000000000..f4990b7921 --- /dev/null +++ b/scripts/system/controllers/godView.js @@ -0,0 +1,117 @@ +"use strict"; +// +// godView.js +// scripts/system/ +// +// Created by Brad Hefta-Gaub on 1 Jun 2017 +// 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 +// +/* globals HMD, Script, Menu, Tablet, Camera */ +/* eslint indent: ["error", 4, { "outerIIFEBody": 0 }] */ + +(function() { // BEGIN LOCAL_SCOPE + +var godView = false; + +var GOD_CAMERA_OFFSET = -1; // 1 meter below the avatar +var GOD_VIEW_HEIGHT = 300; // 300 meter above the ground +var ABOVE_GROUND_DROP = 2; +var MOVE_BY = 1; + +function moveTo(position) { + if (godView) { + MyAvatar.position = position; + Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_CAMERA_OFFSET, z: 0}); + } else { + MyAvatar.position = position; + } +} + +function keyPressEvent(event) { + if (godView) { + switch(event.text) { + case "UP": + moveTo(Vec3.sum(MyAvatar.position, {x:0.0, y: 0, z: -1 * MOVE_BY})); + break; + case "DOWN": + moveTo(Vec3.sum(MyAvatar.position, {x:0, y: 0, z: MOVE_BY})); + break; + case "LEFT": + moveTo(Vec3.sum(MyAvatar.position, {x:-1 * MOVE_BY, y: 0, z: 0})); + break; + case "RIGHT": + moveTo(Vec3.sum(MyAvatar.position, {x:MOVE_BY, y: 0, z: 0})); + break; + } + } +} + +function mousePress(event) { + if (godView) { + var pickRay = Camera.computePickRay(event.x, event.y); + Vec3.print("pr.o:", pickRay.origin); + Vec3.print("pr.d:", pickRay.direction); + Vec3.print("c.p:", Camera.position); + var pointingAt = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction,300)); + var moveToPosition = { x: pointingAt.x, y: MyAvatar.position.y, z: pointingAt.z }; + moveTo(moveToPosition); + } +} + + +function startGodView() { + if (!godView) { + Camera.mode = "first person"; + MyAvatar.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_VIEW_HEIGHT, z: 0}); + Camera.mode = "independent"; + Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_CAMERA_OFFSET, z: 0}); + Camera.orientation = Quat.fromPitchYawRollDegrees(-90,0,0); + godView = true; + } +} + +function endGodView() { + if (godView) { + Camera.mode = "first person"; + MyAvatar.position = Vec3.sum(MyAvatar.position, {x:0, y: (-1 * GOD_VIEW_HEIGHT) + ABOVE_GROUND_DROP, z: 0}); + godView = false; + } +} + +var button; +var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + +function onClicked() { + if (godView) { + endGodView(); + } else { + startGodView(); + } +} + +button = tablet.addButton({ + icon: "icons/tablet-icons/switch-desk-i.svg", // FIXME - consider a better icon from Alan + text: "God View" +}); + +button.clicked.connect(onClicked); +Controller.keyPressEvent.connect(keyPressEvent); +Controller.mousePressEvent.connect(mousePress); + + +Script.scriptEnding.connect(function () { + if (godView) { + endGodView(); + } + button.clicked.disconnect(onClicked); + if (tablet) { + tablet.removeButton(button); + } + Controller.keyPressEvent.disconnect(keyPressEvent); + Controller.mousePressEvent.disconnect(mousePress); +}); + +}()); // END LOCAL_SCOPE From 86eb5b14ca977fe236d34f4d5a6df73c8a805b48 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 28 Jun 2017 13:18:34 -0700 Subject: [PATCH 02/24] disable the user activity logger in DS/AC/ac-client --- assignment-client/src/AssignmentClientApp.cpp | 6 +++++- domain-server/src/DomainServer.cpp | 8 ++++++-- tools/ac-client/src/ACClientApp.cpp | 4 ++++ 3 files changed, 15 insertions(+), 3 deletions(-) diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index 7e9042e609..91554d915b 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -13,9 +13,10 @@ #include #include -#include #include +#include #include +#include #include "Assignment.h" #include "AssignmentClient.h" @@ -207,6 +208,9 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : DependencyManager::registerInheritance(); + // the ACs should not send any user activity events so disable the logger ASAP + UserActivityLogger::getInstance().disable(true); + if (numForks || minForks || maxForks) { AssignmentClientMonitor* monitor = new AssignmentClientMonitor(numForks, minForks, maxForks, requestAssignmentType, assignmentPool, diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8e3d04897b..545484d79d 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -40,11 +40,12 @@ #include #include #include +#include +#include +#include #include "DomainServerNodeData.h" #include "NodeConnectionData.h" -#include -#include int const DomainServer::EXIT_CODE_REBOOT = 234923; @@ -75,6 +76,9 @@ DomainServer::DomainServer(int argc, char* argv[]) : { parseCommandLine(); + // the DS should not send any user activity events so disable the logger ASAP + UserActivityLogger::getInstance().disable(true); + DependencyManager::set(); DependencyManager::set(); diff --git a/tools/ac-client/src/ACClientApp.cpp b/tools/ac-client/src/ACClientApp.cpp index b81d092662..34b7d5b049 100644 --- a/tools/ac-client/src/ACClientApp.cpp +++ b/tools/ac-client/src/ACClientApp.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include "ACClientApp.h" @@ -42,6 +43,8 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : const QCommandLineOption listenPortOption("listenPort", "listen port", QString::number(INVALID_PORT)); parser.addOption(listenPortOption); + // the AC client should not send any user activity events so disable the logger ASAP + UserActivityLogger::getInstance().disable(true); if (!parser.parse(QCoreApplication::arguments())) { qCritical() << parser.errorText() << endl; @@ -66,6 +69,7 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : const_cast(&shared())->setEnabled(QtInfoMsg, false); const_cast(&shared())->setEnabled(QtWarningMsg, false); } + QString domainServerAddress = "127.0.0.1:40103"; if (parser.isSet(domainAddressOption)) { From cf921447489d06aa82f183049519d63804c5e7f7 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Wed, 28 Jun 2017 14:13:09 -0700 Subject: [PATCH 03/24] fix for OAuth OPTIONS firing on XHR 302 --- domain-server/resources/web/js/tables.js | 42 ++++++++++++++++-------- domain-server/src/DomainServer.cpp | 29 ++++++++++------ 2 files changed, 47 insertions(+), 24 deletions(-) diff --git a/domain-server/resources/web/js/tables.js b/domain-server/resources/web/js/tables.js index 09f85a7047..600e5a5f8e 100644 --- a/domain-server/resources/web/js/tables.js +++ b/domain-server/resources/web/js/tables.js @@ -2,11 +2,11 @@ $(document).ready(function(){ // setup the underscore templates var nodeTemplate = _.template($('#nodes-template').html()); var queuedTemplate = _.template($('#queued-template').html()); - + // setup a function to grab the assignments function getNodesAndAssignments() { $.getJSON("nodes.json", function(json){ - + json.nodes.sort(function(a, b){ if (a.type === b.type) { if (a.uptime < b.uptime) { @@ -16,36 +16,50 @@ $(document).ready(function(){ } else { return 0; } - } - + } + if (a.type === "agent" && b.type !== "agent") { return 1; } else if (b.type === "agent" && a.type !== "agent") { return -1; } - + if (a.type > b.type) { return 1; } - + if (a.type < b.type) { return -1; - } + } }); - + $('#nodes-table tbody').html(nodeTemplate(json)); + }).fail(function(jqXHR, textStatus, errorThrown) { + // we assume a 401 means the DS has restarted + // and no longer has our OAuth produced uuid + // so just reload and re-auth + if (jqXHR.status == 401) { + location.reload(); + } }); - - $.getJSON("assignments.json", function(json){ + + $.getJSON("assignments.json", function(json){ $('#assignments-table tbody').html(queuedTemplate(json)); + }).fail(function(jqXHR, textStatus, errorThrown) { + // we assume a 401 means the DS has restarted + // and no longer has our OAuth produced uuid + // so just reload and re-auth + if (jqXHR.status == 401) { + location.reload(); + } }); } - + // do the first GET on page load getNodesAndAssignments(); // grab the new assignments JSON every two seconds var getNodesAndAssignmentsInterval = setInterval(getNodesAndAssignments, 2000); - + // hook the node delete to the X button $(document.body).on('click', '.glyphicon-remove', function(){ // fire off a delete for this node @@ -57,10 +71,10 @@ $(document).ready(function(){ } }); }); - + $(document.body).on('click', '#kill-all-btn', function() { var confirmed_kill = confirm("Are you sure?"); - + if (confirmed_kill == true) { $.ajax({ url: "/nodes/", diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 8e3d04897b..9303bed2b5 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -2091,22 +2091,31 @@ bool DomainServer::isAuthenticatedRequest(HTTPConnection* connection, const QUrl // the user does not have allowed username or role, return 401 return false; } else { - // re-direct this user to OAuth page + static const QByteArray REQUESTED_WITH_HEADER = "X-Requested-With"; + static const QString XML_REQUESTED_WITH = "XMLHttpRequest"; - // generate a random state UUID to use - QUuid stateUUID = QUuid::createUuid(); + if (connection->requestHeaders().value(REQUESTED_WITH_HEADER) == XML_REQUESTED_WITH) { + // unauthorized XHR requests get a 401 and not a 302, since there isn't an XHR + // path to OAuth authorize + connection->respond(HTTPConnection::StatusCode401, UNAUTHENTICATED_BODY); + } else { + // re-direct this user to OAuth page - // add it to the set so we can handle the callback from the OAuth provider - _webAuthenticationStateSet.insert(stateUUID); + // generate a random state UUID to use + QUuid stateUUID = QUuid::createUuid(); - QUrl authURL = oauthAuthorizationURL(stateUUID); + // add it to the set so we can handle the callback from the OAuth provider + _webAuthenticationStateSet.insert(stateUUID); - Headers redirectHeaders; + QUrl authURL = oauthAuthorizationURL(stateUUID); - redirectHeaders.insert("Location", authURL.toEncoded()); + Headers redirectHeaders; - connection->respond(HTTPConnection::StatusCode302, - QByteArray(), HTTPConnection::DefaultContentType, redirectHeaders); + redirectHeaders.insert("Location", authURL.toEncoded()); + + connection->respond(HTTPConnection::StatusCode302, + QByteArray(), HTTPConnection::DefaultContentType, redirectHeaders); + } // we don't know about this user yet, so they are not yet authenticated return false; From 7b79e8c41ede7880ab879e47a300702d5b8351d2 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 5 Jul 2017 13:53:04 -0700 Subject: [PATCH 04/24] Update describe-settings.json --- domain-server/resources/describe-settings.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index bc67a31c02..d9d6abc840 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -50,11 +50,12 @@ { "label": "Places / Paths", "html_id": "places_paths", + "restart": false, "settings": [ { "name": "paths", "label": "Paths", - "help": "Clients can enter a path to reach an exact viewpoint in your domain.
Add rows to the table below to map a path to a viewpoint.
The index path ( / ) is where clients will enter if they do not enter an explicit path.", + "help": "Clients can enter a path to reach an exact viewpoint in your domain.
Add rows to the table below to map a path to a viewpoint.
The index path ( / ) is where clients will enter if they do not enter an explicit path.", "type": "table", "can_add_new_rows": true, "key": { From 1385ef80a7d961886c64e3a35091ef834b9a6df1 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 5 Jul 2017 13:56:40 -0700 Subject: [PATCH 05/24] Update DomainServerSettingsManager.cpp --- domain-server/src/DomainServerSettingsManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 9279648319..1bba9690c1 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1198,6 +1198,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ static const QString SECURITY_ROOT_KEY = "security"; static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; static const QString BROADCASTING_KEY = "broadcasting"; + static const QString PATHS_ROOT_KEY = "Places / Paths"; auto& settingsVariant = _configMap.getConfig(); bool needRestart = false; @@ -1249,7 +1250,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { updateSetting(rootKey, rootValue, *thisMap, matchingDescriptionObject); - if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY) { + if (rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != SETTINGS_PATHS_KEY ) { needRestart = true; } } else { From c069c13ebb29a2ff7fb2863194513770398f2cd6 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 5 Jul 2017 14:12:18 -0700 Subject: [PATCH 06/24] Update DomainServerSettingsManager.cpp --- domain-server/src/DomainServerSettingsManager.cpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 1bba9690c1..e0054a5a00 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1198,8 +1198,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ static const QString SECURITY_ROOT_KEY = "security"; static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; static const QString BROADCASTING_KEY = "broadcasting"; - static const QString PATHS_ROOT_KEY = "Places / Paths"; - + auto& settingsVariant = _configMap.getConfig(); bool needRestart = false; From 872751bb1611d2b480bede9592e27efe3ec7dc50 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 5 Jul 2017 14:30:48 -0700 Subject: [PATCH 07/24] Update describe-settings.json --- domain-server/resources/describe-settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index d9d6abc840..638c52a64d 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -50,12 +50,12 @@ { "label": "Places / Paths", "html_id": "places_paths", - "restart": false, + "restart": false, "settings": [ { "name": "paths", "label": "Paths", - "help": "Clients can enter a path to reach an exact viewpoint in your domain.
Add rows to the table below to map a path to a viewpoint.
The index path ( / ) is where clients will enter if they do not enter an explicit path.", + "help": "Clients can enter a path to reach an exact viewpoint in your domain.
Add rows to the table below to map a path to a viewpoint.
The index path ( / ) is where clients will enter if they do not enter an explicit path.", "type": "table", "can_add_new_rows": true, "key": { From 25773fcf5ab26ae25a7dc1367ea1d782c66ee230 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 5 Jul 2017 14:36:17 -0700 Subject: [PATCH 08/24] Update describe-settings.json --- domain-server/resources/describe-settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index 638c52a64d..f42b56b56d 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -50,12 +50,12 @@ { "label": "Places / Paths", "html_id": "places_paths", - "restart": false, + "restart": false, "settings": [ { "name": "paths", "label": "Paths", - "help": "Clients can enter a path to reach an exact viewpoint in your domain.
Add rows to the table below to map a path to a viewpoint.
The index path ( / ) is where clients will enter if they do not enter an explicit path.", + "help": "Clients can enter a path to reach an exact viewpoint in your domain.
Add rows to the table below to map a path to a viewpoint.
The index path ( / ) is where clients will enter if they do not enter an explicit path.", "type": "table", "can_add_new_rows": true, "key": { From f824290a4cdc5ec34ce9a372afae7ff477989fa5 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 5 Jul 2017 14:38:06 -0700 Subject: [PATCH 09/24] Update describe-settings.json --- domain-server/resources/describe-settings.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index f42b56b56d..638c52a64d 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -50,12 +50,12 @@ { "label": "Places / Paths", "html_id": "places_paths", - "restart": false, + "restart": false, "settings": [ { "name": "paths", "label": "Paths", - "help": "Clients can enter a path to reach an exact viewpoint in your domain.
Add rows to the table below to map a path to a viewpoint.
The index path ( / ) is where clients will enter if they do not enter an explicit path.", + "help": "Clients can enter a path to reach an exact viewpoint in your domain.
Add rows to the table below to map a path to a viewpoint.
The index path ( / ) is where clients will enter if they do not enter an explicit path.", "type": "table", "can_add_new_rows": true, "key": { From 6c15dffb9d0d3737aaae3cac9af01d4061ae3b5b Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 6 Jul 2017 00:45:59 +0100 Subject: [PATCH 10/24] added new glyph --- interface/resources/fonts/hifi-glyphs.ttf | Bin 28952 -> 29160 bytes .../resources/images/calibration-help.png | Bin 0 -> 50683 bytes .../qml/controls-uit/ImageMessageBox.qml | 32 ++++++++++++++++++ .../qml/hifi/tablet/CalibratingScreen.qml | 2 +- .../qml/hifi/tablet/OpenVrConfiguration.qml | 23 +++++++++++++ .../qml/styles-uit/HifiConstants.qml | 1 + 6 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 interface/resources/images/calibration-help.png create mode 100644 interface/resources/qml/controls-uit/ImageMessageBox.qml diff --git a/interface/resources/fonts/hifi-glyphs.ttf b/interface/resources/fonts/hifi-glyphs.ttf index ddb0743bf84edd1b9d4d0ae538cee3e050ff201e..8733349227646b207ec15315e9eab9ee7ae9e252 100644 GIT binary patch delta 760 zcmYL{O=uHA6vzK>c6YLyO_MgZnY}y2^ zhuX9rdhz65Ja`bl4kAU&L6Cws@lX*3J&6>(1i_1-NChLC1{@gP@4?J_??3;!{e?9*v$ndUasvE-=3dplNh?B#L!#VEze!w%BB6Ft#y14bT|?r>8PTc7eZp z4X_P>9ZMLwY`F{pn1293PNc70QkO=z0bUDCUr5G{X-CV}8vvgGgp1V|2m#Eh|+#b-JPP>(gxWg`>M1eTrwsjPJ;JD1CM zZ2tZTq}u@LMnxO}3Hg+C-gWpVR0eB(D8x!Tk;O{4sGnBM0Xj>q#XHnYp|Q#t0jTVz zN)gZz2o-9ANCzx+kg-$k`c`YP#5_cW;$Oizb{eH*lX+xixb~0B2>JpvIG_1s?4F1)7+lf zqmSc|^+ciq5gOL3EJ4HjDsyP~W%kXsn~Btf6hZ(I^JQwk5DG-h@2Q+=%(l_W{nc4w zv{YP8Pl%H>#uX!PEClj_>|8Ex`9kF~Sf#fUKx4%!D;H1zK?JI%Sl=3({rBEl@xR}( F(myDUigEw| delta 553 zcmXw%F=!J}7{`C#sBD7_0f(K=%4+U+HVg6!B<(FX z?UAb71VabWI`*p17pa3BR}EdaJFR%?-8@`-4ifpx!OD86I0fzk7(pT@;N|ZRHTIwU zgQcO?)?z*GpISYBAk)$fPstS-iyq0Slu!oA&LsgzjZ{K8beT|Qr0FN!*&d62X~Pm3 zdPc%S#*PNj0rP1yywN^VC~&WzfR4q70UKDv12(bbVEDd0UO2}IRYl*6PL|gLHpsFy zV3RC6VZ$8`N8QGrNy-$dgm>KueX?AvgfHD7%zNX~eSF%JsM@)p&nQRf_0W=P8DJ#w aSN9a^?R6NKsDHr6>Gh5NVEzxN9{&S4W^f7s diff --git a/interface/resources/images/calibration-help.png b/interface/resources/images/calibration-help.png new file mode 100644 index 0000000000000000000000000000000000000000..e3734a7d9c9fbc6ed450b136ce1cd71c16cd3852 GIT binary patch literal 50683 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%0AgD`I+yh72OZXOcS3 zw7)+O*A5J}S-j2@?|K(xEud+rnk>No+y7T<@M>dpV7_~%nFI}&w<_KK`8}O_OPTb? zw)`K@5A1G!&cGmm12POWmQ2Iw64KImI9=d%xLK6yfBsilqnh@#QhDjg`@*Ar!J%i@ zqjVNeu1h-Hex*udmoOPHzuiW!M;s-}>r}nwAbeW29@ss8*~a_%RY7&1f-DZ3bt*$lhHR|0r>0O$uyW6ZAF7K+z;z;XR`b5Ir&fdZX zRGPLt%auG+8nPee;|WVSYfkkF;C%k2ktVQv=N!51aZP+P~kHtj!noor%RHiFua6 zmH~EdZo%)iBmF<5msfJr1Sl^>LI|807}lwJ{tiV+Q^{b)g7ZIHGhH>DIer(-ui*D^ zLSsf_L*947(XDd6D1Pu@esDoF&8k>zTyKO!I6s$DLL*?Ih=~ zDCHI$+l%B~w!iMEQ~GRQ!Di-n?@)Y!q(=QaPThKmmojnmcZk-%>|bV;$DbdMH0OMM z`)Ry!=Mq!9fP|!QflxHUGU*`tAr~(hks5?RUVzk6u=JCV%?UQ5!;y!Cx#=Y*WudlR z?>>(Z5QHulmiK7#X)eY!_0pQSZlt_E^EB&K+y9($Y4CH(*x|335vf0oSChpKphrT; zVdzE=nuwPTqDdmTEtVi@erp$=ZD>oSaR2F#kIa!-qPA(Lao5jIeG0jGTlFZ_*0`Ggc01{(ih!GINe=j+>Eic_9TDZ*3ytNjn% zYF60tI|zMUhP$<&EP0pYyJ_8ii0LBsc)6oWug6T zzRYV9*aVYDPb$5`w8T1XtgX0ZndCb6HPkcl`QSdqP{opd{*$E`?Z$T-+5@+reVpQ$ z*14iwo!7Xgmp9clqaixutx9*wc*1llMc11qX&O43g+Ty)Fv**CiSbhec0CHl6Dy-e0Rc2^s%v6>pr=BYEKid#`VeO*4g^PkV1QYSeJHvt1{k2#Wgt-$Kyv zOSp`T3?bJ{1A5CkrDOVzB^g4K)1+1m`W5W137?b zMdz+NXLl1YaF14JNpc2@y7`|TGC=xV&YRGDfiz=*P!%+6^=>yy0AHn#_Z#9lH68jS~=eo(hZFfuA=R6ojQQ0)CvFoZ>G&f6la@s1l~; zXOMyXK(yj^*In_eIBdg7uJ_@l%^06JP2w@J#xTkL`j}S7FzwDN4z>dGJ5EnJ?tUGL z*?4Hg&39EEhX6C!u$VB3E3#&Hk@`fip!s82S(&or6%yKyt|t3U9wwEk4J@_p8{^8; z_omXNE)9KpA~TewMD8}Ip^%;P-Js(6he+Yy)Nm$NShc2M*!Rv)mJ3#!leN0vDVgu+ zU;J;!;6;wT2%5W$gMTImFo{Xo^Pw;SfDTE;6fMg@0PK$qCV#nBRDD&@97o_8{+|c` zojN?ezdVQ~bt3Nw@_bmMq zIs_Ie)0QdHrwKP)+7&JIX&o=!HliNG}MWvTCei&oR&k zkgI;3@0quRWjoq!tMO2TMiM6jMZzXT3*8lRkWA5Re|0F|WvQoUzrk^)33fp%H!=^_ z&Jt1sysRxK&4`$XBM_b$o{FnE-xlSO-}U7cu~zxBUU^#KIQ{_DtFYuF+WMFf7lf`k zUM(1ky{lEYX@7RSKg8{~P2_es;-C+*B|2%8@CHDDkJp zTek!q3Sp+)ZN%6k`(UD+3j@INFBDIJHZI^23IjQSn+>~a`#)u_%S{)NGRc4ZAoDuE zptw%CW0@2_TWJWOF^w^pn9EUs? zOiU*4NV=^K<_R4X^FRNI>2TF|b}qGy|8FuU%vyc9?Ph+2|{*5`bcksn{m? zk5e0qB?pg|BFU{875{N=V-etg`E|Mf-F^>Y|1ppf{`-pm^BvHNYFVMszwU1yB?Qhg zTA=B_yx(Z20K`9zQs#f%;QujTf2aC)TMdnYgx|6T1R?&jr-c~_Mk|dU!Cr6JKi)zJ zpqSbEW%2OGgZCuBu_sD2rvGjWNg;3xvPmd1;J^07z%FjKKJ literal 0 HcmV?d00001 diff --git a/interface/resources/qml/controls-uit/ImageMessageBox.qml b/interface/resources/qml/controls-uit/ImageMessageBox.qml new file mode 100644 index 0000000000..492f520d2a --- /dev/null +++ b/interface/resources/qml/controls-uit/ImageMessageBox.qml @@ -0,0 +1,32 @@ +// +// ImageMessageBox.qml +// +// Created by Dante Ruiz on 7/5/2017 +// 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 +// + +import QtQuick 2.5 +import QtQuick.Controls 1.4 +import "../styles-uit" + +Item { + id: imageBox + visible: false + anchors.fill: parent + property alias source: image.source + property alias imageWidth: image.width + proeprty alias imageHeight: image.height + Rectangle { + acnhors.fill: parent + color: "black" + opacity: 0.5 + } + + Image { + id: image + } + +} diff --git a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml index 8053673e9c..dd56bc96ab 100644 --- a/interface/resources/qml/hifi/tablet/CalibratingScreen.qml +++ b/interface/resources/qml/hifi/tablet/CalibratingScreen.qml @@ -65,7 +65,7 @@ Rectangle { HiFiGlyphs { id: image - text: hifi.glyphs.avatar1 + text: hifi.glyphs.avatarTPose size: 190 color: hifi.colors.white diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index 96413534c3..2fe8a69321 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -290,6 +290,27 @@ Rectangle { anchors.leftMargin: leftMargin } + RalewayRegular { + id: info + + text: "See Recommended Tracker Placement" + color: hifi.colors.blueHighlight + size: 10 + anchors { + left: additional.right + leftMargin: 10 + verticalCenter: additional.verticalCenter + } + + MouseArea { + anchors.fill: parent; + + onEntered: info.color = hifi.colors.blueAccent + onExited: info.color = hifi.colors.blueHighlight + onClicked: console.log("text clicked"); + } + } + Row { id: feetConfig anchors.top: additional.bottom @@ -379,6 +400,7 @@ Rectangle { if (checked) { hipBox.checked = true; feetBox.checked = true; + shoulderBox.checked = false; } sendConfigurationSettings(); } @@ -416,6 +438,7 @@ Rectangle { if (checked) { hipBox.checked = true; feetBox.checked = true; + chestBox.checked = false; } sendConfigurationSettings(); } diff --git a/interface/resources/qml/styles-uit/HifiConstants.qml b/interface/resources/qml/styles-uit/HifiConstants.qml index ca39326102..aa968c85ef 100644 --- a/interface/resources/qml/styles-uit/HifiConstants.qml +++ b/interface/resources/qml/styles-uit/HifiConstants.qml @@ -336,5 +336,6 @@ Item { readonly property string source: "\ue01c" readonly property string playback_play: "\ue01d" readonly property string stop_square: "\ue01e" + readonly property string avatarTPose: "\ue01f" } } From 9b7a561c28abe764bed04dc0fcc5f36f3acffc87 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 5 Jul 2017 16:56:21 -0700 Subject: [PATCH 11/24] Update describe-settings.json --- domain-server/resources/describe-settings.json | 1 + 1 file changed, 1 insertion(+) diff --git a/domain-server/resources/describe-settings.json b/domain-server/resources/describe-settings.json index bc67a31c02..e8b5bb2878 100644 --- a/domain-server/resources/describe-settings.json +++ b/domain-server/resources/describe-settings.json @@ -75,6 +75,7 @@ { "name": "descriptors", "label": "Description", + "restart": false, "help": "This data will be queryable from your server. It may be collected by High Fidelity and used to share your domain with others.", "settings": [ { From 8c96d00f14bf1bb7677bf7b5e1e8285da3f96418 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 5 Jul 2017 16:59:47 -0700 Subject: [PATCH 12/24] Update DomainServerSettingsManager.cpp --- domain-server/src/DomainServerSettingsManager.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainServerSettingsManager.cpp b/domain-server/src/DomainServerSettingsManager.cpp index 9279648319..138ed7d8e8 100644 --- a/domain-server/src/DomainServerSettingsManager.cpp +++ b/domain-server/src/DomainServerSettingsManager.cpp @@ -1198,6 +1198,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ static const QString SECURITY_ROOT_KEY = "security"; static const QString AC_SUBNET_WHITELIST_KEY = "ac_subnet_whitelist"; static const QString BROADCASTING_KEY = "broadcasting"; + static const QString DESCRIPTION_ROOT_KEY = "descriptors"; auto& settingsVariant = _configMap.getConfig(); bool needRestart = false; @@ -1265,7 +1266,7 @@ bool DomainServerSettingsManager::recurseJSONObjectAndOverwriteSettings(const QJ if (!matchingDescriptionObject.isEmpty()) { const QJsonValue& settingValue = rootValue.toObject()[settingKey]; updateSetting(settingKey, settingValue, *thisMap, matchingDescriptionObject); - if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY) + if ((rootKey != SECURITY_ROOT_KEY && rootKey != BROADCASTING_KEY && rootKey != DESCRIPTION_ROOT_KEY) || settingKey == AC_SUBNET_WHITELIST_KEY) { needRestart = true; } From 9d2fe8da9c1a6ef4fdf7516a54b7f3b63f8ef469 Mon Sep 17 00:00:00 2001 From: utkarshgautamnyu Date: Wed, 5 Jul 2017 18:02:44 -0700 Subject: [PATCH 13/24] Update DomainServer.cpp --- domain-server/src/DomainServer.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 095613a473..94a77af4ab 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -162,8 +162,10 @@ DomainServer::DomainServer(int argc, char* argv[]) : _gatekeeper.preloadAllowedUserPublicKeys(); // so they can connect on first request + //send signal to DomainMetadata when descriptors changed _metadata = new DomainMetadata(this); - + connect(&_settingsManager, &DomainServerSettingsManager::settingsUpdated, + _metadata, &DomainMetadata::descriptorsChanged); qDebug() << "domain-server is running"; static const QString AC_SUBNET_WHITELIST_SETTING_PATH = "security.ac_subnet_whitelist"; From 258c21838e48d149742b9521b713f0f2a2038fad Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 6 Jul 2017 17:32:40 +0100 Subject: [PATCH 14/24] finished calibration-ui updates --- .../qml/controls-uit/ImageMessageBox.qml | 38 +++++++- .../qml/hifi/tablet/ControllerSettings.qml | 24 +++-- .../qml/hifi/tablet/OpenVrConfiguration.qml | 89 ++++++++++++------- 3 files changed, 111 insertions(+), 40 deletions(-) diff --git a/interface/resources/qml/controls-uit/ImageMessageBox.qml b/interface/resources/qml/controls-uit/ImageMessageBox.qml index 492f520d2a..95c753aab4 100644 --- a/interface/resources/qml/controls-uit/ImageMessageBox.qml +++ b/interface/resources/qml/controls-uit/ImageMessageBox.qml @@ -18,15 +18,47 @@ Item { anchors.fill: parent property alias source: image.source property alias imageWidth: image.width - proeprty alias imageHeight: image.height + property alias imageHeight: image.height + Rectangle { - acnhors.fill: parent + anchors.fill: parent color: "black" - opacity: 0.5 + opacity: 0.3 } Image { id: image + anchors.centerIn: parent + + HiFiGlyphs { + id: closeGlyphButton + text: hifi.glyphs.close + size: 25 + + anchors { + top: parent.top + topMargin: 15 + right: parent.right + rightMargin: 15 + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onEntered: { + parent.text = hifi.glyphs.closeInverted; + } + + onExited: { + parent.text = hifi.glyphs.close; + } + + onClicked: { + imageBox.visible = false; + } + } + } } } diff --git a/interface/resources/qml/hifi/tablet/ControllerSettings.qml b/interface/resources/qml/hifi/tablet/ControllerSettings.qml index e1ba93a840..4814eaf01c 100644 --- a/interface/resources/qml/hifi/tablet/ControllerSettings.qml +++ b/interface/resources/qml/hifi/tablet/ControllerSettings.qml @@ -16,6 +16,7 @@ import "../../controls-uit" as HifiControls StackView { id: stack initialItem: inputConfiguration + property alias messageVisible: imageMessageBox.visible Rectangle { id: inputConfiguration anchors.fill: parent @@ -26,6 +27,15 @@ StackView { property var pluginSettings: null + HifiControls.ImageMessageBox { + id: imageMessageBox + anchors.fill: parent + z: 2000 + imageWidth: 442 + imageHeight: 670 + source: "../../../images/calibration-help.png" + } + Rectangle { width: inputConfiguration.width height: 1 @@ -167,7 +177,7 @@ StackView { loader.item.pluginName = box.currentText; } } - + if (loader.item.hasOwnProperty("displayInformation")) { loader.item.displayConfiguration(); } @@ -183,20 +193,20 @@ StackView { return InputConfiguration.activeInputPlugins(); } } - + function initialize() { changeSource(); } - + function changeSource() { loader.source = ""; var source = ""; if (box.currentText == "Vive") { source = InputConfiguration.configurationLayout("OpenVR"); - } else { + } else { source = InputConfiguration.configurationLayout(box.currentText); } - + loader.source = source; if (source === "") { box.label = "(not configurable)"; @@ -204,14 +214,14 @@ StackView { box.label = ""; } } - + Timer { id: timer repeat: false interval: 300 onTriggered: initialize() } - + Component.onCompleted: { timer.start(); } diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index 2fe8a69321..9db80df3cc 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -50,9 +50,12 @@ Rectangle { readonly property int apply: 1 readonly property int applyAndCalibrate: 2 readonly property int calibrate: 3 - + } - + + + + MouseArea { id: mouseArea @@ -64,6 +67,7 @@ Rectangle { mouse.accepted = false; } } + color: hifi.colors.baseGray RalewayBold { @@ -162,14 +166,14 @@ Rectangle { stepSize: 0.0254 decimals: 4 colorScheme: hifi.colorSchemes.dark - + onEditingFinished: { sendConfigurationSettings(); } } } - - + + RalewayBold { id: hands @@ -245,7 +249,7 @@ Rectangle { anchors.left: openVrConfiguration.left anchors.leftMargin: leftMargin + 10 spacing: 10 - + HifiControls.SpinBox { id: handYOffset decimals: 4 @@ -269,7 +273,7 @@ Rectangle { stepSize: 0.0254 decimals: 4 colorScheme: hifi.colorSchemes.dark - + onEditingFinished: { sendConfigurationSettings(); } @@ -302,12 +306,37 @@ Rectangle { verticalCenter: additional.verticalCenter } + Rectangle { + id: selected + color: hifi.colors.blueHighlight + + width: info.width + height: 1 + + anchors { + top: info.bottom + topMargin: 1 + left: info.left + right: info.right + } + + visible: false + } + MouseArea { anchors.fill: parent; + hoverEnabled: true - onEntered: info.color = hifi.colors.blueAccent - onExited: info.color = hifi.colors.blueHighlight - onClicked: console.log("text clicked"); + onEntered: { + selected.visible = true; + } + + onExited: { + selected.visible = false; + } + onClicked: { + stack.messageVisible = true; + } } } @@ -486,7 +515,7 @@ Rectangle { anchors.leftMargin: leftMargin radius: hifi.buttons.radius - + gradient: Gradient { GradientStop { position: 0.2 @@ -502,7 +531,7 @@ Rectangle { } } } - + GradientStop { position: 1.0 color: { @@ -518,10 +547,10 @@ Rectangle { } } } - - + + HiFiGlyphs { id: glyphButton color: enabled ? hifi.buttons.textColor[calibrationButton.color] @@ -535,7 +564,7 @@ Rectangle { bottomMargin: 1 } } - + RalewayBold { id: calibrationText font.capitalization: Font.AllUppercase @@ -550,7 +579,7 @@ Rectangle { topMargin: 7 } } - + MouseArea { anchors.fill: parent @@ -572,19 +601,19 @@ Rectangle { } } } - + onPressed: { calibrationButton.pressed = true; } - + onReleased: { calibrationButton.pressed = false; } - + onEntered: { calibrationButton.hovered = true; } - + onExited: { calibrationButton.hovered = false; } @@ -690,14 +719,14 @@ Rectangle { calibratingScreen = screen.createObject(); stack.push(calibratingScreen); } - + if (status["calibrated"]) { calibrationScreen.success(); if (status["UI"]) { logAction("mocap_ui_success", status); } - + } else if (!status["calibrated"]) { calibrationScreen.failure(); @@ -809,11 +838,11 @@ Rectangle { var handOverride = handSetting["override"]; var settingsChanged = false; - + if (lastConfiguration["bodyConfiguration"] !== bodySetting) { settingsChanged = true; } - + var lastHead = lastConfiguration["headConfiguration"]; if (lastHead["override"] !== headOverride) { settingsChanged = true; @@ -823,13 +852,13 @@ Rectangle { if (lastHand["override"] !== handOverride) { settingsChanged = true; } - + if (settingsChanged) { if ((!handOverride) && (!headOverride) && (bodySetting === "None")) { state = buttonState.apply; } else { state = buttonState.applyAndCalibrate; - } + } } else { if (state == buttonState.apply) { state = buttonState.disabled; @@ -837,7 +866,7 @@ Rectangle { state = buttonState.calibrate; } } - + lastConfiguration = settings; } @@ -854,7 +883,7 @@ Rectangle { state = buttonState.disabled; } else { state = buttonState.calibrate; - } + } } function updateCalibrationButton() { @@ -920,11 +949,11 @@ Rectangle { "Y": handYOffset.value, "Z": handZOffset.value } - + var settingsObject = { "bodyConfiguration": trackerConfiguration, "headConfiguration": headObject, - "handConfiguration": handObject + "handConfiguration": handObject } return settingsObject; From 6e1f9d275cc6a741a6eb54f9bdc14d1a491bfc47 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 6 Jul 2017 17:54:06 +0100 Subject: [PATCH 15/24] chnaged default head offset --- interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index 9db80df3cc..b5698104d2 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -150,6 +150,7 @@ Rectangle { label: "Y: offset" minimumValue: -10 stepSize: 0.0254 + value: -0.05 colorScheme: hifi.colorSchemes.dark onEditingFinished: { @@ -165,6 +166,7 @@ Rectangle { minimumValue: -10 stepSize: 0.0254 decimals: 4 + value: -0.05 colorScheme: hifi.colorSchemes.dark onEditingFinished: { From 916f57772bc1767168127756960f81d9a5573003 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 6 Jul 2017 10:21:07 -0700 Subject: [PATCH 16/24] default the UAL off for all targets, enable manually for Interface --- assignment-client/src/AssignmentClientApp.cpp | 4 - domain-server/src/DomainServer.cpp | 4 - interface/src/Application.cpp | 108 ++++++++++-------- libraries/networking/src/UserActivityLogger.h | 3 +- tools/ac-client/src/ACClientApp.cpp | 4 - 5 files changed, 61 insertions(+), 62 deletions(-) diff --git a/assignment-client/src/AssignmentClientApp.cpp b/assignment-client/src/AssignmentClientApp.cpp index 91554d915b..ed8d0eb2d1 100644 --- a/assignment-client/src/AssignmentClientApp.cpp +++ b/assignment-client/src/AssignmentClientApp.cpp @@ -16,7 +16,6 @@ #include #include #include -#include #include "Assignment.h" #include "AssignmentClient.h" @@ -208,9 +207,6 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) : DependencyManager::registerInheritance(); - // the ACs should not send any user activity events so disable the logger ASAP - UserActivityLogger::getInstance().disable(true); - if (numForks || minForks || maxForks) { AssignmentClientMonitor* monitor = new AssignmentClientMonitor(numForks, minForks, maxForks, requestAssignmentType, assignmentPool, diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 545484d79d..504af97f80 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -42,7 +42,6 @@ #include #include #include -#include #include "DomainServerNodeData.h" #include "NodeConnectionData.h" @@ -76,9 +75,6 @@ DomainServer::DomainServer(int argc, char* argv[]) : { parseCommandLine(); - // the DS should not send any user activity events so disable the logger ASAP - UserActivityLogger::getInstance().disable(true); - DependencyManager::set(); DependencyManager::set(); diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9052f082dc..85f9469933 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -950,58 +950,68 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo // Make sure we don't time out during slow operations at startup updateHeartbeat(); - - // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. - // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. - static const QString TESTER = "HIFI_TESTER"; - auto gpuIdent = GPUIdent::getInstance(); - auto glContextData = getGLContextData(); - QJsonObject properties = { - { "version", applicationVersion() }, - { "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) }, - { "previousSessionCrashed", _previousSessionCrashed }, - { "previousSessionRuntime", sessionRunTime.get() }, - { "cpu_architecture", QSysInfo::currentCpuArchitecture() }, - { "kernel_type", QSysInfo::kernelType() }, - { "kernel_version", QSysInfo::kernelVersion() }, - { "os_type", QSysInfo::productType() }, - { "os_version", QSysInfo::productVersion() }, - { "gpu_name", gpuIdent->getName() }, - { "gpu_driver", gpuIdent->getDriver() }, - { "gpu_memory", static_cast(gpuIdent->getMemory()) }, - { "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) }, - { "gl_version", glContextData["version"] }, - { "gl_vender", glContextData["vendor"] }, - { "gl_sl_version", glContextData["sl_version"] }, - { "gl_renderer", glContextData["renderer"] }, - { "ideal_thread_count", QThread::idealThreadCount() } - }; - auto macVersion = QSysInfo::macVersion(); - if (macVersion != QSysInfo::MV_None) { - properties["os_osx_version"] = QSysInfo::macVersion(); - } - auto windowsVersion = QSysInfo::windowsVersion(); - if (windowsVersion != QSysInfo::WV_None) { - properties["os_win_version"] = QSysInfo::windowsVersion(); - } - - ProcessorInfo procInfo; - if (getProcessorInfo(procInfo)) { - properties["processor_core_count"] = procInfo.numProcessorCores; - properties["logical_processor_count"] = procInfo.numLogicalProcessors; - properties["processor_l1_cache_count"] = procInfo.numProcessorCachesL1; - properties["processor_l2_cache_count"] = procInfo.numProcessorCachesL2; - properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3; - } - - // add firstRun flag from settings to launch event Setting::Handle firstRun { Settings::firstRun, true }; - properties["first_run"] = firstRun.get(); - // add the user's machine ID to the launch event - properties["machine_fingerprint"] = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); + // once the settings have been loaded, check if we need to flip the default for UserActivityLogger + auto& userActivityLogger = UserActivityLogger::getInstance(); + if (!userActivityLogger.isDisabledSettingSet()) { + // the user activity logger is opt-out for Interface + // but it's defaulted to disabled for other targets + // so we need to enable it here if it has never been disabled by the user + userActivityLogger.disable(false); + } - UserActivityLogger::getInstance().logAction("launch", properties); + if (userActivityLogger.isEnabled()) { + // sessionRunTime will be reset soon by loadSettings. Grab it now to get previous session value. + // The value will be 0 if the user blew away settings this session, which is both a feature and a bug. + static const QString TESTER = "HIFI_TESTER"; + auto gpuIdent = GPUIdent::getInstance(); + auto glContextData = getGLContextData(); + QJsonObject properties = { + { "version", applicationVersion() }, + { "tester", QProcessEnvironment::systemEnvironment().contains(TESTER) }, + { "previousSessionCrashed", _previousSessionCrashed }, + { "previousSessionRuntime", sessionRunTime.get() }, + { "cpu_architecture", QSysInfo::currentCpuArchitecture() }, + { "kernel_type", QSysInfo::kernelType() }, + { "kernel_version", QSysInfo::kernelVersion() }, + { "os_type", QSysInfo::productType() }, + { "os_version", QSysInfo::productVersion() }, + { "gpu_name", gpuIdent->getName() }, + { "gpu_driver", gpuIdent->getDriver() }, + { "gpu_memory", static_cast(gpuIdent->getMemory()) }, + { "gl_version_int", glVersionToInteger(glContextData.value("version").toString()) }, + { "gl_version", glContextData["version"] }, + { "gl_vender", glContextData["vendor"] }, + { "gl_sl_version", glContextData["sl_version"] }, + { "gl_renderer", glContextData["renderer"] }, + { "ideal_thread_count", QThread::idealThreadCount() } + }; + auto macVersion = QSysInfo::macVersion(); + if (macVersion != QSysInfo::MV_None) { + properties["os_osx_version"] = QSysInfo::macVersion(); + } + auto windowsVersion = QSysInfo::windowsVersion(); + if (windowsVersion != QSysInfo::WV_None) { + properties["os_win_version"] = QSysInfo::windowsVersion(); + } + + ProcessorInfo procInfo; + if (getProcessorInfo(procInfo)) { + properties["processor_core_count"] = procInfo.numProcessorCores; + properties["logical_processor_count"] = procInfo.numLogicalProcessors; + properties["processor_l1_cache_count"] = procInfo.numProcessorCachesL1; + properties["processor_l2_cache_count"] = procInfo.numProcessorCachesL2; + properties["processor_l3_cache_count"] = procInfo.numProcessorCachesL3; + } + + properties["first_run"] = firstRun.get(); + + // add the user's machine ID to the launch event + properties["machine_fingerprint"] = uuidStringWithoutCurlyBraces(FingerprintUtils::getMachineFingerprint()); + + userActivityLogger.logAction("launch", properties); + } // Tell our entity edit sender about our known jurisdictions _entityEditSender.setServerJurisdictions(&_entityServerJurisdictions); diff --git a/libraries/networking/src/UserActivityLogger.h b/libraries/networking/src/UserActivityLogger.h index 179e8e6e66..b44c60eba7 100644 --- a/libraries/networking/src/UserActivityLogger.h +++ b/libraries/networking/src/UserActivityLogger.h @@ -33,6 +33,7 @@ public: public slots: bool isEnabled() { return !_disabled.get(); } + bool isDisabledSettingSet() const { return _disabled.isSet(); } void disable(bool disable); void logAction(QString action, QJsonObject details = QJsonObject(), JSONCallbackParameters params = JSONCallbackParameters()); @@ -53,7 +54,7 @@ private slots: private: UserActivityLogger(); - Setting::Handle _disabled { "UserActivityLoggerDisabled", false }; + Setting::Handle _disabled { "UserActivityLoggerDisabled", true }; QElapsedTimer _timer; }; diff --git a/tools/ac-client/src/ACClientApp.cpp b/tools/ac-client/src/ACClientApp.cpp index 34b7d5b049..1a2e6e4049 100644 --- a/tools/ac-client/src/ACClientApp.cpp +++ b/tools/ac-client/src/ACClientApp.cpp @@ -18,7 +18,6 @@ #include #include #include -#include #include "ACClientApp.h" @@ -43,9 +42,6 @@ ACClientApp::ACClientApp(int argc, char* argv[]) : const QCommandLineOption listenPortOption("listenPort", "listen port", QString::number(INVALID_PORT)); parser.addOption(listenPortOption); - // the AC client should not send any user activity events so disable the logger ASAP - UserActivityLogger::getInstance().disable(true); - if (!parser.parse(QCoreApplication::arguments())) { qCritical() << parser.errorText() << endl; parser.showHelp(); From c3c9df0cd0dd521e37f99679b029647c437b53e5 Mon Sep 17 00:00:00 2001 From: ZappoMan Date: Thu, 6 Jul 2017 10:41:46 -0700 Subject: [PATCH 17/24] restore old camera state --- scripts/system/controllers/godView.js | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/scripts/system/controllers/godView.js b/scripts/system/controllers/godView.js index f4990b7921..4b406399fd 100644 --- a/scripts/system/controllers/godView.js +++ b/scripts/system/controllers/godView.js @@ -52,9 +52,6 @@ function keyPressEvent(event) { function mousePress(event) { if (godView) { var pickRay = Camera.computePickRay(event.x, event.y); - Vec3.print("pr.o:", pickRay.origin); - Vec3.print("pr.d:", pickRay.direction); - Vec3.print("c.p:", Camera.position); var pointingAt = Vec3.sum(pickRay.origin, Vec3.multiply(pickRay.direction,300)); var moveToPosition = { x: pointingAt.x, y: MyAvatar.position.y, z: pointingAt.z }; moveTo(moveToPosition); @@ -62,9 +59,11 @@ function mousePress(event) { } +var oldCameraMode = Camera.mode; + function startGodView() { if (!godView) { - Camera.mode = "first person"; + oldCameraMode = Camera.mode; MyAvatar.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_VIEW_HEIGHT, z: 0}); Camera.mode = "independent"; Camera.position = Vec3.sum(MyAvatar.position, {x:0, y: GOD_CAMERA_OFFSET, z: 0}); @@ -75,7 +74,7 @@ function startGodView() { function endGodView() { if (godView) { - Camera.mode = "first person"; + Camera.mode = oldCameraMode; MyAvatar.position = Vec3.sum(MyAvatar.position, {x:0, y: (-1 * GOD_VIEW_HEIGHT) + ABOVE_GROUND_DROP, z: 0}); godView = false; } From 4e3dba176734e72fc1b45b38a9cc7c15afbbd0ad Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 6 Jul 2017 19:12:52 +0100 Subject: [PATCH 18/24] goto HMD keyboard fix --- interface/resources/qml/hifi/tablet/TabletAddressDialog.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 073f143dbe..1a8b33c974 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -227,7 +227,7 @@ StackView { MouseArea { anchors.fill: parent; onClicked: { - if (!addressLine.focus || !HMD.active) { + if (!addressLine.focus || HMD.active) { addressLine.focus = true; addressLine.forceActiveFocus(); addressBarDialog.keyboardEnabled = HMD.active; From de5802418f98fa72059cfb76d878969c44d6ee87 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 6 Jul 2017 19:27:48 +0100 Subject: [PATCH 19/24] fixed some keybaord logic --- interface/resources/qml/hifi/tablet/TabletAddressDialog.qml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 1a8b33c974..9828b3e068 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -227,9 +227,9 @@ StackView { MouseArea { anchors.fill: parent; onClicked: { - if (!addressLine.focus || HMD.active) { - addressLine.focus = true; - addressLine.forceActiveFocus(); + addressLine.focus = true; + addressLine.forceActiveFocus(); + if (HMD.active) { addressBarDialog.keyboardEnabled = HMD.active; } tabletRoot.playButtonClickSound(); From bdcdff3dbc34aadaffdc75605f95f31ffd4d8516 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 6 Jul 2017 12:01:08 -0700 Subject: [PATCH 20/24] force _isSet to be set before isSet() called --- libraries/shared/src/SettingHandle.h | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/shared/src/SettingHandle.h b/libraries/shared/src/SettingHandle.h index 258d1f8491..341a4cb101 100644 --- a/libraries/shared/src/SettingHandle.h +++ b/libraries/shared/src/SettingHandle.h @@ -107,6 +107,7 @@ namespace Setting { } bool isSet() const { + maybeInit(); return _isSet; } From 2d876377d58071255f4dc0e9fa4150f98da13e9a Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 6 Jul 2017 20:04:44 +0100 Subject: [PATCH 21/24] keyboard know disappears when you click of the address line --- .../qml/hifi/tablet/TabletAddressDialog.qml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml index 9828b3e068..a02e79a5e2 100644 --- a/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml +++ b/interface/resources/qml/hifi/tablet/TabletAddressDialog.qml @@ -94,10 +94,20 @@ StackView { property bool keyboardEnabled: false property bool keyboardRaised: false property bool punctuationMode: false - + width: parent.width height: parent.height + MouseArea { + anchors.fill: parent + propagateComposedEvents: true + onPressed: { + parent.forceActiveFocus(); + addressBarDialog.keyboardEnabled = false; + mouse.accepted = false; + } + } + anchors { right: parent.right left: parent.left From a5c7324ec500bc044630684dffe1fc4abbded05e Mon Sep 17 00:00:00 2001 From: Zach Fox Date: Thu, 6 Jul 2017 12:15:44 -0700 Subject: [PATCH 22/24] Ensure admins see PAL admin column --- scripts/system/pal.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/system/pal.js b/scripts/system/pal.js index 2c81622668..c6cf4af0ad 100644 --- a/scripts/system/pal.js +++ b/scripts/system/pal.js @@ -482,10 +482,10 @@ function populateNearbyUserList(selectData, oldAudioData) { isPresent: true, isReplicated: avatar.isReplicated }; + // Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin. + Users.requestUsernameFromID(id); if (id) { addAvatarNode(id); // No overlay for ourselves - // Everyone needs to see admin status. Username and fingerprint returns default constructor output if the requesting user isn't an admin. - Users.requestUsernameFromID(id); avatarsOfInterest[id] = true; } else { // Return our username from the Account API From 1fcb3b32d60e921776f824529e9b4bd1b3030887 Mon Sep 17 00:00:00 2001 From: Dante Ruiz Date: Thu, 6 Jul 2017 20:55:19 +0100 Subject: [PATCH 23/24] fixed letter cap --- interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml index d6d37e6dae..90d6ba7022 100644 --- a/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml +++ b/interface/resources/qml/hifi/tablet/OpenVrConfiguration.qml @@ -737,7 +737,7 @@ Rectangle { RalewayBold { id: viveDesktopText size: 10 - text: "Use vive devices in desktop mode" + text: "Use Vive devices in desktop mode" color: hifi.colors.white anchors { From 6d98d20b129d5f69f1257e8f103898b6cb506b28 Mon Sep 17 00:00:00 2001 From: Stephen Birarda Date: Thu, 6 Jul 2017 16:15:23 -0700 Subject: [PATCH 24/24] cleanup bad QEventLoop::exec() OAuth handling code --- domain-server/src/DomainServer.cpp | 92 +++++++++++++++++++++--------- domain-server/src/DomainServer.h | 7 +++ 2 files changed, 71 insertions(+), 28 deletions(-) diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index 9303bed2b5..bf7a8e2f1e 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1959,7 +1959,8 @@ bool DomainServer::handleHTTPRequest(HTTPConnection* connection, const QUrl& url return _settingsManager.handleAuthenticatedHTTPRequest(connection, url); } -const QString HIFI_SESSION_COOKIE_KEY = "DS_WEB_SESSION_UUID"; +static const QString HIFI_SESSION_COOKIE_KEY = "DS_WEB_SESSION_UUID"; +static const QString STATE_QUERY_KEY = "state"; bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &url, bool skipSubHandler) { qDebug() << "HTTPS request received at" << url.toString(); @@ -1970,10 +1971,9 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u const QString CODE_QUERY_KEY = "code"; QString authorizationCode = codeURLQuery.queryItemValue(CODE_QUERY_KEY); - const QString STATE_QUERY_KEY = "state"; QUuid stateUUID = QUuid(codeURLQuery.queryItemValue(STATE_QUERY_KEY)); - if (!authorizationCode.isEmpty() && !stateUUID.isNull()) { + if (!authorizationCode.isEmpty() && !stateUUID.isNull() && _webAuthenticationStateSet.remove(stateUUID)) { // fire off a request with this code and state to get an access token for the user const QString OAUTH_TOKEN_REQUEST_PATH = "/oauth/token"; @@ -1991,47 +1991,83 @@ bool DomainServer::handleHTTPSRequest(HTTPSConnection* connection, const QUrl &u tokenRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); QNetworkReply* tokenReply = NetworkAccessManager::getInstance().post(tokenRequest, tokenPostBody.toLocal8Bit()); + connect(tokenReply, &QNetworkReply::finished, this, &DomainServer::tokenGrantFinished); - if (_webAuthenticationStateSet.remove(stateUUID)) { - // this is a web user who wants to auth to access web interface - // we hold the response back to them until we get their profile information - // and can decide if they are let in or not + // add this connection to our list of pending connections so that we can hold the response + _pendingOAuthConnections.insert(stateUUID, connection); - QEventLoop loop; - connect(tokenReply, &QNetworkReply::finished, &loop, &QEventLoop::quit); + // set the state UUID on the reply so that we can associate the response with the connection later + tokenReply->setProperty(STATE_QUERY_KEY.toLocal8Bit(), stateUUID); - // start the loop for the token request - loop.exec(); + return true; + } else { + connection->respond(HTTPConnection::StatusCode400); - QNetworkReply* profileReply = profileRequestGivenTokenReply(tokenReply); + return true; + } + } else { + return false; + } +} - // stop the loop once the profileReply is complete - connect(profileReply, &QNetworkReply::finished, &loop, &QEventLoop::quit); +HTTPSConnection* DomainServer::connectionFromReplyWithState(QNetworkReply* reply) { + // grab the UUID state property from the reply + QUuid stateUUID = reply->property(STATE_QUERY_KEY.toLocal8Bit()).toUuid(); - // restart the loop for the profile request - loop.exec(); + if (!stateUUID.isNull()) { + return _pendingOAuthConnections.take(stateUUID); + } else { + return nullptr; + } +} +void DomainServer::tokenGrantFinished() { + auto tokenReply = qobject_cast(sender()); + + if (tokenReply) { + if (tokenReply->error() == QNetworkReply::NoError) { + // now that we have a token for this profile, send off a profile request + QNetworkReply* profileReply = profileRequestGivenTokenReply(tokenReply); + + // forward along the state UUID that we kept with the token request + profileReply->setProperty(STATE_QUERY_KEY.toLocal8Bit(), tokenReply->property(STATE_QUERY_KEY.toLocal8Bit())); + + connect(profileReply, &QNetworkReply::finished, this, &DomainServer::profileRequestFinished); + } else { + // the token grant failed, send back a 500 (assuming the connection is still around) + auto connection = connectionFromReplyWithState(tokenReply); + + if (connection) { + connection->respond(HTTPConnection::StatusCode500); + } + } + + tokenReply->deleteLater(); + } +} + +void DomainServer::profileRequestFinished() { + + auto profileReply = qobject_cast(sender()); + + if (profileReply) { + auto connection = connectionFromReplyWithState(profileReply); + + if (connection) { + if (profileReply->error() == QNetworkReply::NoError) { // call helper method to get cookieHeaders Headers cookieHeaders = setupCookieHeadersFromProfileReply(profileReply); connection->respond(HTTPConnection::StatusCode302, QByteArray(), HTTPConnection::DefaultContentType, cookieHeaders); - delete tokenReply; - delete profileReply; - - // we've redirected the user back to our homepage - return true; - + } else { + // the profile request failed, send back a 500 (assuming the connection is still around) + connection->respond(HTTPConnection::StatusCode500); } } - // respond with a 200 code indicating that login is complete - connection->respond(HTTPConnection::StatusCode200); - - return true; - } else { - return false; + profileReply->deleteLater(); } } diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 8851e3380b..4808297c89 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -111,6 +111,9 @@ private slots: void updateDownstreamNodes(); void updateUpstreamNodes(); + void tokenGrantFinished(); + void profileRequestFinished(); + signals: void iceServerChanged(); void userConnected(); @@ -178,6 +181,8 @@ private: void updateReplicationNodes(ReplicationServerDirection direction); + HTTPSConnection* connectionFromReplyWithState(QNetworkReply* reply); + SubnetList _acSubnetWhitelist; std::vector _replicatedUsernames; @@ -235,6 +240,8 @@ private: bool _sendICEServerAddressToMetaverseAPIInProgress { false }; bool _sendICEServerAddressToMetaverseAPIRedo { false }; + + QHash> _pendingOAuthConnections; };