diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index ca428c88fe..8b73b851b2 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -519,7 +519,7 @@ void DomainServer::setupNodeListAndAssignments() { // add whatever static assignments that have been parsed to the queue addStaticAssignmentsToQueue(); - // set a custum packetVersionMatch as the verify packet operator for the udt::Socket + // set a custom packetVersionMatch as the verify packet operator for the udt::Socket nodeList->setPacketFilterOperator(&DomainServer::packetVersionMatch); } diff --git a/interface/resources/html/img/controls-help-gamepad.png b/interface/resources/html/img/controls-help-gamepad.png index 6887c88adb..e0c8c8901d 100644 Binary files a/interface/resources/html/img/controls-help-gamepad.png and b/interface/resources/html/img/controls-help-gamepad.png differ diff --git a/interface/resources/html/raiseAndLowerKeyboard.js b/interface/resources/html/raiseAndLowerKeyboard.js index aeca4dc112..3897c9ff3f 100644 --- a/interface/resources/html/raiseAndLowerKeyboard.js +++ b/interface/resources/html/raiseAndLowerKeyboard.js @@ -11,6 +11,7 @@ var POLL_FREQUENCY = 500; // ms var MAX_WARNINGS = 3; var numWarnings = 0; + var isWindowFocused = true; var isKeyboardRaised = false; var isNumericKeyboard = false; var KEYBOARD_HEIGHT = 200; @@ -38,15 +39,15 @@ var keyboardRaised = shouldRaiseKeyboard(); var numericKeyboard = shouldSetNumeric(); - if (keyboardRaised !== isKeyboardRaised || numericKeyboard !== isNumericKeyboard) { + if (isWindowFocused && (keyboardRaised !== isKeyboardRaised || numericKeyboard !== isNumericKeyboard)) { - if (typeof EventBridge !== "undefined") { + if (typeof EventBridge !== "undefined" && EventBridge !== null) { EventBridge.emitWebEvent( keyboardRaised ? ("_RAISE_KEYBOARD" + (numericKeyboard ? "_NUMERIC" : "")) : "_LOWER_KEYBOARD" ); } else { if (numWarnings < MAX_WARNINGS) { - console.log("WARNING: no global EventBridge object found"); + console.log("WARNING: No global EventBridge object found"); numWarnings++; } } @@ -65,4 +66,14 @@ isNumericKeyboard = numericKeyboard; } }, POLL_FREQUENCY); + + window.addEventListener("focus", function () { + isWindowFocused = true; + }); + + window.addEventListener("blur", function () { + isWindowFocused = false; + isKeyboardRaised = false; + isNumericKeyboard = false; + }); })(); diff --git a/interface/resources/qml/AddressBarDialog.qml b/interface/resources/qml/AddressBarDialog.qml index ebb29fd513..7cb1caf5c0 100644 --- a/interface/resources/qml/AddressBarDialog.qml +++ b/interface/resources/qml/AddressBarDialog.qml @@ -33,7 +33,10 @@ Window { width: addressBarDialog.implicitWidth height: addressBarDialog.implicitHeight - onShownChanged: addressBarDialog.observeShownChanged(shown); + onShownChanged: { + addressBarDialog.keyboardEnabled = HMD.active; + addressBarDialog.observeShownChanged(shown); + } Component.onCompleted: { root.parentChanged.connect(center); center(); @@ -61,7 +64,6 @@ Window { clearAddressLineTimer.start(); } property var allStories: []; - property int keyboardHeight: 200; property int cardWidth: 200; property int cardHeight: 152; property string metaverseBase: addressBarDialog.metaverseServerUrl + "/api/v1/"; @@ -70,10 +72,12 @@ Window { AddressBarDialog { id: addressBarDialog + property bool keyboardEnabled: false + property bool keyboardRaised: false property bool punctuationMode: false implicitWidth: backgroundImage.width - implicitHeight: backgroundImage.height + keyboardHeight + cardHeight; + implicitHeight: backgroundImage.height + (keyboardEnabled ? keyboard.raisedHeight : keyboardHeight) + cardHeight; // The buttons have their button state changed on hover, so we have to manually fix them up here onBackEnabledChanged: backArrow.buttonState = addressBarDialog.backEnabled ? 1 : 0; @@ -92,7 +96,7 @@ Window { spacing: hifi.layout.spacing; clip: true; anchors { - bottom: backgroundImage.top; + top: parent.top horizontalCenter: backgroundImage.horizontalCenter } model: suggestions; @@ -276,33 +280,15 @@ Window { } } - // virtual keyboard, letters HifiControls.Keyboard { - id: keyboard1 - y: parent.height - height: keyboardHeight - visible: !parent.punctuationMode - enabled: !parent.punctuationMode - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.top: backgroundImage.bottom - anchors.bottomMargin: 0 - } - - HifiControls.KeyboardPunctuation { - id: keyboard2 - y: parent.height - height: keyboardHeight - visible: parent.punctuationMode - enabled: parent.punctuationMode - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.top: backgroundImage.bottom - anchors.bottomMargin: 0 + id: keyboard + raised: parent.keyboardEnabled // Ignore keyboardRaised; keep keyboard raised if enabled (i.e., in HMD). + numeric: parent.punctuationMode + anchors { + top: backgroundImage.bottom + left: parent.left + right: parent.right + } } } diff --git a/interface/resources/qml/Browser.qml b/interface/resources/qml/Browser.qml index b258dadae4..869c0556fb 100644 --- a/interface/resources/qml/Browser.qml +++ b/interface/resources/qml/Browser.qml @@ -31,13 +31,6 @@ ScrollingWindow { addressBar.text = webview.url } - onParentChanged: { - if (visible) { - addressBar.forceActiveFocus(); - addressBar.selectAll() - } - } - function showPermissionsBar(){ permissionsContainer.visible=true; } diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 1f84024e15..08895ecaa1 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -33,6 +33,8 @@ ModalWindow { property string title: "" property int titleWidth: 0 + keyboardOverride: true // Disable ModalWindow's keyboard. + LoginDialog { id: loginDialog diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml index debd40a67b..e1d7a639da 100644 --- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml +++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml @@ -27,6 +27,7 @@ Item { loginDialog.login(usernameField.text, passwordField.text) } + property bool keyboardEnabled: false property bool keyboardRaised: false property bool punctuationMode: false @@ -46,7 +47,7 @@ Item { root.width = Math.max(d.minWidth, Math.min(d.maxWidth, targetWidth)); root.height = Math.max(d.minHeight, Math.min(d.maxHeight, targetHeight)) - + (linkAccountBody.keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y); + + (keyboardEnabled && keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : hifi.dimensions.contentSpacing.y); } } @@ -137,28 +138,13 @@ Item { // Override ScrollingWindow's keyboard that would be at very bottom of dialog. Keyboard { - y: parent.keyboardRaised ? parent.height : 0 - height: parent.keyboardRaised ? 200 : 0 - visible: parent.keyboardRaised && !parent.punctuationMode - enabled: parent.keyboardRaised && !parent.punctuationMode + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode anchors { left: parent.left right: parent.right bottom: buttons.top - bottomMargin: parent.keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0 - } - } - - KeyboardPunctuation { - y: parent.keyboardRaised ? parent.height : 0 - height: parent.keyboardRaised ? 200 : 0 - visible: parent.keyboardRaised && parent.punctuationMode - enabled: parent.keyboardRaised && parent.punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: buttons.top - bottomMargin: parent.keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0 + bottomMargin: keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0 } } @@ -195,9 +181,10 @@ Item { Component.onCompleted: { root.title = qsTr("Sign Into High Fidelity") root.iconText = "<" + keyboardEnabled = HMD.active; d.resize(); - usernameField.forceActiveFocus() + usernameField.forceActiveFocus(); } Connections { diff --git a/interface/resources/qml/ToolWindow.qml b/interface/resources/qml/ToolWindow.qml index 68c8099970..9c0b0a8c1a 100644 --- a/interface/resources/qml/ToolWindow.qml +++ b/interface/resources/qml/ToolWindow.qml @@ -42,6 +42,10 @@ ScrollingWindow { } } + onShownChanged: { + keyboardEnabled = HMD.active; + } + Settings { category: "ToolWindow.Position" property alias x: toolWindow.x diff --git a/interface/resources/qml/controls-uit/BaseWebView.qml b/interface/resources/qml/controls-uit/BaseWebView.qml index ef4764b08f..a5b724f113 100644 --- a/interface/resources/qml/controls-uit/BaseWebView.qml +++ b/interface/resources/qml/controls-uit/BaseWebView.qml @@ -25,8 +25,6 @@ WebEngineView { }); } - - // FIXME hack to get the URL with the auth token included. Remove when we move to Qt 5.6 Timer { id: urlReplacementTimer diff --git a/interface/resources/qml/controls-uit/Keyboard.qml b/interface/resources/qml/controls-uit/Keyboard.qml index 1d957ce9cb..07488fe3e6 100644 --- a/interface/resources/qml/controls-uit/Keyboard.qml +++ b/interface/resources/qml/controls-uit/Keyboard.qml @@ -1,10 +1,28 @@ +// +// FileDialog.qml +// +// Created by Anthony Thibault on 31 Oct 2016 +// 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 +// + import QtQuick 2.0 Item { id: keyboardBase - height: 200 - property alias shiftKey: key27 + + property bool raised: false + property bool numeric: false + + readonly property int raisedHeight: 200 + + height: enabled && raised ? raisedHeight : 0 + visible: enabled && raised + property bool shiftMode: false + property bool numericShiftMode: false function resetShiftMode(mode) { shiftMode = mode; @@ -37,8 +55,8 @@ Item { function forEachKey(func) { var i, j; - for (i = 0; i < column1.children.length; i++) { - var row = column1.children[i]; + for (i = 0; i < columnAlpha.children.length; i++) { + var row = columnAlpha.children[i]; for (j = 0; j < row.children.length; j++) { var key = row.children[j]; func(key); @@ -48,10 +66,12 @@ Item { onShiftModeChanged: { forEachKey(function (key) { - if (shiftMode) { - key.glyph = keyboardBase.toUpper(key.glyph); - } else { - key.glyph = keyboardBase.toLower(key.glyph); + if (/[a-z]/i.test(key.glyph)) { + if (shiftMode) { + key.glyph = keyboardBase.toUpper(key.glyph); + } else { + key.glyph = keyboardBase.toLower(key.glyph); + } } }); } @@ -97,265 +117,177 @@ Item { anchors.bottomMargin: 0 Column { - id: column1 + id: columnAlpha width: 480 height: 200 + visible: !numeric Row { - id: row1 width: 480 height: 50 anchors.left: parent.left - anchors.leftMargin: 0 + anchors.leftMargin: 4 - Key { - id: key1 - width: 44 - glyph: "q" - } - - Key { - id: key2 - width: 44 - glyph: "w" - } - - Key { - id: key3 - width: 44 - glyph: "e" - } - - Key { - id: key4 - width: 43 - glyph: "r" - } - - Key { - id: key5 - width: 43 - glyph: "t" - } - - Key { - id: key6 - width: 44 - glyph: "y" - } - - Key { - id: key7 - width: 44 - glyph: "u" - } - - Key { - id: key8 - width: 43 - glyph: "i" - } - - Key { - id: key9 - width: 42 - glyph: "o" - } - - Key { - id: key10 - width: 44 - glyph: "p" - } - - Key { - id: key28 - width: 45 - glyph: "←" - } + Key { width: 43; glyph: "q"; } + Key { width: 43; glyph: "w"; } + Key { width: 43; glyph: "e"; } + Key { width: 43; glyph: "r"; } + Key { width: 43; glyph: "t"; } + Key { width: 43; glyph: "y"; } + Key { width: 43; glyph: "u"; } + Key { width: 43; glyph: "i"; } + Key { width: 43; glyph: "o"; } + Key { width: 43; glyph: "p"; } + Key { width: 43; glyph: "←"; } } Row { - id: row2 width: 480 height: 50 anchors.left: parent.left - anchors.leftMargin: 18 + anchors.leftMargin: 20 - Key { - id: key11 - width: 43 - } - - Key { - id: key12 - width: 43 - glyph: "s" - } - - Key { - id: key13 - width: 43 - glyph: "d" - } - - Key { - id: key14 - width: 43 - glyph: "f" - } - - Key { - id: key15 - width: 43 - glyph: "g" - } - - Key { - id: key16 - width: 43 - glyph: "h" - } - - Key { - id: key17 - width: 43 - glyph: "j" - } - - Key { - id: key18 - width: 43 - glyph: "k" - } - - Key { - id: key19 - width: 43 - glyph: "l" - } - - Key { - id: key32 - width: 75 - glyph: "⏎" - } + Key { width: 43; glyph: "a"; } + Key { width: 43; glyph: "s"; } + Key { width: 43; glyph: "d"; } + Key { width: 43; glyph: "f"; } + Key { width: 43; glyph: "g"; } + Key { width: 43; glyph: "h"; } + Key { width: 43; glyph: "j"; } + Key { width: 43; glyph: "k"; } + Key { width: 43; glyph: "l"; } + Key { width: 70; glyph: "⏎"; } } Row { - id: row3 width: 480 height: 50 anchors.left: parent.left - anchors.leftMargin: 0 + anchors.leftMargin: 4 Key { - id: key27 - width: 46 + id: shiftKey + width: 43 glyph: "⇪" toggle: true - onToggledChanged: { - shiftMode = toggled; - } + onToggledChanged: shiftMode = toggled } - - Key { - id: key20 - width: 43 - glyph: "z" - } - - Key { - id: key21 - width: 43 - glyph: "x" - } - - Key { - id: key22 - width: 43 - glyph: "c" - } - - Key { - id: key23 - width: 43 - glyph: "v" - } - - Key { - id: key24 - width: 43 - glyph: "b" - } - - Key { - id: key25 - width: 43 - glyph: "n" - } - - Key { - id: key26 - width: 44 - glyph: "m" - } - - Key { - id: key31 - width: 43 - glyph: "_" - } - - Key { - id: key33 - width: 43 - glyph: "?" - } - - Key { - id: key36 - width: 46 - glyph: "/" - } - + Key { width: 43; glyph: "z"; } + Key { width: 43; glyph: "x"; } + Key { width: 43; glyph: "c"; } + Key { width: 43; glyph: "v"; } + Key { width: 43; glyph: "b"; } + Key { width: 43; glyph: "n"; } + Key { width: 43; glyph: "m"; } + Key { width: 43; glyph: "_"; } + Key { width: 43; glyph: "/"; } + Key { width: 43; glyph: "?"; } } Row { - id: row4 width: 480 height: 50 anchors.left: parent.left - anchors.leftMargin: 19 + anchors.leftMargin: 4 Key { - id: key30 - width: 89 - glyph: "&123" - mouseArea.onClicked: { - keyboardBase.parent.punctuationMode = true; - } + width: 70 + glyph: "123" + mouseArea.onClicked: keyboardBase.parent.punctuationMode = true } + Key { width: 231; glyph: " "; } + Key { width: 43; glyph: ","; } + Key { width: 43; glyph: "."; } + Key { width: 43; glyph: "\u276C"; } + Key { width: 43; glyph: "\u276D"; } + } + } + + Column { + id: columnNumeric + width: 480 + height: 200 + visible: numeric + + Row { + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 4 + + Key { width: 43; glyph: "1"; } + Key { width: 43; glyph: "2"; } + Key { width: 43; glyph: "3"; } + Key { width: 43; glyph: "4"; } + Key { width: 43; glyph: "5"; } + Key { width: 43; glyph: "6"; } + Key { width: 43; glyph: "7"; } + Key { width: 43; glyph: "8"; } + Key { width: 43; glyph: "9"; } + Key { width: 43; glyph: "0"; } + Key { width: 43; glyph: "←"; } + } + + Row { + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 4 + + Key { width: 43; glyph: "!"; } + Key { width: 43; glyph: "@"; } + Key { width: 43; glyph: "#"; } + Key { width: 43; glyph: "$"; } + Key { width: 43; glyph: "%"; } + Key { width: 43; glyph: "^"; } + Key { width: 43; glyph: "&"; } + Key { width: 43; glyph: "*"; } + Key { width: 43; glyph: "("; } + Key { width: 43; glyph: ")"; } + Key { width: 43; glyph: "⏎"; } + } + + Row { + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 4 Key { - id: key29 - width: 285 - glyph: " " - } - - Key { - id: key34 + id: numericShiftKey width: 43 - glyph: "⇦" + glyph: "\u21E8" + toggle: true + onToggledChanged: numericShiftMode = toggled } + Key { width: 43; glyph: numericShiftMode ? "`" : "+"; } + Key { width: 43; glyph: numericShiftMode ? "~" : "-"; } + Key { width: 43; glyph: numericShiftMode ? "\u00A3" : "="; } + Key { width: 43; glyph: numericShiftMode ? "\u20AC" : ";"; } + Key { width: 43; glyph: numericShiftMode ? "\u00A5" : ":"; } + Key { width: 43; glyph: numericShiftMode ? "<" : "'"; } + Key { width: 43; glyph: numericShiftMode ? ">" : "\""; } + Key { width: 43; glyph: numericShiftMode ? "[" : "{"; } + Key { width: 43; glyph: numericShiftMode ? "]" : "}"; } + Key { width: 43; glyph: numericShiftMode ? "\\" : "|"; } + } + + Row { + width: 480 + height: 50 + anchors.left: parent.left + anchors.leftMargin: 4 Key { - id: key35 - x: 343 - width: 43 - glyph: "⇨" + width: 70 + glyph: "abc" + mouseArea.onClicked: keyboardBase.parent.punctuationMode = false } - + Key { width: 231; glyph: " "; } + Key { width: 43; glyph: ","; } + Key { width: 43; glyph: "."; } + Key { width: 43; glyph: "\u276C"; } + Key { width: 43; glyph: "\u276D"; } } } } @@ -386,5 +318,4 @@ Item { anchors.top: parent.top anchors.topMargin: 0 } - } diff --git a/interface/resources/qml/controls-uit/KeyboardPunctuation.qml b/interface/resources/qml/controls-uit/KeyboardPunctuation.qml deleted file mode 100644 index 485468b46a..0000000000 --- a/interface/resources/qml/controls-uit/KeyboardPunctuation.qml +++ /dev/null @@ -1,324 +0,0 @@ -import QtQuick 2.0 - -Item { - id: keyboardBase - height: 200 - Rectangle { - id: leftRect - y: 0 - height: 200 - color: "#252525" - anchors.right: keyboardRect.left - anchors.rightMargin: 0 - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - } - - Rectangle { - id: keyboardRect - x: 206 - y: 0 - width: 480 - height: 200 - color: "#252525" - anchors.horizontalCenter: parent.horizontalCenter - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - - Column { - id: column1 - width: 480 - height: 200 - - Row { - id: row1 - width: 480 - height: 50 - anchors.left: parent.left - anchors.leftMargin: 0 - - Key { - id: key1 - width: 43 - glyph: "1" - } - - Key { - id: key2 - width: 43 - glyph: "2" - } - - Key { - id: key3 - width: 43 - glyph: "3" - } - - Key { - id: key4 - width: 43 - glyph: "4" - } - - Key { - id: key5 - width: 43 - glyph: "5" - } - - Key { - id: key6 - width: 43 - glyph: "6" - } - - Key { - id: key7 - width: 43 - glyph: "7" - } - - Key { - id: key8 - width: 43 - glyph: "8" - } - - Key { - id: key9 - width: 43 - glyph: "9" - } - - Key { - id: key10 - width: 43 - glyph: "0" - } - - Key { - id: key28 - width: 50 - glyph: "←" - } - } - - Row { - id: row2 - width: 480 - height: 50 - anchors.left: parent.left - anchors.leftMargin: 0 - - Key { - id: key11 - width: 43 - glyph: "!" - } - - Key { - id: key12 - width: 43 - glyph: "@" - } - - Key { - id: key13 - width: 43 - glyph: "#" - } - - Key { - id: key14 - width: 43 - glyph: "$" - } - - Key { - id: key15 - width: 43 - glyph: "%" - } - - Key { - id: key16 - width: 43 - glyph: "^" - } - - Key { - id: key17 - width: 43 - glyph: "&" - } - - Key { - id: key18 - width: 43 - glyph: "*" - } - - Key { - id: key19 - width: 43 - glyph: "(" - } - - Key { - id: key32 - width: 43 - glyph: ")" - } - - Key { - id: key37 - width: 50 - glyph: "⏎" - } - } - - Row { - id: row3 - width: 480 - height: 50 - anchors.left: parent.left - anchors.leftMargin: 4 - - Key { - id: key27 - width: 43 - glyph: "=" - } - - Key { - id: key20 - width: 43 - glyph: "+" - } - - Key { - id: key21 - width: 43 - glyph: "-" - } - - Key { - id: key22 - width: 43 - glyph: "," - } - - Key { - id: key23 - width: 43 - glyph: "." - } - - Key { - id: key24 - width: 43 - glyph: ";" - } - - Key { - id: key25 - width: 43 - glyph: ":" - } - - Key { - id: key26 - width: 43 - glyph: "'" - } - - Key { - id: key31 - width: 43 - glyph: "\"" - } - - Key { - id: key33 - width: 43 - glyph: "<" - } - - Key { - id: key36 - width: 43 - glyph: ">" - } - - } - - Row { - id: row4 - width: 480 - height: 50 - anchors.left: parent.left - anchors.leftMargin: 19 - - Key { - id: key30 - width: 65 - glyph: "abc" - mouseArea.onClicked: { - keyboardBase.parent.punctuationMode = false - } - } - - Key { - id: key29 - width: 285 - glyph: " " - } - - Key { - id: key34 - width: 43 - glyph: "⇦" - } - - Key { - id: key35 - x: 343 - width: 43 - glyph: "⇨" - } - - } - } - } - - Rectangle { - id: rightRect - y: 280 - height: 200 - color: "#252525" - border.width: 0 - anchors.left: keyboardRect.right - anchors.leftMargin: 0 - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - } - - Rectangle { - id: rectangle1 - color: "#ffffff" - anchors.bottom: keyboardRect.top - anchors.bottomMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.top: parent.top - anchors.topMargin: 0 - } - -} diff --git a/interface/resources/qml/controls-uit/Tree.qml b/interface/resources/qml/controls-uit/Tree.qml index aa1d10f030..8bce092947 100644 --- a/interface/resources/qml/controls-uit/Tree.qml +++ b/interface/resources/qml/controls-uit/Tree.qml @@ -199,6 +199,11 @@ TreeView { unfocusHelper.forceActiveFocus(); } } + + onReadOnlyChanged: { + // Have to explicily set keyboardRaised because automatic setting fails because readOnly is true at the time. + keyboardRaised = activeFocus; + } } } } diff --git a/interface/resources/qml/controls-uit/WebView.qml b/interface/resources/qml/controls-uit/WebView.qml index 2ce007c42a..2895f36944 100644 --- a/interface/resources/qml/controls-uit/WebView.qml +++ b/interface/resources/qml/controls-uit/WebView.qml @@ -13,8 +13,9 @@ import "." BaseWebView { onNewViewRequested: { - var component = Qt.createComponent("../Browser.qml"); - var newWindow = component.createObject(desktop); - request.openIn(newWindow.webView) + // Load dialog via OffscreenUi so that JavaScript EventBridge is available. + var browser = OffscreenUi.load("Browser.qml"); + request.openIn(browser.webView); + browser.webView.forceActiveFocus(); } } diff --git a/interface/resources/qml/controls/WebView.qml b/interface/resources/qml/controls/WebView.qml index c3381ab824..1056a3b75e 100644 --- a/interface/resources/qml/controls/WebView.qml +++ b/interface/resources/qml/controls/WebView.qml @@ -6,9 +6,18 @@ import "../controls-uit" as HiFiControls Item { property alias url: root.url property alias eventBridge: eventBridgeWrapper.eventBridge + property bool keyboardEnabled: true // FIXME - Keyboard HMD only: Default to false property bool keyboardRaised: false property bool punctuationMode: false + // FIXME - Keyboard HMD only: Make Interface either set keyboardRaised property directly in OffscreenQmlSurface + // or provide HMDinfo object to QML in RenderableWebEntityItem and do the following. + /* + onKeyboardRaisedChanged: { + keyboardEnabled = HMDinfo.active; + } + */ + QtObject { id: eventBridgeWrapper WebChannel.id: "eventBridgeWrapper" @@ -20,7 +29,7 @@ Item { x: 0 y: 0 width: parent.width - height: keyboardRaised ? parent.height - keyboard1.height : parent.height + height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height // creates a global EventBridge object. WebEngineScript { @@ -82,7 +91,7 @@ Item { onLoadingChanged: { keyboardRaised = false; punctuationMode = false; - keyboard1.resetShiftMode(false); + keyboard.resetShiftMode(false); // Required to support clicking on "hifi://" links if (WebEngineView.LoadStartedStatus == loadRequest.status) { @@ -105,32 +114,15 @@ Item { } } - // virtual keyboard, letters HiFiControls.Keyboard { - id: keyboard1 - y: keyboardRaised ? parent.height : 0 - height: keyboardRaised ? 200 : 0 - visible: keyboardRaised && !punctuationMode - enabled: keyboardRaised && !punctuationMode - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 + id: keyboard + raised: parent.keyboardEnabled && parent.keyboardRaised + numeric: parent.punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } } - HiFiControls.KeyboardPunctuation { - id: keyboard2 - y: keyboardRaised ? parent.height : 0 - height: keyboardRaised ? 200 : 0 - visible: keyboardRaised && punctuationMode - enabled: keyboardRaised && punctuationMode - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.bottom: parent.bottom - anchors.bottomMargin: 0 - } } diff --git a/interface/resources/qml/dialogs/CustomQueryDialog.qml b/interface/resources/qml/dialogs/CustomQueryDialog.qml index 76d20f0e0a..97f55d087b 100644 --- a/interface/resources/qml/dialogs/CustomQueryDialog.qml +++ b/interface/resources/qml/dialogs/CustomQueryDialog.qml @@ -22,7 +22,7 @@ ModalWindow { implicitWidth: 640; implicitHeight: 320; visible: true; - keyboardEnabled: false // Disable ModalWindow's keyboard. + keyboardOverride: true // Disable ModalWindow's keyboard. signal selected(var result); signal canceled(); @@ -51,6 +51,7 @@ ModalWindow { } } + property bool keyboardEnabled: false property bool keyboardRaised: false property bool punctuationMode: false onKeyboardRaisedChanged: d.resize(); @@ -116,7 +117,7 @@ ModalWindow { var targetHeight = (textField.visible ? textField.controlHeight + hifi.dimensions.contentSpacing.y : 0) + (extraInputs.visible ? extraInputs.height + hifi.dimensions.contentSpacing.y : 0) + (buttons.height + 3 * hifi.dimensions.contentSpacing.y) + - (root.keyboardRaised ? (200 + hifi.dimensions.contentSpacing.y) : 0); + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + hifi.dimensions.contentSpacing.y) : 0); root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth); root.height = (targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? @@ -153,38 +154,15 @@ ModalWindow { } } - Item { + Keyboard { id: keyboard - - height: keyboardRaised ? 200 : 0 - + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode anchors { left: parent.left right: parent.right bottom: parent.bottom - bottomMargin: keyboardRaised ? hifi.dimensions.contentSpacing.y : 0 - } - - Keyboard { - id: keyboard1 - visible: keyboardRaised && !punctuationMode - enabled: keyboardRaised && !punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - } - - KeyboardPunctuation { - id: keyboard2 - visible: keyboardRaised && punctuationMode - enabled: keyboardRaised && punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } + bottomMargin: raised ? hifi.dimensions.contentSpacing.y : 0 } } } @@ -339,6 +317,7 @@ ModalWindow { } Component.onCompleted: { + keyboardEnabled = HMD.active; updateIcon(); updateCheckbox(); d.resize(); diff --git a/interface/resources/qml/dialogs/FileDialog.qml b/interface/resources/qml/dialogs/FileDialog.qml index 0942e728f9..648d3a475d 100644 --- a/interface/resources/qml/dialogs/FileDialog.qml +++ b/interface/resources/qml/dialogs/FileDialog.qml @@ -27,7 +27,7 @@ ModalWindow { id: root resizable: true implicitWidth: 480 - implicitHeight: 360 + (fileDialogItem.keyboardRaised ? 200 + hifi.dimensions.contentSpacing.y : 0) + implicitHeight: 360 + (fileDialogItem.keyboardEnabled && fileDialogItem.keyboardRaised ? keyboard.raisedHeight + hifi.dimensions.contentSpacing.y : 0) minSize: Qt.vector2d(360, 240) draggable: true @@ -70,7 +70,9 @@ ModalWindow { signal canceled(); Component.onCompleted: { - console.log("Helper " + helper + " drives " + drives) + console.log("Helper " + helper + " drives " + drives); + + fileDialogItem.keyboardEnabled = HMD.active; // HACK: The following lines force the model to initialize properly such that the go-up button // works properly from the initial screen. @@ -106,6 +108,7 @@ ModalWindow { height: pane.height anchors.margins: 0 + property bool keyboardEnabled: false property bool keyboardRaised: false property bool punctuationMode: false @@ -626,7 +629,7 @@ ModalWindow { left: parent.left right: selectionType.visible ? selectionType.left: parent.right rightMargin: selectionType.visible ? hifi.dimensions.contentSpacing.x : 0 - bottom: keyboard1.top + bottom: keyboard.top bottomMargin: hifi.dimensions.contentSpacing.y } readOnly: !root.saveDialog @@ -648,25 +651,15 @@ ModalWindow { } Keyboard { - id: keyboard1 - height: parent.keyboardRaised ? 200 : 0 - visible: parent.keyboardRaised && !parent.punctuationMode - enabled: visible - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: buttonRow.top - anchors.bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0 - } - - KeyboardPunctuation { - id: keyboard2 - height: parent.keyboardRaised ? 200 : 0 - visible: parent.keyboardRaised && parent.punctuationMode - enabled: visible - anchors.right: parent.right - anchors.left: parent.left - anchors.bottom: buttonRow.top - anchors.bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0 + id: keyboard + raised: parent.keyboardEnabled && parent.keyboardRaised + numeric: parent.punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: buttonRow.top + bottomMargin: visible ? hifi.dimensions.contentSpacing.y : 0 + } } Row { diff --git a/interface/resources/qml/dialogs/QueryDialog.qml b/interface/resources/qml/dialogs/QueryDialog.qml index cf1b1e370a..a922e6efc7 100644 --- a/interface/resources/qml/dialogs/QueryDialog.qml +++ b/interface/resources/qml/dialogs/QueryDialog.qml @@ -22,6 +22,7 @@ ModalWindow { implicitWidth: 640 implicitHeight: 320 visible: true + keyboardOverride: true // Disable ModalWindow's keyboard. signal selected(var result); signal canceled(); @@ -45,6 +46,12 @@ ModalWindow { property int titleWidth: 0 onTitleWidthChanged: d.resize(); + property bool keyboardEnabled: false + property bool keyboardRaised: false + property bool punctuationMode: false + + onKeyboardRaisedChanged: d.resize(); + function updateIcon() { if (!root) { return; @@ -59,11 +66,6 @@ ModalWindow { height: pane.height anchors.margins: 0 - property bool keyboardRaised: false - property bool punctuationMode: false - - onKeyboardRaisedChanged: d.resize(); - QtObject { id: d readonly property int minWidth: 480 @@ -74,15 +76,15 @@ ModalWindow { function resize() { var targetWidth = Math.max(titleWidth, pane.width) var targetHeight = (items ? comboBox.controlHeight : textResult.controlHeight) + 5 * hifi.dimensions.contentSpacing.y + buttons.height - root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth) - root.height = ((targetHeight < d.minHeight) ? d.minHeight: ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + (modalWindowItem.keyboardRaised ? (200 + 2 * hifi.dimensions.contentSpacing.y) : 0) + root.width = (targetWidth < d.minWidth) ? d.minWidth : ((targetWidth > d.maxWdith) ? d.maxWidth : targetWidth); + root.height = ((targetHeight < d.minHeight) ? d.minHeight : ((targetHeight > d.maxHeight) ? d.maxHeight : targetHeight)) + ((keyboardEnabled && keyboardRaised) ? (keyboard.raisedHeight + 2 * hifi.dimensions.contentSpacing.y) : 0) } } Item { anchors { top: parent.top - bottom: keyboard1.top; + bottom: keyboard.top; left: parent.left; right: parent.right; margins: 0 @@ -116,33 +118,16 @@ ModalWindow { } } - // virtual keyboard, letters Keyboard { - id: keyboard1 - y: parent.keyboardRaised ? parent.height : 0 - height: parent.keyboardRaised ? 200 : 0 - visible: parent.keyboardRaised && !parent.punctuationMode - enabled: parent.keyboardRaised && !parent.punctuationMode - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.bottom: buttons.top - anchors.bottomMargin: parent.keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0 - } - - KeyboardPunctuation { - id: keyboard2 - y: parent.keyboardRaised ? parent.height : 0 - height: parent.keyboardRaised ? 200 : 0 - visible: parent.keyboardRaised && parent.punctuationMode - enabled: parent.keyboardRaised && parent.punctuationMode - anchors.right: parent.right - anchors.rightMargin: 0 - anchors.left: parent.left - anchors.leftMargin: 0 - anchors.bottom: buttons.top - anchors.bottomMargin: parent.keyboardRaised ? 2 * hifi.dimensions.contentSpacing.y : 0 + id: keyboard + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: buttons.top + bottomMargin: raised ? 2 * hifi.dimensions.contentSpacing.y : 0 + } } Flow { @@ -203,6 +188,7 @@ ModalWindow { } Component.onCompleted: { + keyboardEnabled = HMD.active; updateIcon(); d.resize(); textResult.forceActiveFocus(); diff --git a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml index 16d25b3c4c..652e02b6b9 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarBrowser.qml @@ -10,23 +10,84 @@ import QtQuick 2.5 import QtQuick.Controls 1.4 -import QtWebEngine 1.1 +import QtWebChannel 1.0 +import QtWebEngine 1.2 -import "../../windows" as Windows -import "../../controls-uit" as Controls +import "../../windows" +import "../../controls-uit" import "../../styles-uit" -Windows.Window { +Window { id: root HifiConstants { id: hifi } width: 900; height: 700 resizable: true modality: Qt.ApplicationModal - Controls.WebView { - id: webview + property alias eventBridge: eventBridgeWrapper.eventBridge + + Item { anchors.fill: parent - url: "https://metaverse.highfidelity.com/marketplace?category=avatars" - focus: true + + property bool keyboardEnabled: false + property bool keyboardRaised: true + property bool punctuationMode: false + + BaseWebView { + id: webview + url: "https://metaverse.highfidelity.com/marketplace?category=avatars" + focus: true + + anchors { + top: parent.top + left: parent.left + right: parent.right + bottom: keyboard.top + } + + property alias eventBridgeWrapper: eventBridgeWrapper + + QtObject { + id: eventBridgeWrapper + WebChannel.id: "eventBridgeWrapper" + property var eventBridge; + } + + webChannel.registeredObjects: [eventBridgeWrapper] + + // Create a global EventBridge object for raiseAndLowerKeyboard. + WebEngineScript { + id: createGlobalEventBridge + sourceCode: eventBridgeJavaScriptToInject + injectionPoint: WebEngineScript.DocumentCreation + worldId: WebEngineScript.MainWorld + } + + // Detect when may want to raise and lower keyboard. + WebEngineScript { + id: raiseAndLowerKeyboard + injectionPoint: WebEngineScript.Deferred + sourceUrl: resourceDirectoryUrl + "html/raiseAndLowerKeyboard.js" + worldId: WebEngineScript.MainWorld + } + + userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ] + + } + + Keyboard { + id: keyboard + raised: parent.keyboardEnabled && parent.keyboardRaised + numeric: parent.punctuationMode + anchors { + left: parent.left + right: parent.right + bottom: parent.bottom + } + } + + Component.onCompleted: { + keyboardEnabled = HMD.active; + } } } diff --git a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml index 8f05ca4ffe..0c5c5bf630 100644 --- a/interface/resources/qml/dialogs/preferences/AvatarPreference.qml +++ b/interface/resources/qml/dialogs/preferences/AvatarPreference.qml @@ -74,11 +74,6 @@ Preference { colorScheme: hifi.colorSchemes.dark } - Component { - id: avatarBrowserBuilder; - AvatarBrowser { } - } - Button { id: button text: "Browse" @@ -87,12 +82,12 @@ Preference { verticalCenter: dataTextField.verticalCenter } onClicked: { - root.browser = avatarBrowserBuilder.createObject(desktop); + // Load dialog via OffscreenUi so that JavaScript EventBridge is available. + root.browser = OffscreenUi.load("dialogs/preferences/AvatarBrowser.qml"); root.browser.windowDestroyed.connect(function(){ root.browser = null; - }) + }); } } - } } diff --git a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml index cc9a570d47..27d225b58e 100644 --- a/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml +++ b/interface/resources/qml/hifi/dialogs/AttachmentsDialog.qml @@ -45,7 +45,7 @@ ScrollingWindow { Rectangle { width: parent.width - height: root.height - (keyboardRaised ? 200 : 0) + height: root.height - (keyboardEnabled && keyboardRaised ? 200 : 0) radius: 4 color: hifi.colors.baseGray @@ -210,7 +210,7 @@ ScrollingWindow { } onKeyboardRaisedChanged: { - if (keyboardRaised) { + if (keyboardEnabled && keyboardRaised) { // Scroll to item with focus if necessary. var footerHeight = newAttachmentButton.height + buttonRow.height + 3 * hifi.dimensions.contentSpacing.y; var delta = activator.mouseY diff --git a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml index a5a254f605..7a63c0604c 100644 --- a/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml +++ b/interface/resources/qml/hifi/dialogs/ModelBrowserDialog.qml @@ -30,7 +30,7 @@ ScrollingWindow { Rectangle { width: parent.width - height: root.height - (keyboardRaised ? 200 : 0) + height: root.height - (keyboardEnabled && keyboardRaised ? 200 : 0) radius: 4 color: hifi.colors.baseGray diff --git a/interface/resources/qml/windows/ScrollingWindow.qml b/interface/resources/qml/windows/ScrollingWindow.qml index ce4bd45cff..1f9b59d2b4 100644 --- a/interface/resources/qml/windows/ScrollingWindow.qml +++ b/interface/resources/qml/windows/ScrollingWindow.qml @@ -34,7 +34,8 @@ Window { property var footer: Item { } // Optional static footer at the bottom of the dialog. readonly property var footerContentHeight: footer.height > 0 ? (footer.height + 2 * hifi.dimensions.contentSpacing.y + 3) : 0 - property bool keyboardEnabled: true // Set false if derived control implements its own keyboard. + property bool keyboardOverride: false // Set true in derived control if it implements its own keyboard. + property bool keyboardEnabled: false property bool keyboardRaised: false property bool punctuationMode: false @@ -132,7 +133,7 @@ Window { // Optional non-scrolling footer. id: footerPane - property alias keyboardEnabled: window.keyboardEnabled + property alias keyboardOverride: window.keyboardOverride property alias keyboardRaised: window.keyboardRaised property alias punctuationMode: window.punctuationMode @@ -141,9 +142,9 @@ Window { bottom: parent.bottom } width: parent.contentWidth - height: footerContentHeight + (keyboardEnabled && keyboardRaised ? 200 : 0) + height: footerContentHeight + (keyboard.enabled && keyboard.raised ? keyboard.height : 0) color: hifi.colors.baseGray - visible: footer.height > 0 || keyboardEnabled && keyboardRaised + visible: footer.height > 0 || keyboard.enabled && keyboard.raised Item { // Horizontal rule. @@ -181,22 +182,10 @@ Window { } HiFiControls.Keyboard { - id: keyboard1 - height: parent.keyboardEnabled && parent.keyboardRaised ? 200 : 0 - visible: parent.keyboardEnabled && parent.keyboardRaised && !parent.punctuationMode - enabled: parent.keyboardEnabled && parent.keyboardRaised && !parent.punctuationMode - anchors { - left: parent.left - right: parent.right - bottom: parent.bottom - } - } - - HiFiControls.KeyboardPunctuation { - id: keyboard2 - height: parent.keyboardEnabled && parent.keyboardRaised ? 200 : 0 - visible: parent.keyboardEnabled && parent.keyboardRaised && parent.punctuationMode - enabled: parent.keyboardEnabled && parent.keyboardRaised && parent.punctuationMode + id: keyboard + enabled: !keyboardOverride + raised: keyboardEnabled && keyboardRaised + numeric: punctuationMode anchors { left: parent.left right: parent.right @@ -207,9 +196,9 @@ Window { } onKeyboardRaisedChanged: { - if (keyboardEnabled && keyboardRaised) { + if (!keyboardOverride && keyboardEnabled && keyboardRaised) { var delta = activator.mouseY - - (activator.height + activator.y - 200 - footerContentHeight - hifi.dimensions.controlLineHeight); + - (activator.height + activator.y - keyboard.raisedHeight - footerContentHeight - hifi.dimensions.controlLineHeight); if (delta > 0) { pane.scrollBy(delta); @@ -220,4 +209,10 @@ Window { } } } + + Component.onCompleted: { + if (typeof HMD !== "undefined") { + keyboardEnabled = HMD.active; + } + } } diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index b13c432a14..db9f7757cf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -94,6 +94,7 @@ #include #include #include +#include #include #include #include @@ -415,8 +416,6 @@ bool setupEssentials(int& argc, char** argv) { static const auto SUPPRESS_SETTINGS_RESET = "--suppress-settings-reset"; bool suppressPrompt = cmdOptionExists(argc, const_cast(argv), SUPPRESS_SETTINGS_RESET); bool previousSessionCrashed = CrashHandler::checkForResetSettings(suppressPrompt); - CrashHandler::writeRunningMarkerFiler(); - qAddPostRoutine(CrashHandler::deleteRunningMarkerFile); DependencyManager::registerInheritance(); DependencyManager::registerInheritance(); @@ -504,8 +503,11 @@ Q_GUI_EXPORT void qt_gl_set_global_share_context(QOpenGLContext *context); Setting::Handle sessionRunTime{ "sessionRunTime", 0 }; -Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : +Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bool runServer, QString runServerPathOption) : QApplication(argc, argv), + _shouldRunServer(runServer), + _runServerPath(runServerPathOption), + _runningMarker(this, RUNNING_MARKER_FILENAME), _window(new MainWindow(desktop())), _sessionRunTimer(startupTimer), _previousSessionCrashed(setupEssentials(argc, argv)), @@ -529,7 +531,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : _maxOctreePPS(maxOctreePacketsPerSecond.get()), _lastFaceTrackerUpdate(0) { - + _runningMarker.startRunningMarker(); PluginContainer* pluginContainer = dynamic_cast(this); // set the container for any plugins that care PluginManager::getInstance()->setContainer(pluginContainer); @@ -575,6 +577,28 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : qCDebug(interfaceapp) << "[VERSION] We will use DEVELOPMENT global services."; #endif + + bool wantsSandboxRunning = shouldRunServer(); + static bool determinedSandboxState = false; + SandboxUtils sandboxUtils; + sandboxUtils.ifLocalSandboxRunningElse([&]() { + qCDebug(interfaceapp) << "Home sandbox appears to be running....."; + determinedSandboxState = true; + }, [&, wantsSandboxRunning]() { + qCDebug(interfaceapp) << "Home sandbox does not appear to be running...."; + determinedSandboxState = true; + if (wantsSandboxRunning) { + QString contentPath = getRunServerPath(); + SandboxUtils::runLocalSandbox(contentPath, true, RUNNING_MARKER_FILENAME); + } + }); + + quint64 MAX_WAIT_TIME = USECS_PER_SECOND * 4; + auto startWaiting = usecTimestampNow(); + while (!determinedSandboxState && (usecTimestampNow() - startWaiting <= MAX_WAIT_TIME)) { + QCoreApplication::processEvents(); + usleep(USECS_PER_MSEC * 50); // 20hz + } _bookmarks = new Bookmarks(); // Before setting up the menu @@ -1265,7 +1289,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // Get sandbox content set version, if available auto acDirPath = PathUtils::getRootDataDirectory() + BuildInfo::MODIFIED_ORGANIZATION + "/assignment-client/"; auto contentVersionPath = acDirPath + "content-version.txt"; - qDebug() << "Checking " << contentVersionPath << " for content version"; + qCDebug(interfaceapp) << "Checking " << contentVersionPath << " for content version"; auto contentVersion = 0; QFile contentVersionFile(contentVersionPath); if (contentVersionFile.open(QIODevice::ReadOnly | QIODevice::Text)) { @@ -1273,7 +1297,7 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // toInt() returns 0 if the conversion fails, so we don't need to specifically check for failure contentVersion = line.toInt(); } - qDebug() << "Server content version: " << contentVersion; + qCDebug(interfaceapp) << "Server content version: " << contentVersion; bool hasTutorialContent = contentVersion >= 1; @@ -1283,10 +1307,10 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : bool shouldGoToTutorial = hasHMDAndHandControllers && hasTutorialContent && !tutorialComplete.get(); - qDebug() << "Has HMD + Hand Controllers: " << hasHMDAndHandControllers << ", current plugin: " << _displayPlugin->getName(); - qDebug() << "Has tutorial content: " << hasTutorialContent; - qDebug() << "Tutorial complete: " << tutorialComplete.get(); - qDebug() << "Should go to tutorial: " << shouldGoToTutorial; + qCDebug(interfaceapp) << "Has HMD + Hand Controllers: " << hasHMDAndHandControllers << ", current plugin: " << _displayPlugin->getName(); + qCDebug(interfaceapp) << "Has tutorial content: " << hasTutorialContent; + qCDebug(interfaceapp) << "Tutorial complete: " << tutorialComplete.get(); + qCDebug(interfaceapp) << "Should go to tutorial: " << shouldGoToTutorial; // when --url in command line, teleport to location const QString HIFI_URL_COMMAND_LINE_KEY = "--url"; @@ -1299,11 +1323,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : const QString TUTORIAL_PATH = "/tutorial_begin"; if (shouldGoToTutorial) { - DependencyManager::get()->ifLocalSandboxRunningElse([=]() { - qDebug() << "Home sandbox appears to be running, going to Home."; + sandboxUtils.ifLocalSandboxRunningElse([=]() { + qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; DependencyManager::get()->goToLocalSandbox(TUTORIAL_PATH); }, [=]() { - qDebug() << "Home sandbox does not appear to be running, going to Entry."; + qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; if (firstRun.get()) { showHelp(); } @@ -1324,18 +1348,18 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer) : // If this is a first run we short-circuit the address passed in if (isFirstRun) { if (hasHMDAndHandControllers) { - DependencyManager::get()->ifLocalSandboxRunningElse([=]() { - qDebug() << "Home sandbox appears to be running, going to Home."; + sandboxUtils.ifLocalSandboxRunningElse([=]() { + qCDebug(interfaceapp) << "Home sandbox appears to be running, going to Home."; DependencyManager::get()->goToLocalSandbox(); }, [=]() { - qDebug() << "Home sandbox does not appear to be running, going to Entry."; + qCDebug(interfaceapp) << "Home sandbox does not appear to be running, going to Entry."; DependencyManager::get()->goToEntry(); }); } else { DependencyManager::get()->goToEntry(); } } else { - qDebug() << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); + qCDebug(interfaceapp) << "Not first run... going to" << qPrintable(addressLookupString.isEmpty() ? QString("previous location") : addressLookupString); DependencyManager::get()->loadSettings(addressLookupString); } } diff --git a/interface/src/Application.h b/interface/src/Application.h index e6429f1ebe..5f78658254 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -46,6 +46,8 @@ #include #include +#include + #include "avatar/MyAvatar.h" #include "Bookmarks.h" #include "Camera.h" @@ -87,6 +89,8 @@ static const UINT UWM_SHOW_APPLICATION = RegisterWindowMessage("UWM_SHOW_APPLICATION_{71123FD6-3DA8-4DC1-9C27-8A12A6250CBA}_" + qgetenv("USERNAME")); #endif +static const QString RUNNING_MARKER_FILENAME = "Interface.running"; + class Application; #if defined(qApp) #undef qApp @@ -103,7 +107,16 @@ class Application : public QApplication, // TODO? Get rid of those friend class OctreePacketProcessor; +private: + bool _shouldRunServer { false }; + QString _runServerPath; + RunningMarker _runningMarker; + public: + // startup related getter/setters + bool shouldRunServer() const { return _shouldRunServer; } + bool hasRunServerPath() const { return !_runServerPath.isEmpty(); } + QString getRunServerPath() const { return _runServerPath; } // virtual functions required for PluginContainer virtual ui::Menu* getPrimaryMenu() override; @@ -127,7 +140,7 @@ public: static void initPlugins(const QStringList& arguments); static void shutdownPlugins(); - Application(int& argc, char** argv, QElapsedTimer& startup_time); + Application(int& argc, char** argv, QElapsedTimer& startup_time, bool runServer, QString runServerPathOption); ~Application(); void postLambdaEvent(std::function f) override; diff --git a/interface/src/CrashHandler.cpp b/interface/src/CrashHandler.cpp index 3c5f03bef3..0c9698ffc3 100644 --- a/interface/src/CrashHandler.cpp +++ b/interface/src/CrashHandler.cpp @@ -23,9 +23,10 @@ #include #include +#include "Application.h" #include "Menu.h" -static const QString RUNNING_MARKER_FILENAME = "Interface.running"; +#include bool CrashHandler::checkForResetSettings(bool suppressPrompt) { QSettings::setDefaultFormat(QSettings::IniFormat); @@ -39,7 +40,7 @@ bool CrashHandler::checkForResetSettings(bool suppressPrompt) { // If option does not exist in Interface.ini so assume default behavior. bool displaySettingsResetOnCrash = !displayCrashOptions.isValid() || displayCrashOptions.toBool(); - QFile runningMarkerFile(runningMarkerFilePath()); + QFile runningMarkerFile(RunningMarker::getMarkerFilePath(RUNNING_MARKER_FILENAME)); bool wasLikelyCrash = runningMarkerFile.exists(); if (suppressPrompt) { @@ -161,20 +162,3 @@ void CrashHandler::handleCrash(CrashHandler::Action action) { } } -void CrashHandler::writeRunningMarkerFiler() { - QFile runningMarkerFile(runningMarkerFilePath()); - if (!runningMarkerFile.exists()) { - runningMarkerFile.open(QIODevice::WriteOnly); - runningMarkerFile.close(); - } -} -void CrashHandler::deleteRunningMarkerFile() { - QFile runningMarkerFile(runningMarkerFilePath()); - if (runningMarkerFile.exists()) { - runningMarkerFile.remove(); - } -} - -const QString CrashHandler::runningMarkerFilePath() { - return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + RUNNING_MARKER_FILENAME; -} diff --git a/interface/src/CrashHandler.h b/interface/src/CrashHandler.h index a65fed677d..308cac3411 100644 --- a/interface/src/CrashHandler.h +++ b/interface/src/CrashHandler.h @@ -19,9 +19,6 @@ class CrashHandler { public: static bool checkForResetSettings(bool suppressPrompt = false); - static void writeRunningMarkerFiler(); - static void deleteRunningMarkerFile(); - private: enum Action { DELETE_INTERFACE_INI, @@ -31,8 +28,6 @@ private: static Action promptUserForAction(bool showCrashMessage); static void handleCrash(Action action); - - static const QString runningMarkerFilePath(); }; #endif // hifi_CrashHandler_h diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 3c4a3fd77a..5b0da4f578 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -37,6 +37,8 @@ #include #endif + + int main(int argc, const char* argv[]) { #if HAS_BUGSPLAT static QString BUG_SPLAT_DATABASE = "interface_alpha"; @@ -128,22 +130,9 @@ int main(int argc, const char* argv[]) { parser.addOption(runServerOption); parser.addOption(serverContentPathOption); parser.parse(arguments); - if (parser.isSet(runServerOption)) { - QString applicationDirPath = QFileInfo(arguments[0]).path(); - QString serverPath = applicationDirPath + "/server-console/server-console.exe"; - qDebug() << "Application dir path is: " << applicationDirPath; - qDebug() << "Server path is: " << serverPath; - QStringList args; - if (parser.isSet(serverContentPathOption)) { - QString serverContentPath = QFileInfo(arguments[0]).path() + "/" + parser.value(serverContentPathOption); - args << "--" << "--contentPath" << serverContentPath; - } - qDebug() << QFileInfo(arguments[0]).path(); - qDebug() << QProcess::startDetached(serverPath, args); - - // Sleep a short amount of time to give the server a chance to start - usleep(2000000); - } + bool runServer = parser.isSet(runServerOption); + bool serverContentPathOptionIsSet = parser.isSet(serverContentPathOption); + QString serverContentPathOptionValue = serverContentPathOptionIsSet ? parser.value(serverContentPathOption) : QString(); QElapsedTimer startupTime; startupTime.start(); @@ -166,10 +155,11 @@ int main(int argc, const char* argv[]) { SteamClient::init(); + int exitCode; { QSettings::setDefaultFormat(QSettings::IniFormat); - Application app(argc, const_cast(argv), startupTime); + Application app(argc, const_cast(argv), startupTime, runServer, serverContentPathOptionValue); // If we failed the OpenGLVersion check, log it. if (override) { @@ -223,7 +213,6 @@ int main(int argc, const char* argv[]) { QTranslator translator; translator.load("i18n/interface_en"); app.installTranslator(&translator); - qCDebug(interfaceapp, "Created QT Application."); exitCode = app.exec(); server.close(); diff --git a/interface/src/ui/Snapshot.cpp b/interface/src/ui/Snapshot.cpp index a0ee260bcc..3334b0301b 100644 --- a/interface/src/ui/Snapshot.cpp +++ b/interface/src/ui/Snapshot.cpp @@ -43,9 +43,7 @@ const QString SNAPSHOTS_DIRECTORY = "Snapshots"; const QString URL = "highfidelity_url"; -Setting::Handle Snapshot::snapshotsLocation("snapshotsLocation", - QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); -Setting::Handle Snapshot::hasSetSnapshotsLocation("hasSetSnapshotsLocation", false); +Setting::Handle Snapshot::snapshotsLocation("snapshotsLocation"); SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) { @@ -105,42 +103,44 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary) { const int IMAGE_QUALITY = 100; if (!isTemporary) { - QString snapshotFullPath; - if (!hasSetSnapshotsLocation.get()) { - snapshotFullPath = QFileDialog::getExistingDirectory(nullptr, "Choose Snapshots Directory", snapshotsLocation.get()); - hasSetSnapshotsLocation.set(true); + QString snapshotFullPath = snapshotsLocation.get(); + + if (snapshotFullPath.isEmpty()) { + snapshotFullPath = OffscreenUi::getExistingDirectory(nullptr, "Choose Snapshots Directory", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation)); snapshotsLocation.set(snapshotFullPath); - } else { - snapshotFullPath = snapshotsLocation.get(); } - if (!snapshotFullPath.endsWith(QDir::separator())) { - snapshotFullPath.append(QDir::separator()); + if (!snapshotFullPath.isEmpty()) { // not cancelled + + if (!snapshotFullPath.endsWith(QDir::separator())) { + snapshotFullPath.append(QDir::separator()); + } + + snapshotFullPath.append(filename); + + QFile* imageFile = new QFile(snapshotFullPath); + imageFile->open(QIODevice::WriteOnly); + + shot.save(imageFile, 0, IMAGE_QUALITY); + imageFile->close(); + + return imageFile; } - snapshotFullPath.append(filename); - - QFile* imageFile = new QFile(snapshotFullPath); - imageFile->open(QIODevice::WriteOnly); - - shot.save(imageFile, 0, IMAGE_QUALITY); - imageFile->close(); - - return imageFile; - - } else { - QTemporaryFile* imageTempFile = new QTemporaryFile(QDir::tempPath() + "/XXXXXX-" + filename); - - if (!imageTempFile->open()) { - qDebug() << "Unable to open QTemporaryFile for temp snapshot. Will not save."; - return NULL; - } - - shot.save(imageTempFile, 0, IMAGE_QUALITY); - imageTempFile->close(); - - return imageTempFile; } + // Either we were asked for a tempororary, or the user didn't set a directory. + QTemporaryFile* imageTempFile = new QTemporaryFile(QDir::tempPath() + "/XXXXXX-" + filename); + + if (!imageTempFile->open()) { + qDebug() << "Unable to open QTemporaryFile for temp snapshot. Will not save."; + return NULL; + } + imageTempFile->setAutoRemove(isTemporary); + + shot.save(imageTempFile, 0, IMAGE_QUALITY); + imageTempFile->close(); + + return imageTempFile; } void Snapshot::uploadSnapshot(const QString& filename) { diff --git a/interface/src/ui/Snapshot.h b/interface/src/ui/Snapshot.h index 90138d450d..2daed0e860 100644 --- a/interface/src/ui/Snapshot.h +++ b/interface/src/ui/Snapshot.h @@ -39,7 +39,6 @@ public: static SnapshotMetaData* parseSnapshotData(QString snapshotPath); static Setting::Handle snapshotsLocation; - static Setting::Handle hasSetSnapshotsLocation; static void uploadSnapshot(const QString& filename); private: static QFile* savedFileForSnapshot(QImage & image, bool isTemporary); diff --git a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp index 021edde2c2..bea3e6a0de 100644 --- a/libraries/entities-renderer/src/RenderableWebEntityItem.cpp +++ b/libraries/entities-renderer/src/RenderableWebEntityItem.cpp @@ -108,6 +108,7 @@ bool RenderableWebEntityItem::buildWebSurface(EntityTreeRenderer* renderer) { _webSurface->resume(); _webSurface->getRootItem()->setProperty("url", _sourceUrl); _webSurface->getRootContext()->setContextProperty("desktop", QVariant()); + // FIXME - Keyboard HMD only: Possibly add "HMDinfo" object to context for WebView.qml. // forward web events to EntityScriptingInterface auto entities = DependencyManager::get(); diff --git a/libraries/gl/src/gl/OffscreenQmlSurface.cpp b/libraries/gl/src/gl/OffscreenQmlSurface.cpp index f48e2e6092..d16968f370 100644 --- a/libraries/gl/src/gl/OffscreenQmlSurface.cpp +++ b/libraries/gl/src/gl/OffscreenQmlSurface.cpp @@ -309,6 +309,7 @@ void OffscreenQmlSurface::create(QOpenGLContext* shareContext) { } // FIXME + _glData = ::getGLContextData(); // Initialize JSON structure so that it can be filled in later and then used in QML. _qmlEngine->rootContext()->setContextProperty("GL", _glData); _qmlEngine->rootContext()->setContextProperty("offscreenWindow", QVariant::fromValue(getWindow())); _qmlComponent = new QQmlComponent(_qmlEngine); @@ -726,13 +727,13 @@ void OffscreenQmlSurface::setFocusText(bool newFocusText) { } // UTF-8 encoded symbols -static const uint8_t UPWARDS_WHITE_ARROW_FROM_BAR[] = { 0xE2, 0x87, 0xAA, 0x00 }; // shift -static const uint8_t LEFT_ARROW[] = { 0xE2, 0x86, 0x90, 0x00 }; // backspace -static const uint8_t LEFTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA6, 0x00 }; // left arrow -static const uint8_t RIGHTWARD_WHITE_ARROW[] = { 0xE2, 0x87, 0xA8, 0x00 }; // right arrow -static const uint8_t ASTERISIM[] = { 0xE2, 0x81, 0x82, 0x00 }; // symbols -static const uint8_t RETURN_SYMBOL[] = { 0xE2, 0x8F, 0x8E, 0x00 }; // return -static const char PUNCTUATION_STRING[] = "&123"; +static const uint8_t SHIFT_ARROW[] = { 0xE2, 0x87, 0xAA, 0x00 }; +static const uint8_t NUMERIC_SHIFT_ARROW[] = { 0xE2, 0x87, 0xA8, 0x00 }; +static const uint8_t BACKSPACE_SYMBOL[] = { 0xE2, 0x86, 0x90, 0x00 }; +static const uint8_t LEFT_ARROW[] = { 0xE2, 0x9D, 0xAC, 0x00 }; +static const uint8_t RIGHT_ARROW[] = { 0xE2, 0x9D, 0xAD, 0x00 }; +static const uint8_t RETURN_SYMBOL[] = { 0xE2, 0x8F, 0x8E, 0x00 }; +static const char PUNCTUATION_STRING[] = "123"; static const char ALPHABET_STRING[] = "abc"; static bool equals(const QByteArray& byteArray, const uint8_t* ptr) { @@ -752,19 +753,19 @@ void OffscreenQmlSurface::synthesizeKeyPress(QString key) { int scanCode = (int)utf8Key[0]; QString keyString = key; - if (equals(utf8Key, UPWARDS_WHITE_ARROW_FROM_BAR) || equals(utf8Key, ASTERISIM) || + if (equals(utf8Key, SHIFT_ARROW) || equals(utf8Key, NUMERIC_SHIFT_ARROW) || equals(utf8Key, (uint8_t*)PUNCTUATION_STRING) || equals(utf8Key, (uint8_t*)ALPHABET_STRING)) { return; // ignore - } else if (equals(utf8Key, LEFT_ARROW)) { + } else if (equals(utf8Key, BACKSPACE_SYMBOL)) { scanCode = Qt::Key_Backspace; keyString = "\x08"; } else if (equals(utf8Key, RETURN_SYMBOL)) { scanCode = Qt::Key_Return; keyString = "\x0d"; - } else if (equals(utf8Key, LEFTWARD_WHITE_ARROW)) { + } else if (equals(utf8Key, LEFT_ARROW)) { scanCode = Qt::Key_Left; keyString = ""; - } else if (equals(utf8Key, RIGHTWARD_WHITE_ARROW)) { + } else if (equals(utf8Key, RIGHT_ARROW)) { scanCode = Qt::Key_Right; keyString = ""; } @@ -787,6 +788,7 @@ void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool n numeric = numeric || QString(item->metaObject()->className()).left(7) == "SpinBox"; if (item->property("keyboardRaised").isValid()) { + // FIXME - HMD only: Possibly set value of "keyboardEnabled" per isHMDMode() for use in WebView.qml. if (item->property("punctuationMode").isValid()) { item->setProperty("punctuationMode", QVariant(numeric)); } diff --git a/libraries/gl/src/gl/TextureRecycler.cpp b/libraries/gl/src/gl/TextureRecycler.cpp index 4438e158c5..04cd38f156 100644 --- a/libraries/gl/src/gl/TextureRecycler.cpp +++ b/libraries/gl/src/gl/TextureRecycler.cpp @@ -37,27 +37,35 @@ void TextureRecycler::clear() { _allTextures.clear(); } +void TextureRecycler::addTexture() { + uint32_t newTexture; + glGenTextures(1, &newTexture); + glBindTexture(GL_TEXTURE_2D, newTexture); + if (_useMipmaps) { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); + } else { + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + } + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _size.x, _size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); + _allTextures.emplace(std::piecewise_construct, std::forward_as_tuple(newTexture), std::forward_as_tuple(newTexture, _size)); + _readyTextures.push(newTexture); +} + uint32_t TextureRecycler::getNextTexture() { + while (_allTextures.size() < _textureCount) { + addTexture(); + } + if (_readyTextures.empty()) { - uint32_t newTexture; - glGenTextures(1, &newTexture); - glBindTexture(GL_TEXTURE_2D, newTexture); - if (_useMipmaps) { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR); - } else { - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); - } - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_LOD_BIAS, -0.2f); - glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 8.0f); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, _size.x, _size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0); - _allTextures.emplace(std::piecewise_construct, std::forward_as_tuple(newTexture), std::forward_as_tuple(newTexture, _size)); - _readyTextures.push(newTexture); + addTexture(); } uint32_t result = _readyTextures.front(); diff --git a/libraries/gl/src/gl/TextureRecycler.h b/libraries/gl/src/gl/TextureRecycler.h index 46cbcad219..4175dfd201 100644 --- a/libraries/gl/src/gl/TextureRecycler.h +++ b/libraries/gl/src/gl/TextureRecycler.h @@ -15,15 +15,21 @@ #include +// GPU resources are typically buffered for one copy being used by the renderer, +// one copy in flight, and one copy being used by the receiver +#define GPU_RESOURCE_BUFFER_SIZE 3 + class TextureRecycler { public: TextureRecycler(bool useMipmaps) : _useMipmaps(useMipmaps) {} void setSize(const uvec2& size); + void setTextureCount(uint8_t textureCount); void clear(); uint32_t getNextTexture(); void recycleTexture(uint32_t texture); private: + void addTexture(); struct TexInfo { const uint32_t _tex{ 0 }; @@ -42,6 +48,7 @@ private: Queue _readyTextures; uvec2 _size{ 1920, 1080 }; bool _useMipmaps; + uint8_t _textureCount { GPU_RESOURCE_BUFFER_SIZE }; }; #endif diff --git a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp index 3c34765011..2e1084e581 100644 --- a/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp +++ b/libraries/gpu-gl/src/gpu/gl/GLBackend.cpp @@ -663,9 +663,19 @@ void GLBackend::recycle() const { Lock lock(_trashMutex); std::swap(_externalTexturesTrash, externalTexturesTrash); } - for (auto pair : externalTexturesTrash) { - auto fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); - pair.second(pair.first, fence); + if (!externalTexturesTrash.empty()) { + std::vector fences; + fences.resize(externalTexturesTrash.size()); + for (size_t i = 0; i < externalTexturesTrash.size(); ++i) { + fences[i] = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + } + // External texture fences will be read in another thread/context, so we need a flush + glFlush(); + size_t index = 0; + for (auto pair : externalTexturesTrash) { + auto fence = fences[index++]; + pair.second(pair.first, fence); + } } } diff --git a/libraries/networking/src/AddressManager.cpp b/libraries/networking/src/AddressManager.cpp index f7f305dcc8..6cba4873ac 100644 --- a/libraries/networking/src/AddressManager.cpp +++ b/libraries/networking/src/AddressManager.cpp @@ -831,32 +831,3 @@ void AddressManager::addCurrentAddressToHistory(LookupTrigger trigger) { } } -void AddressManager::ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, - std::function localSandboxNotRunningDoThat) { - - QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); - QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL); - sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); - sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); - QNetworkReply* reply = networkAccessManager.get(sandboxStatus); - - connect(reply, &QNetworkReply::finished, this, [reply, localSandboxRunningDoThis, localSandboxNotRunningDoThat]() { - auto statusData = reply->readAll(); - auto statusJson = QJsonDocument::fromJson(statusData); - if (!statusJson.isEmpty()) { - auto statusObject = statusJson.object(); - auto serversValue = statusObject.value("servers"); - if (!serversValue.isUndefined() && serversValue.isObject()) { - auto serversObject = serversValue.toObject(); - auto serversCount = serversObject.size(); - const int MINIMUM_EXPECTED_SERVER_COUNT = 5; - if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) { - localSandboxRunningDoThis(); - return; - } - } - } - localSandboxNotRunningDoThat(); - }); -} - diff --git a/libraries/networking/src/AddressManager.h b/libraries/networking/src/AddressManager.h index ab7b53a6d9..1707132c08 100644 --- a/libraries/networking/src/AddressManager.h +++ b/libraries/networking/src/AddressManager.h @@ -25,7 +25,6 @@ const QString HIFI_URL_SCHEME = "hifi"; const QString DEFAULT_HIFI_ADDRESS = "hifi://welcome"; const QString SANDBOX_HIFI_ADDRESS = "hifi://localhost"; -const QString SANDBOX_STATUS_URL = "http://localhost:60332/status"; const QString INDEX_PATH = "/"; const QString GET_PLACE = "/api/v1/places/%1"; @@ -78,11 +77,6 @@ public: const QStack& getBackStack() const { return _backStack; } const QStack& getForwardStack() const { return _forwardStack; } - /// determines if the local sandbox is likely running. It does not account for custom setups, and is only - /// intended to detect the standard local sandbox install. - void ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, - std::function localSandboxNotRunningDoThat); - public slots: void handleLookupString(const QString& lookupString, bool fromSuggestions = false); diff --git a/libraries/networking/src/AssetClient.cpp b/libraries/networking/src/AssetClient.cpp index 6a2cbf20cf..0563141511 100644 --- a/libraries/networking/src/AssetClient.cpp +++ b/libraries/networking/src/AssetClient.cpp @@ -45,6 +45,8 @@ AssetClient::AssetClient() { packetReceiver.registerListener(PacketType::AssetUploadReply, this, "handleAssetUploadReply"); connect(nodeList.data(), &LimitedNodeList::nodeKilled, this, &AssetClient::handleNodeKilled); + connect(nodeList.data(), &LimitedNodeList::clientConnectionToNodeReset, + this, &AssetClient::handleNodeClientConnectionReset); } void AssetClient::init() { @@ -233,15 +235,15 @@ MessageID AssetClient::getAsset(const QString& hash, DataOffset start, DataOffse packet->writePrimitive(start); packet->writePrimitive(end); - nodeList->sendPacket(std::move(packet), *assetServer); + if (nodeList->sendPacket(std::move(packet), *assetServer) != -1) { + _pendingRequests[assetServer][messageID] = { QSharedPointer(), callback, progressCallback }; - _pendingRequests[assetServer][messageID] = { QSharedPointer(), callback, progressCallback }; - - return messageID; - } else { - callback(false, AssetServerError::NoError, QByteArray()); - return INVALID_MESSAGE_ID; + return messageID; + } } + + callback(false, AssetServerError::NoError, QByteArray()); + return INVALID_MESSAGE_ID; } MessageID AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callback) { @@ -259,15 +261,15 @@ MessageID AssetClient::getAssetInfo(const QString& hash, GetInfoCallback callbac packet->writePrimitive(messageID); packet->write(QByteArray::fromHex(hash.toLatin1())); - nodeList->sendPacket(std::move(packet), *assetServer); + if (nodeList->sendPacket(std::move(packet), *assetServer) != -1) { + _pendingInfoRequests[assetServer][messageID] = callback; - _pendingInfoRequests[assetServer][messageID] = callback; - - return messageID; - } else { - callback(false, AssetServerError::NoError, { "", 0 }); - return INVALID_MESSAGE_ID; + return messageID; + } } + + callback(false, AssetServerError::NoError, { "", 0 }); + return INVALID_MESSAGE_ID; } void AssetClient::handleAssetGetInfoReply(QSharedPointer message, SharedNodePointer senderNode) { @@ -453,15 +455,15 @@ MessageID AssetClient::getAssetMapping(const AssetPath& path, MappingOperationCa packetList->writeString(path); - nodeList->sendPacketList(std::move(packetList), *assetServer); + if (nodeList->sendPacketList(std::move(packetList), *assetServer) != -1) { + _pendingMappingRequests[assetServer][messageID] = callback; - _pendingMappingRequests[assetServer][messageID] = callback; - - return messageID; - } else { - callback(false, AssetServerError::NoError, QSharedPointer()); - return INVALID_MESSAGE_ID; + return messageID; + } } + + callback(false, AssetServerError::NoError, QSharedPointer()); + return INVALID_MESSAGE_ID; } MessageID AssetClient::getAllAssetMappings(MappingOperationCallback callback) { @@ -478,15 +480,15 @@ MessageID AssetClient::getAllAssetMappings(MappingOperationCallback callback) { packetList->writePrimitive(AssetMappingOperationType::GetAll); - nodeList->sendPacketList(std::move(packetList), *assetServer); + if (nodeList->sendPacketList(std::move(packetList), *assetServer) != -1) { + _pendingMappingRequests[assetServer][messageID] = callback; - _pendingMappingRequests[assetServer][messageID] = callback; - - return messageID; - } else { - callback(false, AssetServerError::NoError, QSharedPointer()); - return INVALID_MESSAGE_ID; + return messageID; + } } + + callback(false, AssetServerError::NoError, QSharedPointer()); + return INVALID_MESSAGE_ID; } MessageID AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOperationCallback callback) { @@ -507,15 +509,15 @@ MessageID AssetClient::deleteAssetMappings(const AssetPathList& paths, MappingOp packetList->writeString(path); } - nodeList->sendPacketList(std::move(packetList), *assetServer); + if (nodeList->sendPacketList(std::move(packetList), *assetServer) != -1) { + _pendingMappingRequests[assetServer][messageID] = callback; - _pendingMappingRequests[assetServer][messageID] = callback; - - return messageID; - } else { - callback(false, AssetServerError::NoError, QSharedPointer()); - return INVALID_MESSAGE_ID; + return messageID; + } } + + callback(false, AssetServerError::NoError, QSharedPointer()); + return INVALID_MESSAGE_ID; } MessageID AssetClient::setAssetMapping(const QString& path, const AssetHash& hash, MappingOperationCallback callback) { @@ -535,15 +537,15 @@ MessageID AssetClient::setAssetMapping(const QString& path, const AssetHash& has packetList->writeString(path); packetList->write(QByteArray::fromHex(hash.toUtf8())); - nodeList->sendPacketList(std::move(packetList), *assetServer); + if (nodeList->sendPacketList(std::move(packetList), *assetServer) != -1) { + _pendingMappingRequests[assetServer][messageID] = callback; - _pendingMappingRequests[assetServer][messageID] = callback; - - return messageID; - } else { - callback(false, AssetServerError::NoError, QSharedPointer()); - return INVALID_MESSAGE_ID; + return messageID; + } } + + callback(false, AssetServerError::NoError, QSharedPointer()); + return INVALID_MESSAGE_ID; } MessageID AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetPath& newPath, MappingOperationCallback callback) { @@ -561,16 +563,16 @@ MessageID AssetClient::renameAssetMapping(const AssetPath& oldPath, const AssetP packetList->writeString(oldPath); packetList->writeString(newPath); - nodeList->sendPacketList(std::move(packetList), *assetServer); + if (nodeList->sendPacketList(std::move(packetList), *assetServer) != -1) { + _pendingMappingRequests[assetServer][messageID] = callback; - _pendingMappingRequests[assetServer][messageID] = callback; + return messageID; - return messageID; - - } else { - callback(false, AssetServerError::NoError, QSharedPointer()); - return INVALID_MESSAGE_ID; + } } + + callback(false, AssetServerError::NoError, QSharedPointer()); + return INVALID_MESSAGE_ID; } bool AssetClient::cancelMappingRequest(MessageID id) { @@ -646,15 +648,15 @@ MessageID AssetClient::uploadAsset(const QByteArray& data, UploadResultCallback packetList->writePrimitive(size); packetList->write(data.constData(), size); - nodeList->sendPacketList(std::move(packetList), *assetServer); + if (nodeList->sendPacketList(std::move(packetList), *assetServer) != -1) { + _pendingUploads[assetServer][messageID] = callback; - _pendingUploads[assetServer][messageID] = callback; - - return messageID; - } else { - callback(false, AssetServerError::NoError, QString()); - return INVALID_MESSAGE_ID; + return messageID; + } } + + callback(false, AssetServerError::NoError, QString()); + return INVALID_MESSAGE_ID; } void AssetClient::handleAssetUploadReply(QSharedPointer message, SharedNodePointer senderNode) { @@ -704,6 +706,34 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) { return; } + forceFailureOfPendingRequests(node); + + { + auto messageMapIt = _pendingUploads.find(node); + if (messageMapIt != _pendingUploads.end()) { + for (const auto& value : messageMapIt->second) { + value.second(false, AssetServerError::NoError, ""); + } + messageMapIt->second.clear(); + } + } +} + +void AssetClient::handleNodeClientConnectionReset(SharedNodePointer node) { + // a client connection to a Node was reset + // if it was an AssetServer we need to cause anything pending to fail so it is re-attempted + + if (node->getType() != NodeType::AssetServer) { + return; + } + + qCDebug(asset_client) << "AssetClient detected client connection reset handshake with Asset Server - failing any pending requests"; + + forceFailureOfPendingRequests(node); +} + +void AssetClient::forceFailureOfPendingRequests(SharedNodePointer node) { + { auto messageMapIt = _pendingRequests.find(node); if (messageMapIt != _pendingRequests.end()) { @@ -731,16 +761,6 @@ void AssetClient::handleNodeKilled(SharedNodePointer node) { } } - { - auto messageMapIt = _pendingUploads.find(node); - if (messageMapIt != _pendingUploads.end()) { - for (const auto& value : messageMapIt->second) { - value.second(false, AssetServerError::NoError, ""); - } - messageMapIt->second.clear(); - } - } - { auto messageMapIt = _pendingMappingRequests.find(node); if (messageMapIt != _pendingMappingRequests.end()) { diff --git a/libraries/networking/src/AssetClient.h b/libraries/networking/src/AssetClient.h index 536a2e3603..10a88c0676 100644 --- a/libraries/networking/src/AssetClient.h +++ b/libraries/networking/src/AssetClient.h @@ -75,6 +75,7 @@ private slots: void handleAssetUploadReply(QSharedPointer message, SharedNodePointer senderNode); void handleNodeKilled(SharedNodePointer node); + void handleNodeClientConnectionReset(SharedNodePointer node); private: MessageID getAssetMapping(const AssetHash& hash, MappingOperationCallback callback); @@ -96,6 +97,8 @@ private: void handleProgressCallback(const QWeakPointer& node, MessageID messageID, qint64 size, DataOffset length); void handleCompleteCallback(const QWeakPointer& node, MessageID messageID); + void forceFailureOfPendingRequests(SharedNodePointer node); + struct GetAssetRequestData { QSharedPointer message; ReceivedAssetCallback completeCallback; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index ce555315e8..e652c2deb3 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -32,6 +32,7 @@ #include #include "AccountManager.h" +#include "AssetClient.h" #include "Assignment.h" #include "HifiSockAddr.h" #include "NetworkLogging.h" @@ -41,7 +42,8 @@ static Setting::Handle LIMITED_NODELIST_LOCAL_PORT("LimitedNodeList.Loc const std::set SOLO_NODE_TYPES = { NodeType::AvatarMixer, - NodeType::AudioMixer + NodeType::AudioMixer, + NodeType::AssetServer }; LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : @@ -113,6 +115,12 @@ LimitedNodeList::LimitedNodeList(int socketListenPort, int dtlsListenPort) : using std::placeholders::_1; _nodeSocket.setPacketFilterOperator(std::bind(&LimitedNodeList::isPacketVerified, this, _1)); + // set our socketBelongsToNode method as the connection creation filter operator for the udt::Socket + _nodeSocket.setConnectionCreationFilterOperator(std::bind(&LimitedNodeList::sockAddrBelongsToNode, this, _1)); + + // handle when a socket connection has its receiver side reset - might need to emit clientConnectionToNodeReset + connect(&_nodeSocket, &udt::Socket::clientHandshakeRequestComplete, this, &LimitedNodeList::clientConnectionToSockAddrReset); + _packetStatTimer.start(); if (_stunSockAddr.getAddress().isNull()) { @@ -317,6 +325,8 @@ void LimitedNodeList::fillPacketHeader(const NLPacket& packet, const QUuid& conn } } +static const qint64 ERROR_SENDING_PACKET_BYTES = -1; + qint64 LimitedNodeList::sendUnreliablePacket(const NLPacket& packet, const Node& destinationNode) { Q_ASSERT(!packet.isPartOfMessage()); @@ -353,7 +363,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& return sendPacket(std::move(packet), *activeSocket, destinationNode.getConnectionSecret()); } else { qCDebug(networking) << "LimitedNodeList::sendPacket called without active socket for node" << destinationNode << "- not sending"; - return 0; + return ERROR_SENDING_PACKET_BYTES; } } @@ -392,7 +402,7 @@ qint64 LimitedNodeList::sendPacketList(NLPacketList& packetList, const Node& des } else { qCDebug(networking) << "LimitedNodeList::sendPacketList called without active socket for node" << destinationNode << " - not sending."; - return 0; + return ERROR_SENDING_PACKET_BYTES; } } @@ -438,7 +448,7 @@ qint64 LimitedNodeList::sendPacketList(std::unique_ptr packetList, return _nodeSocket.writePacketList(std::move(packetList), *activeSocket); } else { qCDebug(networking) << "LimitedNodeList::sendPacketList called without active socket for node. Not sending."; - return 0; + return ERROR_SENDING_PACKET_BYTES; } } @@ -446,7 +456,7 @@ qint64 LimitedNodeList::sendPacket(std::unique_ptr packet, const Node& const HifiSockAddr& overridenSockAddr) { if (overridenSockAddr.isNull() && !destinationNode.getActiveSocket()) { qCDebug(networking) << "LimitedNodeList::sendPacket called without active socket for node. Not sending."; - return 0; + return ERROR_SENDING_PACKET_BYTES; } // use the node's active socket as the destination socket if there is no overriden socket address @@ -1136,3 +1146,23 @@ void LimitedNodeList::flagTimeForConnectionStep(ConnectionStep connectionStep, q } } } + +void LimitedNodeList::clientConnectionToSockAddrReset(const HifiSockAddr& sockAddr) { + // for certain reliable channels higher level classes may need to know if the udt::Connection has been reset + auto matchingNode = findNodeWithAddr(sockAddr); + + if (matchingNode) { + emit clientConnectionToNodeReset(matchingNode); + } +} + +#if (PR_BUILD || DEV_BUILD) + +void LimitedNodeList::sendFakedHandshakeRequestToNode(SharedNodePointer node) { + + if (node && node->getActiveSocket()) { + _nodeSocket.sendFakedHandshakeRequest(*node->getActiveSocket()); + } +} + +#endif diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index e74a6c49f8..2f9462ae8b 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -41,6 +41,7 @@ #include "NLPacketList.h" #include "PacketReceiver.h" #include "ReceivedMessage.h" +#include "udt/ControlPacket.h" #include "udt/PacketHeaders.h" #include "udt/Socket.h" #include "UUIDHasher.h" @@ -235,6 +236,9 @@ public: static void makeSTUNRequestPacket(char* stunRequestPacket); +#if (PR_BUILD || DEV_BUILD) + void sendFakedHandshakeRequestToNode(SharedNodePointer node); +#endif public slots: void reset(); @@ -262,6 +266,8 @@ signals: void nodeKilled(SharedNodePointer); void nodeActivated(SharedNodePointer); + void clientConnectionToNodeReset(SharedNodePointer); + void localSockAddrChanged(const HifiSockAddr& localSockAddr); void publicSockAddrChanged(const HifiSockAddr& publicSockAddr); @@ -274,6 +280,8 @@ signals: protected slots: void connectedForLocalSocketTest(); void errorTestingLocalSocket(); + + void clientConnectionToSockAddrReset(const HifiSockAddr& sockAddr); protected: LimitedNodeList(int socketListenPort = INVALID_PORT, int dtlsListenPort = INVALID_PORT); @@ -299,6 +307,8 @@ protected: void sendPacketToIceServer(PacketType packetType, const HifiSockAddr& iceServerSockAddr, const QUuid& clientID, const QUuid& peerRequestID = QUuid()); + bool sockAddrBelongsToNode(const HifiSockAddr& sockAddr) { return findNodeWithAddr(sockAddr) != SharedNodePointer(); } + QUuid _sessionUUID; NodeHash _nodeHash; mutable QReadWriteLock _nodeMutex; diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 27e6f17c33..f2b28a04a7 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -104,6 +104,10 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) connect(&_domainHandler, SIGNAL(connectedToDomain(QString)), &_keepAlivePingTimer, SLOT(start())); connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, &_keepAlivePingTimer, &QTimer::stop); + // set our sockAddrBelongsToDomainOrNode method as the connection creation filter for the udt::Socket + using std::placeholders::_1; + _nodeSocket.setConnectionCreationFilterOperator(std::bind(&NodeList::sockAddrBelongsToDomainOrNode, this, _1)); + // we definitely want STUN to update our public socket, so call the LNL to kick that off startSTUNPublicSocketUpdate(); @@ -703,6 +707,10 @@ void NodeList::sendKeepAlivePings() { }); } +bool NodeList::sockAddrBelongsToDomainOrNode(const HifiSockAddr& sockAddr) { + return _domainHandler.getSockAddr() == sockAddr || LimitedNodeList::sockAddrBelongsToNode(sockAddr); +} + void NodeList::ignoreNodeBySessionID(const QUuid& nodeID) { // enumerate the nodes to send a reliable ignore packet to each that can leverage it diff --git a/libraries/networking/src/NodeList.h b/libraries/networking/src/NodeList.h index f08c0dbe45..7f98b8c736 100644 --- a/libraries/networking/src/NodeList.h +++ b/libraries/networking/src/NodeList.h @@ -132,6 +132,8 @@ private: void pingPunchForInactiveNode(const SharedNodePointer& node); + bool sockAddrBelongsToDomainOrNode(const HifiSockAddr& sockAddr); + NodeType_t _ownerType; NodeSet _nodeTypesOfInterest; DomainHandler _domainHandler; diff --git a/libraries/networking/src/SandboxUtils.cpp b/libraries/networking/src/SandboxUtils.cpp new file mode 100644 index 0000000000..98c963b793 --- /dev/null +++ b/libraries/networking/src/SandboxUtils.cpp @@ -0,0 +1,96 @@ +// +// SandboxUtils.cpp +// libraries/networking/src +// +// Created by Brad Hefta-Gaub on 2016-10-15. +// 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "SandboxUtils.h" +#include "NetworkAccessManager.h" + + +void SandboxUtils::ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, + std::function localSandboxNotRunningDoThat) { + + QNetworkAccessManager& networkAccessManager = NetworkAccessManager::getInstance(); + QNetworkRequest sandboxStatus(SANDBOX_STATUS_URL); + sandboxStatus.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true); + sandboxStatus.setHeader(QNetworkRequest::UserAgentHeader, HIGH_FIDELITY_USER_AGENT); + QNetworkReply* reply = networkAccessManager.get(sandboxStatus); + + connect(reply, &QNetworkReply::finished, this, [reply, localSandboxRunningDoThis, localSandboxNotRunningDoThat]() { + if (reply->error() == QNetworkReply::NoError) { + auto statusData = reply->readAll(); + auto statusJson = QJsonDocument::fromJson(statusData); + if (!statusJson.isEmpty()) { + auto statusObject = statusJson.object(); + auto serversValue = statusObject.value("servers"); + if (!serversValue.isUndefined() && serversValue.isObject()) { + auto serversObject = serversValue.toObject(); + auto serversCount = serversObject.size(); + const int MINIMUM_EXPECTED_SERVER_COUNT = 5; + if (serversCount >= MINIMUM_EXPECTED_SERVER_COUNT) { + localSandboxRunningDoThis(); + return; + } + } + } + } + localSandboxNotRunningDoThat(); + }); +} + + +void SandboxUtils::runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName) { + QString applicationDirPath = QFileInfo(QCoreApplication::applicationFilePath()).path(); + QString serverPath = applicationDirPath + "/server-console/server-console.exe"; + qDebug() << "Application dir path is: " << applicationDirPath; + qDebug() << "Server path is: " << serverPath; + qDebug() << "autoShutdown: " << autoShutdown; + + bool hasContentPath = !contentPath.isEmpty(); + bool passArgs = autoShutdown || hasContentPath; + + QStringList args; + + if (passArgs) { + args << "--"; + } + + if (hasContentPath) { + QString serverContentPath = applicationDirPath + "/" + contentPath; + args << "--contentPath" << serverContentPath; + } + + if (autoShutdown) { + QString interfaceRunningStateFile = RunningMarker::getMarkerFilePath(runningMarkerName); + args << "--shutdownWatcher" << interfaceRunningStateFile; + } + + qDebug() << applicationDirPath; + qDebug() << "Launching sandbox with:" << args; + qDebug() << QProcess::startDetached(serverPath, args); + + // Sleep a short amount of time to give the server a chance to start + usleep(2000000); /// do we really need this?? +} diff --git a/libraries/networking/src/SandboxUtils.h b/libraries/networking/src/SandboxUtils.h new file mode 100644 index 0000000000..e40ff71a56 --- /dev/null +++ b/libraries/networking/src/SandboxUtils.h @@ -0,0 +1,32 @@ +// +// SandboxUtils.h +// libraries/networking/src +// +// Created by Brad Hefta-Gaub on 2016-10-15. +// 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 +// + +#ifndef hifi_SandboxUtils_h +#define hifi_SandboxUtils_h + +#include +#include + + +const QString SANDBOX_STATUS_URL = "http://localhost:60332/status"; + +class SandboxUtils : public QObject { + Q_OBJECT +public: + /// determines if the local sandbox is likely running. It does not account for custom setups, and is only + /// intended to detect the standard local sandbox install. + void ifLocalSandboxRunningElse(std::function localSandboxRunningDoThis, + std::function localSandboxNotRunningDoThat); + + static void runLocalSandbox(QString contentPath, bool autoShutdown, QString runningMarkerName); +}; + +#endif // hifi_SandboxUtils_h diff --git a/libraries/networking/src/udt/Connection.cpp b/libraries/networking/src/udt/Connection.cpp index dcceef0fdc..53cacaeeb4 100644 --- a/libraries/networking/src/udt/Connection.cpp +++ b/libraries/networking/src/udt/Connection.cpp @@ -426,18 +426,25 @@ SequenceNumber Connection::nextACK() const { } } +void Connection::sendHandshakeRequest() { + auto handshakeRequestPacket = ControlPacket::create(ControlPacket::HandshakeRequest, 0); + _parentSocket->writeBasePacket(*handshakeRequestPacket, _destination); + + _didRequestHandshake = true; +} + bool Connection::processReceivedSequenceNumber(SequenceNumber sequenceNumber, int packetSize, int payloadSize) { if (!_hasReceivedHandshake) { // Refuse to process any packets until we've received the handshake // Send handshake request to re-request a handshake - auto handshakeRequestPacket = ControlPacket::create(ControlPacket::HandshakeRequest, 0); - _parentSocket->writeBasePacket(*handshakeRequestPacket, _destination); #ifdef UDT_CONNECTION_DEBUG qCDebug(networking) << "Received packet before receiving handshake, sending HandshakeRequest"; #endif + sendHandshakeRequest(); + return false; } @@ -789,6 +796,11 @@ void Connection::processHandshake(ControlPacketPointer controlPacket) { // indicate that handshake has been received _hasReceivedHandshake = true; + + if (_didRequestHandshake) { + emit receiverHandshakeRequestComplete(_destination); + _didRequestHandshake = false; + } } void Connection::processHandshakeACK(ControlPacketPointer controlPacket) { diff --git a/libraries/networking/src/udt/Connection.h b/libraries/networking/src/udt/Connection.h index d6121d47b2..a18a23f160 100644 --- a/libraries/networking/src/udt/Connection.h +++ b/libraries/networking/src/udt/Connection.h @@ -79,10 +79,13 @@ public: void setMaxBandwidth(int maxBandwidth); + void sendHandshakeRequest(); + signals: void packetSent(); void connectionInactive(const HifiSockAddr& sockAddr); - + void receiverHandshakeRequestComplete(const HifiSockAddr& sockAddr); + private slots: void recordSentPackets(int payload, int total); void recordRetransmission(); @@ -129,6 +132,7 @@ private: bool _hasReceivedHandshake { false }; // flag for receipt of handshake from server bool _hasReceivedHandshakeACK { false }; // flag for receipt of handshake ACK from client + bool _didRequestHandshake { false }; // flag for request of handshake from server p_high_resolution_clock::time_point _connectionStart = p_high_resolution_clock::now(); // holds the time_point for creation of this connection p_high_resolution_clock::time_point _lastReceiveTime; // holds the last time we received anything from sender diff --git a/libraries/networking/src/udt/SendQueue.cpp b/libraries/networking/src/udt/SendQueue.cpp index ba43076b31..65619e2b50 100644 --- a/libraries/networking/src/udt/SendQueue.cpp +++ b/libraries/networking/src/udt/SendQueue.cpp @@ -97,6 +97,9 @@ SendQueue::SendQueue(Socket* socket, HifiSockAddr dest) : _currentSequenceNumber = _initialSequenceNumber - 1; _atomicCurrentSequenceNumber = uint32_t(_currentSequenceNumber); _lastACKSequenceNumber = uint32_t(_currentSequenceNumber) - 1; + + // default the last receiver response to the current time + _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); } void SendQueue::queuePacket(std::unique_ptr packet) { @@ -166,7 +169,7 @@ void SendQueue::ack(SequenceNumber ack) { void SendQueue::nak(SequenceNumber start, SequenceNumber end) { // this is a response from the client, re-set our timeout expiry - _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); + _lastReceiverResponse = QDateTime::currentMSecsSinceEpoch(); { std::lock_guard nakLocker(_naksLock); @@ -520,7 +523,6 @@ bool SendQueue::isInactive(bool attemptedToSendPacket) { if (sinceLastResponse > 0 && sinceLastResponse >= int64_t(NUM_TIMEOUTS_BEFORE_INACTIVE * (_estimatedTimeout / USECS_PER_MSEC)) && - _lastReceiverResponse > 0 && sinceLastResponse > MIN_MS_BEFORE_INACTIVE) { // If the flow window has been full for over CONSIDER_INACTIVE_AFTER, // then signal the queue is inactive and return so it can be cleaned up diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index 37ededa55c..03a32a5fe3 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -171,11 +171,28 @@ qint64 Socket::writePacketList(std::unique_ptr packetList, const Hif } void Socket::writeReliablePacket(Packet* packet, const HifiSockAddr& sockAddr) { - findOrCreateConnection(sockAddr).sendReliablePacket(std::unique_ptr(packet)); + auto connection = findOrCreateConnection(sockAddr); + if (connection) { + connection->sendReliablePacket(std::unique_ptr(packet)); + } +#ifdef UDT_CONNECTION_DEBUG + else { + qCDebug(networking) << "Socket::writeReliablePacket refusing to send packet - no connection was created"; + } +#endif + } void Socket::writeReliablePacketList(PacketList* packetList, const HifiSockAddr& sockAddr) { - findOrCreateConnection(sockAddr).sendReliablePacketList(std::unique_ptr(packetList)); + auto connection = findOrCreateConnection(sockAddr); + if (connection) { + connection->sendReliablePacketList(std::unique_ptr(packetList)); + } +#ifdef UDT_CONNECTION_DEBUG + else { + qCDebug(networking) << "Socket::writeReliablePacketList refusing to send packet list - no connection was created"; + } +#endif } qint64 Socket::writeDatagram(const char* data, qint64 size, const HifiSockAddr& sockAddr) { @@ -198,25 +215,40 @@ qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& soc return bytesWritten; } -Connection& Socket::findOrCreateConnection(const HifiSockAddr& sockAddr) { +Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr) { auto it = _connectionsHash.find(sockAddr); if (it == _connectionsHash.end()) { - auto congestionControl = _ccFactory->create(); - congestionControl->setMaxBandwidth(_maxBandwidth); - auto connection = std::unique_ptr(new Connection(this, sockAddr, std::move(congestionControl))); + // we did not have a matching connection, time to see if we should make one - // we queue the connection to cleanup connection in case it asks for it during its own rate control sync - QObject::connect(connection.get(), &Connection::connectionInactive, this, &Socket::cleanupConnection); + if (_connectionCreationFilterOperator && !_connectionCreationFilterOperator(sockAddr)) { + // the connection creation filter did not allow us to create a new connection +#ifdef UDT_CONNECTION_DEBUG + qCDebug(networking) << "Socket::findOrCreateConnection refusing to create connection for" << sockAddr + << "due to connection creation filter"; +#endif + return nullptr; + } else { + auto congestionControl = _ccFactory->create(); + congestionControl->setMaxBandwidth(_maxBandwidth); + auto connection = std::unique_ptr(new Connection(this, sockAddr, std::move(congestionControl))); + + // we queue the connection to cleanup connection in case it asks for it during its own rate control sync + QObject::connect(connection.get(), &Connection::connectionInactive, this, &Socket::cleanupConnection); + + // allow higher-level classes to find out when connections have completed a handshake + QObject::connect(connection.get(), &Connection::receiverHandshakeRequestComplete, + this, &Socket::clientHandshakeRequestComplete); #ifdef UDT_CONNECTION_DEBUG - qCDebug(networking) << "Creating new connection to" << sockAddr; + qCDebug(networking) << "Creating new connection to" << sockAddr; #endif - it = _connectionsHash.insert(it, std::make_pair(sockAddr, std::move(connection))); + it = _connectionsHash.insert(it, std::make_pair(sockAddr, std::move(connection))); + } } - return *it->second; + return it->second.get(); } void Socket::clearConnections() { @@ -292,9 +324,12 @@ void Socket::readPendingDatagrams() { // setup a control packet from the data we just read auto controlPacket = ControlPacket::fromReceivedPacket(std::move(buffer), packetSizeWithHeader, senderSockAddr); - // move this control packet to the matching connection - auto& connection = findOrCreateConnection(senderSockAddr); - connection.processControl(move(controlPacket)); + // move this control packet to the matching connection, if there is one + auto connection = findOrCreateConnection(senderSockAddr); + + if (connection) { + connection->processControl(move(controlPacket)); + } } else { // setup a Packet from the data we just read @@ -304,19 +339,21 @@ void Socket::readPendingDatagrams() { if (!_packetFilterOperator || _packetFilterOperator(*packet)) { if (packet->isReliable()) { // if this was a reliable packet then signal the matching connection with the sequence number - auto& connection = findOrCreateConnection(senderSockAddr); + auto connection = findOrCreateConnection(senderSockAddr); - if (!connection.processReceivedSequenceNumber(packet->getSequenceNumber(), - packet->getDataSize(), - packet->getPayloadSize())) { - // the connection indicated that we should not continue processing this packet + if (!connection || !connection->processReceivedSequenceNumber(packet->getSequenceNumber(), + packet->getDataSize(), + packet->getPayloadSize())) { + // the connection could not be created or indicated that we should not continue processing this packet continue; } } if (packet->isPartOfMessage()) { - auto& connection = findOrCreateConnection(senderSockAddr); - connection.queueReceivedMessagePacket(std::move(packet)); + auto connection = findOrCreateConnection(senderSockAddr); + if (connection) { + connection->queueReceivedMessagePacket(std::move(packet)); + } } else if (_packetHandler) { // call the verified packet callback to let it handle this packet _packetHandler(std::move(packet)); @@ -427,3 +464,14 @@ void Socket::handleStateChanged(QAbstractSocket::SocketState socketState) { qCWarning(networking) << "udt::Socket state changed - state is now" << socketState; } } + +#if (PR_BUILD || DEV_BUILD) + +void Socket::sendFakedHandshakeRequest(const HifiSockAddr& sockAddr) { + auto connection = findOrCreateConnection(sockAddr); + if (connection) { + connection->sendHandshakeRequest(); + } +} + +#endif diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index bc4393d4bd..59c3ec9cde 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -37,6 +37,7 @@ class PacketList; class SequenceNumber; using PacketFilterOperator = std::function; +using ConnectionCreationFilterOperator = std::function; using BasePacketHandler = std::function)>; using PacketHandler = std::function)>; @@ -68,6 +69,8 @@ public: void setPacketHandler(PacketHandler handler) { _packetHandler = handler; } void setMessageHandler(MessageHandler handler) { _messageHandler = handler; } void setMessageFailureHandler(MessageFailureHandler handler) { _messageFailureHandler = handler; } + void setConnectionCreationFilterOperator(ConnectionCreationFilterOperator filterOperator) + { _connectionCreationFilterOperator = filterOperator; } void addUnfilteredHandler(const HifiSockAddr& senderSockAddr, BasePacketHandler handler) { _unfilteredHandlers[senderSockAddr] = handler; } @@ -80,6 +83,13 @@ public: StatsVector sampleStatsForAllConnections(); +#if (PR_BUILD || DEV_BUILD) + void sendFakedHandshakeRequest(const HifiSockAddr& sockAddr); +#endif + +signals: + void clientHandshakeRequestComplete(const HifiSockAddr& sockAddr); + public slots: void cleanupConnection(HifiSockAddr sockAddr); void clearConnections(); @@ -93,7 +103,8 @@ private slots: private: void setSystemBufferSizes(); - Connection& findOrCreateConnection(const HifiSockAddr& sockAddr); + Connection* findOrCreateConnection(const HifiSockAddr& sockAddr); + bool socketMatchesNodeOrDomain(const HifiSockAddr& sockAddr); // privatized methods used by UDTTest - they are private since they must be called on the Socket thread ConnectionStats::Stats sampleStatsForConnection(const HifiSockAddr& destination); @@ -109,6 +120,7 @@ private: PacketHandler _packetHandler; MessageHandler _messageHandler; MessageFailureHandler _messageFailureHandler; + ConnectionCreationFilterOperator _connectionCreationFilterOperator; std::unordered_map _unfilteredHandlers; std::unordered_map _unreliableSequenceNumbers; diff --git a/libraries/script-engine/src/AssetScriptingInterface.cpp b/libraries/script-engine/src/AssetScriptingInterface.cpp index 1065cc2e6a..00fa1f3ba5 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.cpp +++ b/libraries/script-engine/src/AssetScriptingInterface.cpp @@ -17,6 +17,7 @@ #include #include #include +#include AssetScriptingInterface::AssetScriptingInterface(QScriptEngine* engine) : _engine(engine) @@ -86,3 +87,13 @@ void AssetScriptingInterface::downloadData(QString urlString, QScriptValue callb assetRequest->start(); } + +#if (PR_BUILD || DEV_BUILD) +void AssetScriptingInterface::sendFakedHandshake() { + auto nodeList = DependencyManager::get(); + SharedNodePointer assetServer = nodeList->soloNodeOfType(NodeType::AssetServer); + + nodeList->sendFakedHandshakeRequestToNode(assetServer); +} + +#endif diff --git a/libraries/script-engine/src/AssetScriptingInterface.h b/libraries/script-engine/src/AssetScriptingInterface.h index 85746ad36e..82d220ab34 100644 --- a/libraries/script-engine/src/AssetScriptingInterface.h +++ b/libraries/script-engine/src/AssetScriptingInterface.h @@ -28,6 +28,10 @@ public: Q_INVOKABLE void downloadData(QString url, QScriptValue downloadComplete); Q_INVOKABLE void setMapping(QString path, QString hash, QScriptValue callback); +#if (PR_BUILD || DEV_BUILD) + Q_INVOKABLE void sendFakedHandshake(); +#endif + protected: QSet _pendingRequests; QScriptEngine* _engine; diff --git a/libraries/shared/src/RunningMarker.cpp b/libraries/shared/src/RunningMarker.cpp new file mode 100644 index 0000000000..d0d45fd517 --- /dev/null +++ b/libraries/shared/src/RunningMarker.cpp @@ -0,0 +1,76 @@ +// +// RunningMarker.cpp +// libraries/shared/src +// +// Created by Brad Hefta-Gaub on 2016-10-16 +// 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 "RunningMarker.h" + +#include +#include +#include +#include + +#include "NumericalConstants.h" +#include "PathUtils.h" + + +RunningMarker::RunningMarker(QObject* parent, QString name) : + _parent(parent), + _name(name) +{ +} + +void RunningMarker::startRunningMarker() { + static const int RUNNING_STATE_CHECK_IN_MSECS = MSECS_PER_SECOND; + + // start the nodeThread so its event loop is running + QThread* runningMarkerThread = new QThread(_parent); + runningMarkerThread->setObjectName("Running Marker Thread"); + runningMarkerThread->start(); + + writeRunningMarkerFiler(); // write the first file, even before timer + + QTimer* runningMarkerTimer = new QTimer(_parent); + QObject::connect(runningMarkerTimer, &QTimer::timeout, [=](){ + writeRunningMarkerFiler(); + }); + runningMarkerTimer->start(RUNNING_STATE_CHECK_IN_MSECS); + + // put the time on the thread + runningMarkerTimer->moveToThread(runningMarkerThread); +} + +RunningMarker::~RunningMarker() { + deleteRunningMarkerFile(); +} + +void RunningMarker::writeRunningMarkerFiler() { + QFile runningMarkerFile(getFilePath()); + + // always write, even it it exists, so that it touches the files + if (runningMarkerFile.open(QIODevice::WriteOnly)) { + runningMarkerFile.close(); + } +} + +void RunningMarker::deleteRunningMarkerFile() { + QFile runningMarkerFile(getFilePath()); + if (runningMarkerFile.exists()) { + runningMarkerFile.remove(); + } +} + +QString RunningMarker::getFilePath() { + return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + _name; +} + +QString RunningMarker::getMarkerFilePath(QString name) { + return QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + name; +} + diff --git a/libraries/shared/src/RunningMarker.h b/libraries/shared/src/RunningMarker.h new file mode 100644 index 0000000000..ca670496a5 --- /dev/null +++ b/libraries/shared/src/RunningMarker.h @@ -0,0 +1,35 @@ +// +// RunningMarker.h +// interface/src +// +// Created by Brad Hefta-Gaub on 2016-10-15. +// 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 +// + +#ifndef hifi_RunningMarker_h +#define hifi_RunningMarker_h + +#include +#include + +class RunningMarker { +public: + RunningMarker(QObject* parent, QString name); + ~RunningMarker(); + + void startRunningMarker(); + + QString getFilePath(); + static QString getMarkerFilePath(QString name); +protected: + void writeRunningMarkerFiler(); + void deleteRunningMarkerFile(); + + QObject* _parent { nullptr }; + QString _name; +}; + +#endif // hifi_RunningMarker_h diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 8cae137f67..ca7d3f7c17 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -616,6 +616,28 @@ QString OffscreenUi::fileSaveDialog(const QString& caption, const QString& dir, return fileDialog(map); } +QString OffscreenUi::existingDirectoryDialog(const QString& caption, const QString& dir, const QString& filter, QString* selectedFilter, QFileDialog::Options options) { + if (QThread::currentThread() != thread()) { + QString result; + QMetaObject::invokeMethod(this, "existingDirectoryDialog", Qt::BlockingQueuedConnection, + Q_RETURN_ARG(QString, result), + Q_ARG(QString, caption), + Q_ARG(QString, dir), + Q_ARG(QString, filter), + Q_ARG(QString*, selectedFilter), + Q_ARG(QFileDialog::Options, options)); + return result; + } + + QVariantMap map; + map.insert("caption", caption); + map.insert("dir", QUrl::fromLocalFile(dir)); + map.insert("filter", filter); + map.insert("options", static_cast(options)); + map.insert("selectDirectory", true); + return fileDialog(map); +} + QString OffscreenUi::getOpenFileName(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) { return DependencyManager::get()->fileOpenDialog(caption, dir, filter, selectedFilter, options); } @@ -624,6 +646,10 @@ QString OffscreenUi::getSaveFileName(void* ignored, const QString &caption, cons return DependencyManager::get()->fileSaveDialog(caption, dir, filter, selectedFilter, options); } +QString OffscreenUi::getExistingDirectory(void* ignored, const QString &caption, const QString &dir, const QString &filter, QString *selectedFilter, QFileDialog::Options options) { + return DependencyManager::get()->existingDirectoryDialog(caption, dir, filter, selectedFilter, options); +} + bool OffscreenUi::eventFilter(QObject* originalDestination, QEvent* event) { if (!filterEnabled(originalDestination, event)) { return false; diff --git a/libraries/ui/src/OffscreenUi.h b/libraries/ui/src/OffscreenUi.h index bc0a6bf544..2e6e853336 100644 --- a/libraries/ui/src/OffscreenUi.h +++ b/libraries/ui/src/OffscreenUi.h @@ -115,11 +115,14 @@ public: Q_INVOKABLE QString fileOpenDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); Q_INVOKABLE QString fileSaveDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); + Q_INVOKABLE QString existingDirectoryDialog(const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); // Compatibility with QFileDialog::getOpenFileName static QString getOpenFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); // Compatibility with QFileDialog::getSaveFileName static QString getSaveFileName(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); + // Compatibility with QFileDialog::getExistingDirectory + static QString getExistingDirectory(void* ignored, const QString &caption = QString(), const QString &dir = QString(), const QString &filter = QString(), QString *selectedFilter = 0, QFileDialog::Options options = 0); Q_INVOKABLE QVariant inputDialog(const Icon icon, const QString& title, const QString& label = QString(), const QVariant& current = QVariant()); Q_INVOKABLE QVariant customInputDialog(const Icon icon, const QString& title, const QVariantMap& config); diff --git a/plugins/oculus/src/OculusDisplayPlugin.cpp b/plugins/oculus/src/OculusDisplayPlugin.cpp index b5f4d79042..415965e948 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.cpp +++ b/plugins/oculus/src/OculusDisplayPlugin.cpp @@ -59,13 +59,15 @@ void OculusDisplayPlugin::customizeContext() { ovrResult result = ovr_CreateTextureSwapChainGL(_session, &desc, &_textureSwapChain); if (!OVR_SUCCESS(result)) { - logFatal("Failed to create swap textures"); + logCritical("Failed to create swap textures"); + return; } int length = 0; result = ovr_GetTextureSwapChainLength(_session, _textureSwapChain, &length); if (!OVR_SUCCESS(result) || !length) { - qFatal("Unable to count swap chain textures"); + logCritical("Unable to count swap chain textures"); + return; } for (int i = 0; i < length; ++i) { GLuint chainTexId; @@ -83,6 +85,7 @@ void OculusDisplayPlugin::customizeContext() { _sceneLayer.ColorTexture[0] = _textureSwapChain; // not needed since the structure was zeroed on init, but explicit _sceneLayer.ColorTexture[1] = nullptr; + _customized = true; } void OculusDisplayPlugin::uncustomizeContext() { @@ -98,10 +101,14 @@ void OculusDisplayPlugin::uncustomizeContext() { ovr_DestroyTextureSwapChain(_session, _textureSwapChain); _textureSwapChain = nullptr; _outputFramebuffer.reset(); + _customized = false; Parent::uncustomizeContext(); } void OculusDisplayPlugin::hmdPresent() { + if (!_customized) { + return; + } PROFILE_RANGE_EX(__FUNCTION__, 0xff00ff00, (uint64_t)_currentFrame->frameIndex) diff --git a/plugins/oculus/src/OculusDisplayPlugin.h b/plugins/oculus/src/OculusDisplayPlugin.h index 4d10fd38a5..0c7d57c4f4 100644 --- a/plugins/oculus/src/OculusDisplayPlugin.h +++ b/plugins/oculus/src/OculusDisplayPlugin.h @@ -32,5 +32,6 @@ private: static const QString NAME; ovrTextureSwapChain _textureSwapChain; gpu::FramebufferPointer _outputFramebuffer; + bool _customized { false }; }; diff --git a/plugins/oculus/src/OculusHelpers.cpp b/plugins/oculus/src/OculusHelpers.cpp index 80390fd538..5fbc0db7d1 100644 --- a/plugins/oculus/src/OculusHelpers.cpp +++ b/plugins/oculus/src/OculusHelpers.cpp @@ -39,12 +39,12 @@ void logWarning(const char* what) { qWarning(oculus) << what << ":" << getError().ErrorString; } -void logFatal(const char* what) { +void logCritical(const char* what) { std::string error("[oculus] "); error += what; error += ": "; error += getError().ErrorString; - qFatal(error.c_str()); + qCritical(error.c_str()); } diff --git a/plugins/oculus/src/OculusHelpers.h b/plugins/oculus/src/OculusHelpers.h index ba0547ae0a..e55dde1034 100644 --- a/plugins/oculus/src/OculusHelpers.h +++ b/plugins/oculus/src/OculusHelpers.h @@ -15,7 +15,7 @@ #include void logWarning(const char* what); -void logFatal(const char* what); +void logCritical(const char* what); bool oculusAvailable(); ovrSession acquireOculusSession(); void releaseOculusSession(); diff --git a/scripts/system/controllers/handControllerGrab.js b/scripts/system/controllers/handControllerGrab.js index c3260e8724..e2a982b308 100644 --- a/scripts/system/controllers/handControllerGrab.js +++ b/scripts/system/controllers/handControllerGrab.js @@ -15,9 +15,9 @@ (function() { // BEGIN LOCAL_SCOPE -Script.include("/~/system/libraries/utils.js"); -Script.include("/~/system/libraries/Xform.js"); -Script.include("/~/system/libraries/controllers.js"); +Script.include("../libraries/utils.js"); +Script.include("../libraries/Xform.js"); +Script.include("../libraries/controllers.js"); // // add lines where the hand ray picking is happening @@ -792,7 +792,7 @@ function MyController(hand) { }; this.setState = function(newState, reason) { - + setGrabCommunications((newState === STATE_DISTANCE_HOLDING) || (newState === STATE_NEAR_GRABBING)); if (WANT_DEBUG || WANT_DEBUG_STATE) { var oldStateName = stateToName(this.state); var newStateName = stateToName(newState); @@ -2770,10 +2770,10 @@ var handleHandMessages = function(channel, message, sender) { Messages.messageReceived.connect(handleHandMessages); -var BASIC_TIMER_INTERVAL = 20; // 20ms = 50hz good enough +var BASIC_TIMER_INTERVAL_MS = 20; // 20ms = 50hz good enough var updateIntervalTimer = Script.setInterval(function(){ - update(); -}, BASIC_TIMER_INTERVAL); + update(BASIC_TIMER_INTERVAL_MS / 1000); +}, BASIC_TIMER_INTERVAL_MS); function cleanup() { Script.clearInterval(updateIntervalTimer); diff --git a/scripts/system/controllers/handControllerPointer.js b/scripts/system/controllers/handControllerPointer.js index b5e38fc38c..1fc0a7a82c 100644 --- a/scripts/system/controllers/handControllerPointer.js +++ b/scripts/system/controllers/handControllerPointer.js @@ -20,7 +20,7 @@ // When partially squeezing over a HUD element, a laser or the reticle is shown where the active hand // controller beam intersects the HUD. -Script.include("/~/system/libraries/controllers.js"); +Script.include("../libraries/controllers.js"); // UTILITIES ------------- // @@ -484,6 +484,9 @@ function update() { if (!activeTrigger.state) { return off(); // No trigger } + if (getGrabCommunications()) { + return off(); + } var hudPoint2d = activeHudPoint2d(activeHand); if (!hudPoint2d) { return off(); diff --git a/scripts/system/libraries/controllers.js b/scripts/system/libraries/controllers.js index c77ce4578c..8b5c1bc2fb 100644 --- a/scripts/system/libraries/controllers.js +++ b/scripts/system/libraries/controllers.js @@ -7,6 +7,14 @@ // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html /* global MyAvatar, Vec3, Controller, Quat */ +var GRAB_COMMUNICATIONS_SETTING = "io.highfidelity.isFarGrabbing"; +setGrabCommunications = function setFarGrabCommunications(on) { + Settings.setValue(GRAB_COMMUNICATIONS_SETTING, on ? "on" : ""); +} +getGrabCommunications = function getFarGrabCommunications() { + return !!Settings.getValue(GRAB_COMMUNICATIONS_SETTING, ""); +} + // var GRAB_POINT_SPHERE_OFFSET = { x: 0, y: 0.2, z: 0 }; // var GRAB_POINT_SPHERE_OFFSET = { x: 0.1, y: 0.175, z: 0.04 }; diff --git a/server-console/src/main.js b/server-console/src/main.js index 2d9320da15..72f4520020 100644 --- a/server-console/src/main.js +++ b/server-console/src/main.js @@ -128,6 +128,12 @@ function shutdown() { } } +function forcedShutdown() { + if (!isShuttingDown) { + shutdownCallback(0); + } +} + function shutdownCallback(idx) { if (idx == 0 && !isShuttingDown) { isShuttingDown = true; @@ -226,6 +232,7 @@ if (shouldQuit) { // Check command line arguments to see how to find binaries var argv = require('yargs').argv; + var pathFinder = require('./modules/path-finder.js'); var interfacePath = null; @@ -774,6 +781,7 @@ function maybeShowSplash() { } } + const trayIconOS = (osType == "Darwin") ? "osx" : "win"; var trayIcons = {}; trayIcons[ProcessGroupStates.STARTED] = "console-tray-" + trayIconOS + ".png"; @@ -842,6 +850,34 @@ function onContentLoaded() { // start the home server homeServer.start(); } + + // If we were launched with the shutdownWatcher option, then we need to watch for the interface app + // shutting down. The interface app will regularly update a running state file which we will check. + // If the file doesn't exist or stops updating for a significant amount of time, we will shut down. + if (argv.shutdownWatcher) { + console.log("Shutdown watcher requested... argv.shutdownWatcher:", argv.shutdownWatcher); + var MAX_TIME_SINCE_EDIT = 5000; // 5 seconds between updates + var firstAttemptToCheck = new Date().getTime(); + var shutdownWatchInterval = setInterval(function(){ + var stats = fs.stat(argv.shutdownWatcher, function(err, stats) { + if (err) { + var sinceFirstCheck = new Date().getTime() - firstAttemptToCheck; + if (sinceFirstCheck > MAX_TIME_SINCE_EDIT) { + console.log("Running state file is missing, assume interface has shutdown... shutting down snadbox."); + forcedShutdown(); + clearTimeout(shutdownWatchInterval); + } + } else { + var sinceEdit = new Date().getTime() - stats.mtime.getTime(); + if (sinceEdit > MAX_TIME_SINCE_EDIT) { + console.log("Running state of interface hasn't updated in MAX time... shutting down."); + forcedShutdown(); + clearTimeout(shutdownWatchInterval); + } + } + }); + }, 1000); + } } diff --git a/unpublishedScripts/DomainContent/CellScience/moveCellsAC.js b/unpublishedScripts/DomainContent/CellScience/moveCellsAC.js index d696d464bf..7b0669c616 100644 --- a/unpublishedScripts/DomainContent/CellScience/moveCellsAC.js +++ b/unpublishedScripts/DomainContent/CellScience/moveCellsAC.js @@ -39,11 +39,13 @@ var THROTTLE = true; var THROTTLE_RATE = 5000; var sinceLastUpdate = 0; +var entitiesToMove = []; //print('cells script') function findCells() { var results = Entities.findEntities(basePosition, 60000); + Script.clearInterval(octreeQueryInterval); // we don't need it any more if (results.length === 0) { // print('no entities found') @@ -55,9 +57,7 @@ function findCells() { // print('name is:: ' + name) if (name === 'Cell') { // print('found a cell!!' + v) - Script.setTimeout(function() { - moveCell(v); - }, Math.random() * THROTTLE_RATE); + entitiesToMove.push(v); } }); } @@ -93,6 +93,7 @@ function update(deltaTime) { Entities.setPacketsPerSecond(6000); print("PPS:" + Entities.getPacketsPerSecond()); initialized = true; + Script.setTimeout(findCells, 20 * 1000); // After 20 seconds of getting entities, look for cells. } return; } @@ -102,7 +103,11 @@ function update(deltaTime) { if (sinceLastUpdate > THROTTLE_RATE) { // print('SHOULD FIND CELLS!!!') sinceLastUpdate = 0; - findCells(); + entitiesToMove.forEach(function (v) { + Script.setTimeout(function() { + moveCell(v); + }, Math.random() * THROTTLE_RATE); // don't move all of them every five seconds, but at random times over interval + }); } else { // print('returning in update ' + sinceLastUpdate) return; @@ -116,4 +121,4 @@ function unload() { } Script.update.connect(update); -Script.scriptEnding.connect(unload); \ No newline at end of file +Script.scriptEnding.connect(unload); diff --git a/unpublishedScripts/DomainContent/CellScience/moveVesiclesAC.js b/unpublishedScripts/DomainContent/CellScience/moveVesiclesAC.js index 4d4ce76d74..114abd4984 100644 --- a/unpublishedScripts/DomainContent/CellScience/moveVesiclesAC.js +++ b/unpublishedScripts/DomainContent/CellScience/moveVesiclesAC.js @@ -39,11 +39,13 @@ var THROTTLE = true; var THROTTLE_RATE = 5000; var sinceLastUpdate = 0; +var entitiesToMove = []; //print('vesicle script') function findVesicles() { var results = Entities.findEntities(basePosition, 60000); + Script.clearInterval(octreeQueryInterval); // we don't need it any more if (results.length === 0) { // print('no entities found'); @@ -54,9 +56,7 @@ function findVesicles() { var name = Entities.getEntityProperties(v, 'name').name; if (name === 'vesicle') { //print('found a vesicle!!' + v) - Script.setTimeout(function() { - moveVesicle(v); - }, Math.random() * THROTTLE_RATE); + entitiesToMove.push(v); } }); } @@ -100,6 +100,7 @@ function update(deltaTime) { Entities.setPacketsPerSecond(6000); print("PPS:" + Entities.getPacketsPerSecond()); initialized = true; + Script.setTimeout(findVesicles, 20 * 1000); // After 20 seconds of getting entities, look for cells. } return; } @@ -108,7 +109,11 @@ function update(deltaTime) { sinceLastUpdate = sinceLastUpdate + deltaTime * 1000; if (sinceLastUpdate > THROTTLE_RATE) { sinceLastUpdate = 0; - findVesicles(); + entitiesToMove.forEach(function (v) { + Script.setTimeout(function() { + moveVesicle(v); + }, Math.random() * THROTTLE_RATE); // don't move all of them every five seconds, but at random times over interval + }); } else { return; } @@ -121,4 +126,4 @@ function unload() { } Script.update.connect(update); -Script.scriptEnding.connect(unload); \ No newline at end of file +Script.scriptEnding.connect(unload);