diff --git a/BUILD.md b/BUILD.md index bd264a74ad..c9bb48d4b7 100644 --- a/BUILD.md +++ b/BUILD.md @@ -106,3 +106,4 @@ The following build options can be used when running CMake #### Devices You can support external input/output devices such as Leap Motion, MIDI, and more by adding each individual SDK in the visible building path. Refer to the readme file available in each device folder in [interface/external/](interface/external) for the detailed explanation of the requirements to use the device. + \ No newline at end of file diff --git a/assignment-client/src/avatars/ScriptableAvatar.h b/assignment-client/src/avatars/ScriptableAvatar.h index 34dc25914f..3ef908bedb 100644 --- a/assignment-client/src/avatars/ScriptableAvatar.h +++ b/assignment-client/src/avatars/ScriptableAvatar.h @@ -74,7 +74,8 @@ * avatar. Read-only. * @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's * size in the virtual world. Read-only. - * @property {boolean} hasPriority - is the avatar in a Hero zone? Read-only. + * @property {boolean} hasPriority - true if the avatar is in a "hero" zone, false if it isn't. + * Read-only. * * @example Create a scriptable avatar. * (function () { @@ -138,6 +139,9 @@ public: /// Returns the index of the joint with the specified name, or -1 if not found/unknown. Q_INVOKABLE virtual int getJointIndex(const QString& name) const override; + /**jsdoc + * @comment Uses the base class's JSDoc. + */ Q_INVOKABLE virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; /**jsdoc diff --git a/domain-server/src/DomainGatekeeper.cpp b/domain-server/src/DomainGatekeeper.cpp index f5705a570b..709c5810e2 100644 --- a/domain-server/src/DomainGatekeeper.cpp +++ b/domain-server/src/DomainGatekeeper.cpp @@ -57,7 +57,7 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointergetSize() == 0) { return; } - + QDataStream packetStream(message->getMessage()); // read a NodeConnectionData object from the packet so we can pass around this data while we're inspecting it @@ -88,11 +88,10 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointersecond); } else if (!STATICALLY_ASSIGNED_NODES.contains(nodeConnection.nodeType)) { - QString username; QByteArray usernameSignature; if (message->getBytesLeftToRead() > 0) { @@ -122,9 +121,13 @@ void DomainGatekeeper::processConnectRequestPacket(QSharedPointersetNodeInterestSet(safeInterestSet); nodeData->setPlaceName(nodeConnection.placeName); - qDebug() << "Allowed connection from node" << uuidStringWithoutCurlyBraces(node->getUUID()) - << "on" << message->getSenderSockAddr() << "with MAC" << nodeConnection.hardwareAddress - << "and machine fingerprint" << nodeConnection.machineFingerprint; + qDebug() << "Allowed connection from node" << uuidStringWithoutCurlyBraces(node->getUUID()) + << "on" << message->getSenderSockAddr() + << "with MAC" << nodeConnection.hardwareAddress + << "and machine fingerprint" << nodeConnection.machineFingerprint + << "user" << username + << "reason" << QString(nodeConnection.connectReason ? "SilentDomainDisconnect" : "Connect") + << "previous connection uptime" << nodeConnection.previousConnectionUpTime/USECS_PER_MSEC << "msec"; // signal that we just connected a node so the DomainServer can get it a list // and broadcast its presence right away @@ -468,7 +471,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect if (node->getPublicSocket() == nodeConnection.publicSockAddr && node->getLocalSocket() == nodeConnection.localSockAddr) { // we have a node that already has these exact sockets // this can occur if a node is failing to connect to the domain - + // remove the old node before adding the new node qDebug() << "Deleting existing connection from same sockaddr: " << node->getUUID(); existingNodeID = node->getUUID(); @@ -842,7 +845,7 @@ void DomainGatekeeper::processICEPingPacket(QSharedPointer mess // before we respond to this ICE ping packet, make sure we have a peer in the list that matches QUuid icePeerID = QUuid::fromRfc4122({ message->getRawMessage(), NUM_BYTES_RFC4122_UUID }); - + if (_icePeers.contains(icePeerID)) { auto pingReplyPacket = limitedNodeList->constructICEPingReplyPacket(*message, limitedNodeList->getSessionUUID()); @@ -882,7 +885,6 @@ void DomainGatekeeper::getGroupMemberships(const QString& username) { QJsonArray groupIDs = QJsonArray::fromStringList(groupIDSet.toList()); json["groups"] = groupIDs; - // if we've already asked, wait for the answer before asking again QString lowerUsername = username.toLower(); if (_inFlightGroupMembershipsRequests.contains(lowerUsername)) { @@ -969,7 +971,7 @@ void DomainGatekeeper::getDomainOwnerFriendsList() { QNetworkAccessManager::GetOperation, callbackParams, QByteArray(), NULL, QVariantMap()); } - + } void DomainGatekeeper::getDomainOwnerFriendsListJSONCallback(QNetworkReply* requestReply) { diff --git a/domain-server/src/DomainServer.cpp b/domain-server/src/DomainServer.cpp index e71ea0971c..44887599d3 100644 --- a/domain-server/src/DomainServer.cpp +++ b/domain-server/src/DomainServer.cpp @@ -1079,7 +1079,7 @@ void DomainServer::processListRequestPacket(QSharedPointer mess // client-side send time of last connect/domain list request nodeData->setLastDomainCheckinTimestamp(nodeRequestData.lastPingTimestamp); - sendDomainListToNode(sendingNode, message->getFirstPacketReceiveTime(), message->getSenderSockAddr()); + sendDomainListToNode(sendingNode, message->getFirstPacketReceiveTime(), message->getSenderSockAddr(), false); } bool DomainServer::isInInterestSet(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB) { @@ -1145,7 +1145,7 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode, quint64 reques DomainServerNodeData* nodeData = static_cast(newNode->getLinkedData()); // reply back to the user with a PacketType::DomainList - sendDomainListToNode(newNode, requestReceiveTime, nodeData->getSendingSockAddr()); + sendDomainListToNode(newNode, requestReceiveTime, nodeData->getSendingSockAddr(), true); // if this node is a user (unassigned Agent), signal if (newNode->getType() == NodeType::Agent && !nodeData->wasAssigned()) { @@ -1161,7 +1161,7 @@ void DomainServer::handleConnectedNode(SharedNodePointer newNode, quint64 reques broadcastNewNode(newNode); } -void DomainServer::sendDomainListToNode(const SharedNodePointer& node, quint64 requestPacketReceiveTime, const HifiSockAddr &senderSockAddr) { +void DomainServer::sendDomainListToNode(const SharedNodePointer& node, quint64 requestPacketReceiveTime, const HifiSockAddr &senderSockAddr, bool newConnection) { const int NUM_DOMAIN_LIST_EXTENDED_HEADER_BYTES = NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID + NUM_BYTES_RFC4122_UUID + NLPacket::NUM_BYTES_LOCALID + 4; @@ -1181,6 +1181,7 @@ void DomainServer::sendDomainListToNode(const SharedNodePointer& node, quint64 r extendedHeaderStream << nodeData->getLastDomainCheckinTimestamp(); extendedHeaderStream << quint64(duration_cast(system_clock::now().time_since_epoch()).count()); extendedHeaderStream << quint64(duration_cast(p_high_resolution_clock::now().time_since_epoch()).count()) - requestPacketReceiveTime; + extendedHeaderStream << newConnection; auto domainListPackets = NLPacketList::create(PacketType::DomainList, extendedHeader); // always send the node their own UUID back diff --git a/domain-server/src/DomainServer.h b/domain-server/src/DomainServer.h index 36ebe5a234..54b7fbe466 100644 --- a/domain-server/src/DomainServer.h +++ b/domain-server/src/DomainServer.h @@ -173,7 +173,7 @@ private: void handleKillNode(SharedNodePointer nodeToKill); void broadcastNodeDisconnect(const SharedNodePointer& disconnnectedNode); - void sendDomainListToNode(const SharedNodePointer& node, quint64 requestPacketReceiveTime, const HifiSockAddr& senderSockAddr); + void sendDomainListToNode(const SharedNodePointer& node, quint64 requestPacketReceiveTime, const HifiSockAddr& senderSockAddr, bool newConnection); bool isInInterestSet(const SharedNodePointer& nodeA, const SharedNodePointer& nodeB); diff --git a/domain-server/src/NodeConnectionData.cpp b/domain-server/src/NodeConnectionData.cpp index b3ea005bd1..b4aaacd749 100644 --- a/domain-server/src/NodeConnectionData.cpp +++ b/domain-server/src/NodeConnectionData.cpp @@ -35,6 +35,10 @@ NodeConnectionData NodeConnectionData::fromDataStream(QDataStream& dataStream, c // now the machine fingerprint dataStream >> newHeader.machineFingerprint; + + dataStream >> newHeader.connectReason; + + dataStream >> newHeader.previousConnectionUpTime; } dataStream >> newHeader.lastPingTimestamp; diff --git a/domain-server/src/NodeConnectionData.h b/domain-server/src/NodeConnectionData.h index 43661f9caf..23eceb0dca 100644 --- a/domain-server/src/NodeConnectionData.h +++ b/domain-server/src/NodeConnectionData.h @@ -31,6 +31,8 @@ public: QString placeName; QString hardwareAddress; QUuid machineFingerprint; + quint32 connectReason; + quint64 previousConnectionUpTime; QByteArray protocolVersion; }; diff --git a/interface/resources/avatar/animations/jog_fwd.fbx b/interface/resources/avatar/animations/jog_fwd.fbx index 3c3b8118ab..f389fea364 100644 Binary files a/interface/resources/avatar/animations/jog_fwd.fbx and b/interface/resources/avatar/animations/jog_fwd.fbx differ diff --git a/interface/resources/avatar/animations/run_fwd.fbx b/interface/resources/avatar/animations/run_fwd.fbx index 86add969e5..f28f9e0569 100644 Binary files a/interface/resources/avatar/animations/run_fwd.fbx and b/interface/resources/avatar/animations/run_fwd.fbx differ diff --git a/interface/resources/avatar/avatar-animation.json b/interface/resources/avatar/avatar-animation.json index 738975bb8c..fd206ce475 100644 --- a/interface/resources/avatar/avatar-animation.json +++ b/interface/resources/avatar/avatar-animation.json @@ -1463,7 +1463,7 @@ "data": { "alpha": 0.0, "desiredSpeed": 1.4, - "characteristicSpeeds": [0.5, 1.8, 2.3, 3.2, 4.5], + "characteristicSpeeds": [0.5, 1.8, 2.3, 3.0, 5.0], "alphaVar": "moveForwardAlpha", "desiredSpeedVar": "moveForwardSpeed" }, @@ -1509,8 +1509,8 @@ "type": "clip", "data": { "url": "qrc:///avatar/animations/jog_fwd.fbx", - "startFrame": 0.0, - "endFrame": 25.0, + "startFrame": 1.0, + "endFrame": 22.0, "timeScale": 1.0, "loopFlag": true }, @@ -1522,7 +1522,7 @@ "data": { "url": "qrc:///avatar/animations/run_fwd.fbx", "startFrame": 1.0, - "endFrame": 22.0, + "endFrame": 23.0, "timeScale": 1.0, "loopFlag": true }, @@ -2099,4 +2099,4 @@ } ] } -} \ No newline at end of file +} diff --git a/interface/resources/qml/+android_interface/Stats.qml b/interface/resources/qml/+android_interface/Stats.qml index 54f6086a86..1f07af786f 100644 --- a/interface/resources/qml/+android_interface/Stats.qml +++ b/interface/resources/qml/+android_interface/Stats.qml @@ -269,6 +269,9 @@ Item { StatText { text: "GPU: " + root.gpuFrameTime.toFixed(1) + " ms" } + StatText { + text: "LOD Target: " + root.lodTargetFramerate + " Hz Angle: " + root.lodAngle + " deg" + } StatText { text: "Drawcalls: " + root.drawcalls } diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml index 2d5c68c0e8..e3cd492edb 100644 --- a/interface/resources/qml/LoginDialog.qml +++ b/interface/resources/qml/LoginDialog.qml @@ -13,15 +13,14 @@ import QtQuick 2.4 import controlsUit 1.0 as HifiControlsUit import stylesUit 1.0 as HifiStylesUit -import "LoginDialog" - FocusScope { id: root - HifiStylesUit.HifiConstants { id: hifi } objectName: "LoginDialog" property bool shown: true visible: shown + HifiStylesUit.HifiConstants { id: hifi } + anchors.fill: parent readonly property bool isTablet: false @@ -33,12 +32,17 @@ FocusScope { property bool keyboardRaised: false property bool punctuationMode: false property bool isPassword: false - property string title: "" - property string text: "" - property int titleWidth: 0 + property alias bannerWidth: banner.width property alias bannerHeight: banner.height + property string title: "" + property string text: "" + + property int titleWidth: 0 + + property bool isHMD: HMD.active + function tryDestroy() { root.destroy() } diff --git a/interface/resources/qml/Stats.qml b/interface/resources/qml/Stats.qml index 5af3ba9168..2c991aa9dd 100644 --- a/interface/resources/qml/Stats.qml +++ b/interface/resources/qml/Stats.qml @@ -259,6 +259,35 @@ Item { visible: root.expanded; text: "Entity Servers In: " + root.entityPacketsInKbps + " kbps"; } + StatText { + visible: !root.expanded + text: "Octree Elements Server: " + root.serverElements + + " Local: " + root.localElements; + } + StatText { + visible: root.expanded + text: "Octree Sending Mode: " + root.sendingMode; + } + StatText { + visible: root.expanded + text: "Octree Packets to Process: " + root.packetStats; + } + StatText { + visible: root.expanded + text: "Octree Elements - "; + } + StatText { + visible: root.expanded + text: "\tServer: " + root.serverElements + + " Internal: " + root.serverInternal + + " Leaves: " + root.serverLeaves; + } + StatText { + visible: root.expanded + text: "\tLocal: " + root.localElements + + " Internal: " + root.localInternal + + " Leaves: " + root.localLeaves; + } StatText { visible: root.expanded; text: "Downloads: " + root.downloads + "/" + root.downloadLimit + @@ -316,6 +345,9 @@ Item { StatText { text: "GPU frame size: " + root.gpuFrameSize.x + " x " + root.gpuFrameSize.y } + StatText { + text: "LOD Target: " + root.lodTargetFramerate + " Hz Angle: " + root.lodAngle + " deg" + } StatText { text: "Drawcalls: " + root.drawcalls } @@ -401,35 +433,6 @@ Item { text: " out of view: " + root.shadowOutOfView + " too small: " + root.shadowTooSmall; } - StatText { - visible: !root.expanded - text: "Octree Elements Server: " + root.serverElements + - " Local: " + root.localElements; - } - StatText { - visible: root.expanded - text: "Octree Sending Mode: " + root.sendingMode; - } - StatText { - visible: root.expanded - text: "Octree Packets to Process: " + root.packetStats; - } - StatText { - visible: root.expanded - text: "Octree Elements - "; - } - StatText { - visible: root.expanded - text: "\tServer: " + root.serverElements + - " Internal: " + root.serverInternal + - " Leaves: " + root.serverLeaves; - } - StatText { - visible: root.expanded - text: "\tLocal: " + root.localElements + - " Internal: " + root.localInternal + - " Leaves: " + root.localLeaves; - } StatText { visible: root.expanded text: "LOD: " + root.lodStatus; diff --git a/interface/resources/qml/dialogs/TabletLoginDialog.qml b/interface/resources/qml/dialogs/TabletLoginDialog.qml index 8d6444bc0e..b01bb5b761 100644 --- a/interface/resources/qml/dialogs/TabletLoginDialog.qml +++ b/interface/resources/qml/dialogs/TabletLoginDialog.qml @@ -23,43 +23,36 @@ FocusScope { objectName: "LoginDialog" visible: true + HifiStylesUit.HifiConstants { id: hifi } + anchors.fill: parent - width: parent.width - height: parent.height - property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system"); - - property bool isHMD: HMD.active - property bool gotoPreviousApp: false; + readonly property bool isTablet: true + readonly property bool isOverlay: false + property string iconText: hifi.glyphs.avatar + property int iconSize: 35 property bool keyboardEnabled: false property bool keyboardRaised: false property bool punctuationMode: false property bool isPassword: false - readonly property bool isTablet: true - readonly property bool isOverlay: false - property alias text: loginKeyboard.mirroredText - - property int titleWidth: 0 property alias bannerWidth: banner.width property alias bannerHeight: banner.height - property string iconText: hifi.glyphs.avatar - property int iconSize: 35 - property var pane: QtObject { - property real width: root.width - property real height: root.height - } + property int titleWidth: 0 - function tryDestroy() { - tabletProxy.gotoHomeScreen(); - } + property bool isHMD: HMD.active - MouseArea { - width: root.width - height: root.height - } + // TABLET SPECIFIC PROPERTIES START // + property alias text: loginKeyboard.mirroredText + + width: parent.width + height: parent.height + + property var tabletProxy: Tablet.getTablet("com.highfidelity.interface.tablet.system") + + property bool gotoPreviousApp: false property bool keyboardOverride: true @@ -70,7 +63,20 @@ FocusScope { property alias loginDialog: loginDialog property alias hifi: hifi - HifiStylesUit.HifiConstants { id: hifi } + property var pane: QtObject { + property real width: root.width + property real height: root.height + } + + MouseArea { + width: root.width + height: root.height + } + // TABLET SPECIFIC PROPERTIES END // + + function tryDestroy() { + tabletProxy.gotoHomeScreen(); + } Timer { id: keyboardTimer @@ -102,6 +108,15 @@ FocusScope { anchors.fill: parent } + Rectangle { + z: -6 + id: opaqueRect + height: parent.height + width: parent.width + opacity: 0.65 + color: "black" + } + Item { z: -5 id: bannerContainer @@ -119,15 +134,6 @@ FocusScope { } } - Rectangle { - z: -6 - id: opaqueRect - height: parent.height - width: parent.width - opacity: 0.65 - color: "black" - } - HifiControlsUit.Keyboard { id: loginKeyboard raised: root.keyboardEnabled && root.keyboardRaised diff --git a/interface/resources/qml/hifi/Desktop.qml b/interface/resources/qml/hifi/Desktop.qml index 5fa6234504..3239471a00 100644 --- a/interface/resources/qml/hifi/Desktop.qml +++ b/interface/resources/qml/hifi/Desktop.qml @@ -12,6 +12,8 @@ import controlsUit 1.0 OriginalDesktop.Desktop { id: desktop + property alias toolbarObjectName: sysToolbar.objectName + MouseArea { id: hoverWatch anchors.fill: parent @@ -70,7 +72,12 @@ OriginalDesktop.Desktop { x: sysToolbar.x buttonModel: tablet ? tablet.buttons : null; shown: tablet ? tablet.toolbarMode : false; + + onVisibleChanged: { + desktop.toolbarVisibleChanged(visible, sysToolbar.objectName); + } } + signal toolbarVisibleChanged(bool isVisible, string toolbarName); QtSettings.Settings { id: settings; diff --git a/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml b/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml index 4c6615ebdc..ef9a3cbe24 100644 --- a/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml +++ b/interface/resources/qml/hifi/simplifiedUI/avatarApp/AvatarApp.qml @@ -88,7 +88,7 @@ Rectangle { Image { id: accent - source: "../images/accent.svg" + source: "images/accent.svg" anchors.top: parent.top anchors.right: parent.right width: 60 @@ -122,7 +122,7 @@ Rectangle { Tablet.playSound(TabletEnums.ButtonClick); // Can't use `Window.location` in QML, so just use what setting `Window.location` actually calls under the hood: // AddressManager.handleLookupString(). - AddressManager.handleLookupString(LocationBookmarks.getHomeLocationAddress()); + AddressManager.handleLookupString(LocationBookmarks.getAddress("hqhome")); } } } diff --git a/interface/resources/qml/hifi/simplifiedUI/images/accent.svg b/interface/resources/qml/hifi/simplifiedUI/avatarApp/images/accent.svg similarity index 100% rename from interface/resources/qml/hifi/simplifiedUI/images/accent.svg rename to interface/resources/qml/hifi/simplifiedUI/avatarApp/images/accent.svg diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml index ec4f1b3bd5..60703a8062 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/SettingsApp.qml @@ -14,16 +14,41 @@ import stylesUit 1.0 as HifiStylesUit import "./audio" as AudioSettings import "./general" as GeneralSettings import "./vr" as VrSettings +import "./dev" as DevSettings Rectangle { property string activeTabView: "generalTabView" id: root color: simplifiedUI.colors.darkBackground anchors.fill: parent + property bool developerModeEnabled: Settings.getValue("simplifiedUI/developerModeEnabled", false) SimplifiedConstants.SimplifiedConstants { id: simplifiedUI } + + focus: true + Keys.onPressed: { + if ((event.key == Qt.Key_D) && (event.modifiers & Qt.ControlModifier && event.modifiers & Qt.AltModifier && event.modifiers & Qt.ShiftModifier)) { + var currentSetting = Settings.getValue("simplifiedUI/developerModeEnabled", false); + var newSetting = !currentSetting; + Settings.setValue("simplifiedUI/developerModeEnabled", newSetting); + root.developerModeEnabled = newSetting; + if (newSetting) { + console.log("Developer mode ON. You are now a developer!"); + } else { + console.log("Developer mode OFF."); + if (root.activeTabView === "devTabView") { + tabListView.currentIndex = 2; + root.activeTabView = "vrTabView"; + } + } + } + } + + Component.onCompleted: { + root.forceActiveFocus(); + } Rectangle { @@ -49,6 +74,10 @@ Rectangle { tabTitle: "VR" tabViewName: "vrTabView" } + ListElement { + tabTitle: "Dev" + tabViewName: "devTabView" + } } @@ -70,6 +99,8 @@ Rectangle { highlight: highlightBar interactive: contentItem.width > width delegate: Item { + visible: model.tabTitle !== "Dev" || (model.tabTitle === "Dev" && root.developerModeEnabled) + width: tabTitleText.paintedWidth + 64 height: parent.height @@ -125,14 +156,30 @@ Rectangle { visible: activeTabView === "vrTabView" anchors.fill: parent } + + DevSettings.Dev { + id: devTabViewContainer + visible: activeTabView === "devTabView" + anchors.fill: parent + } } Image { - source: "../images/accent.svg" + source: { + if (root.activeTabView === "generalTabView") { + "images/accent1.svg" + } else if (root.activeTabView === "audioTabView") { + "images/accent2.svg" + } else if (root.activeTabView === "vrTabView") { + "images/accent3.svg" + } else { + "images/accent3.svg" + } + } anchors.right: parent.right - anchors.bottom: parent.bottom - width: 94 - height: 175 + anchors.top: tabContainer.bottom + width: 106 + height: 200 } diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml index 7a19ab1a0f..4dc5e973fc 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/audio/Audio.qml @@ -57,7 +57,7 @@ Flickable { Layout.preferredWidth: parent.width spacing: 0 - HifiStylesUit.GraphikRegular { + HifiStylesUit.GraphikSemiBold { id: volumeControlsTitle text: "Volume Controls" Layout.preferredWidth: parent.width @@ -154,7 +154,7 @@ Flickable { Layout.preferredWidth: parent.width spacing: 0 - HifiStylesUit.GraphikRegular { + HifiStylesUit.GraphikSemiBold { id: micControlsTitle text: "Default Mute Controls" Layout.maximumWidth: parent.width @@ -196,7 +196,7 @@ Flickable { Layout.preferredWidth: parent.width spacing: 0 - HifiStylesUit.GraphikRegular { + HifiStylesUit.GraphikSemiBold { id: inputDeviceTitle text: "Which input device?" Layout.maximumWidth: parent.width @@ -291,7 +291,7 @@ Flickable { Layout.preferredWidth: parent.width spacing: 0 - HifiStylesUit.GraphikRegular { + HifiStylesUit.GraphikSemiBold { id: outputDeviceTitle text: "Which output device?" Layout.maximumWidth: parent.width diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/dev/Dev.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/dev/Dev.qml new file mode 100644 index 0000000000..a8c0a8f158 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/dev/Dev.qml @@ -0,0 +1,97 @@ +// +// Dev.qml +// +// Created by Zach Fox on 2019-06-11 +// Copyright 2019 High Fidelity, Inc. +// +// Distributed under the Apache License, Version 2.0. +// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html +// + +import QtQuick 2.10 +import QtQuick.Controls 2.3 +import "../../simplifiedConstants" as SimplifiedConstants +import "../../simplifiedControls" as SimplifiedControls +import stylesUit 1.0 as HifiStylesUit +import QtQuick.Layouts 1.3 + +Flickable { + id: root + contentWidth: parent.width + contentHeight: devColumnLayout.height + topMargin: 24 + bottomMargin: 24 + clip: true + + onVisibleChanged: { + if (visible) { + root.contentX = 0; + root.contentY = -root.topMargin; + } + } + + + SimplifiedConstants.SimplifiedConstants { + id: simplifiedUI + } + + ColumnLayout { + id: devColumnLayout + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + spacing: simplifiedUI.margins.settings.spacingBetweenSettings + + ColumnLayout { + id: uiControlsContainer + Layout.preferredWidth: parent.width + spacing: 0 + + HifiStylesUit.GraphikSemiBold { + id: uiControlsTitle + text: "User Interface" + Layout.maximumWidth: parent.width + height: paintedHeight + size: 22 + color: simplifiedUI.colors.text.white + } + + HifiStylesUit.GraphikRegular { + id: uiControlsSubtitle + text: "You'll have to restart Interface after changing either of these settings. If you don't get any Toolbar apps back after restarting, run defaultScripts.js manually." + Layout.maximumWidth: parent.width + height: paintedHeight + size: 16 + color: simplifiedUI.colors.text.white + wrapMode: Text.Wrap + } + + ColumnLayout { + id: uiControlsSwitchGroup + Layout.topMargin: simplifiedUI.margins.settings.settingsGroupTopMargin + + SimplifiedControls.Switch { + id: keepMenusSwitch + width: parent.width + height: 18 + labelTextOn: "Keep Old Menus (File, Edit, etc)" + checked: Settings.getValue("simplifiedUI/keepMenus", false); + onClicked: { + Settings.setValue("simplifiedUI/keepMenus", !Settings.getValue("simplifiedUI/keepMenus", false)); + } + } + + SimplifiedControls.Switch { + id: keepOldUIAndScriptsSwitch + width: parent.width + height: 18 + labelTextOn: "Keep Old UI and Scripts" + checked: Settings.getValue("simplifiedUI/keepExistingUIAndScripts", false); + onClicked: { + Settings.setValue("simplifiedUI/keepExistingUIAndScripts", !Settings.getValue("simplifiedUI/keepExistingUIAndScripts", false)); + } + } + } + } + } +} diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml index 9c3a33412e..f56d0f33bd 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/general/General.qml @@ -51,7 +51,7 @@ Flickable { Layout.preferredWidth: parent.width spacing: 0 - HifiStylesUit.GraphikRegular { + HifiStylesUit.GraphikSemiBold { id: avatarNameTagsTitle text: "Avatar Name Tags" Layout.maximumWidth: parent.width @@ -99,9 +99,9 @@ Flickable { Layout.preferredWidth: parent.width spacing: 0 - HifiStylesUit.GraphikRegular { + HifiStylesUit.GraphikSemiBold { id: performanceTitle - text: "Graphics Preset" + text: "Graphics Settings" Layout.maximumWidth: parent.width height: paintedHeight size: 22 @@ -115,7 +115,7 @@ Flickable { SimplifiedControls.RadioButton { id: performanceLow - text: "Low" + text: "Low Quality" + (PlatformInfo.getTierProfiled() === PerformanceEnums.LOW ? " (Recommended)" : "") checked: Performance.getPerformancePreset() === PerformanceEnums.LOW onClicked: { Performance.setPerformancePreset(PerformanceEnums.LOW); @@ -124,7 +124,7 @@ Flickable { SimplifiedControls.RadioButton { id: performanceMedium - text: "Medium" + text: "Medium Quality" + (PlatformInfo.getTierProfiled() === PerformanceEnums.MID ? " (Recommended)" : "") checked: Performance.getPerformancePreset() === PerformanceEnums.MID onClicked: { Performance.setPerformancePreset(PerformanceEnums.MID); @@ -133,7 +133,7 @@ Flickable { SimplifiedControls.RadioButton { id: performanceHigh - text: "High" + text: "High Quality" + (PlatformInfo.getTierProfiled() === PerformanceEnums.HIGH ? " (Recommended)" : "") checked: Performance.getPerformancePreset() === PerformanceEnums.HIGH onClicked: { Performance.setPerformancePreset(PerformanceEnums.HIGH); @@ -147,7 +147,7 @@ Flickable { Layout.preferredWidth: parent.width spacing: 0 - HifiStylesUit.GraphikRegular { + HifiStylesUit.GraphikSemiBold { id: cameraTitle text: "Camera View" Layout.maximumWidth: parent.width @@ -199,7 +199,7 @@ Flickable { wrapMode: Text.Wrap width: paintedWidth height: paintedHeight - size: 22 + size: 14 color: simplifiedUI.colors.text.lightBlue MouseArea { diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/images/accent1.svg b/interface/resources/qml/hifi/simplifiedUI/settingsApp/images/accent1.svg new file mode 100644 index 0000000000..885edef5ac --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/images/accent1.svg @@ -0,0 +1,4 @@ + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/images/accent2.svg b/interface/resources/qml/hifi/simplifiedUI/settingsApp/images/accent2.svg new file mode 100644 index 0000000000..027d9bb623 --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/images/accent2.svg @@ -0,0 +1,4 @@ + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/images/accent3.svg b/interface/resources/qml/hifi/simplifiedUI/settingsApp/images/accent3.svg new file mode 100644 index 0000000000..07cc23ef1e --- /dev/null +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/images/accent3.svg @@ -0,0 +1,4 @@ + + + + diff --git a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml index c7e3cc9fc2..9f5ed3ff8f 100644 --- a/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml +++ b/interface/resources/qml/hifi/simplifiedUI/settingsApp/vr/VR.qml @@ -57,7 +57,7 @@ Flickable { Layout.preferredWidth: parent.width spacing: 0 - HifiStylesUit.GraphikRegular { + HifiStylesUit.GraphikSemiBold { id: controlsTitle text: "VR Movement Controls" Layout.maximumWidth: parent.width @@ -143,7 +143,7 @@ Flickable { Layout.preferredWidth: parent.width spacing: 0 - HifiStylesUit.GraphikRegular { + HifiStylesUit.GraphikSemiBold { id: micControlsTitle text: "Default Mute Controls" Layout.maximumWidth: parent.width @@ -185,7 +185,7 @@ Flickable { Layout.preferredWidth: parent.width spacing: 0 - HifiStylesUit.GraphikRegular { + HifiStylesUit.GraphikSemiBold { id: inputDeviceTitle text: "Which input device?" Layout.maximumWidth: parent.width @@ -280,7 +280,7 @@ Flickable { Layout.preferredWidth: parent.width spacing: 0 - HifiStylesUit.GraphikRegular { + HifiStylesUit.GraphikSemiBold { id: outputDeviceTitle text: "Which output device?" Layout.maximumWidth: parent.width diff --git a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml index 1f628b041d..5aa94d798e 100644 --- a/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml +++ b/interface/resources/qml/hifi/simplifiedUI/simplifiedConstants/SimplifiedConstants.qml @@ -183,7 +183,7 @@ QtObject { readonly property QtObject settings: QtObject { property int subtitleTopMargin: 2 - property int settingsGroupTopMargin: 24 + property int settingsGroupTopMargin: 14 property int spacingBetweenSettings: 48 property int spacingBetweenRadiobuttons: 14 } diff --git a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml index 36c882beb1..42d53d3f79 100644 --- a/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml +++ b/interface/resources/qml/hifi/simplifiedUI/topBar/SimplifiedTopBar.qml @@ -18,6 +18,16 @@ import "qrc:////qml//hifi//models" as HifiModels // Absolute path so the same c Rectangle { id: root + focus: true + + signal keyPressEvent(int key, int modifiers) + Keys.onPressed: { + keyPressEvent(event.key, event.modifiers); + } + signal keyReleaseEvent(int key, int modifiers) + Keys.onReleased: { + keyReleaseEvent(event.key, event.modifiers); + } SimplifiedConstants.SimplifiedConstants { id: simplifiedUI @@ -37,6 +47,12 @@ Rectangle { onSkeletonModelURLChanged: { root.updatePreviewUrl(); + + if ((MyAvatar.skeletonModelURL.indexOf("defaultAvatar") > -1 || MyAvatar.skeletonModelURL.indexOf("fst") === -1) && + topBarInventoryModel.count > 0) { + Settings.setValue("simplifiedUI/alreadyAutoSelectedAvatar", true); + MyAvatar.skeletonModelURL = topBarInventoryModel.get(0).download_url; + } } } @@ -83,6 +99,13 @@ Rectangle { topBarInventoryModel.getNextPage(); } else { inventoryFullyReceived = true; + + // If we have an avatar in our inventory AND we haven't already auto-selected an avatar... + if ((!Settings.getValue("simplifiedUI/alreadyAutoSelectedAvatar", false) || + MyAvatar.skeletonModelURL.indexOf("defaultAvatar") > -1 || MyAvatar.skeletonModelURL.indexOf("fst") === -1) && topBarInventoryModel.count > 0) { + Settings.setValue("simplifiedUI/alreadyAutoSelectedAvatar", true); + MyAvatar.skeletonModelURL = topBarInventoryModel.get(0).download_url; + } } } } @@ -455,5 +478,5 @@ Rectangle { break; } } - signal sendToScript(var message); + signal sendToScript(var message) } diff --git a/interface/resources/qml/hifi/tablet/Edit.qml b/interface/resources/qml/hifi/tablet/Edit.qml index 099c53cda2..ca18388def 100644 --- a/interface/resources/qml/hifi/tablet/Edit.qml +++ b/interface/resources/qml/hifi/tablet/Edit.qml @@ -34,7 +34,8 @@ StackView { } function pushSource(path) { - editRoot.push(Qt.resolvedUrl("../../" + path), itemProperties, + var item = Qt.createComponent(Qt.resolvedUrl("../../" + path)); + editRoot.push(item, itemProperties, StackView.Immediate); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); } diff --git a/interface/resources/qml/hifi/tablet/EditTools.qml b/interface/resources/qml/hifi/tablet/EditTools.qml index f989038c16..976e98cd50 100644 --- a/interface/resources/qml/hifi/tablet/EditTools.qml +++ b/interface/resources/qml/hifi/tablet/EditTools.qml @@ -37,7 +37,8 @@ StackView { } function pushSource(path) { - editRoot.push(Qt.resolvedUrl("../../" + path), itemProperties, + var item = Qt.createComponent(Qt.resolvedUrl("../../" + path)); + editRoot.push(item, itemProperties, StackView.Immediate); editRoot.currentItem.sendToScript.connect(editRoot.sendToScript); } diff --git a/interface/resources/qml/hifi/tablet/TabletAudioBuffers.qml b/interface/resources/qml/hifi/tablet/TabletAudioBuffers.qml index 239c2452d4..2836c2bac6 100644 --- a/interface/resources/qml/hifi/tablet/TabletAudioBuffers.qml +++ b/interface/resources/qml/hifi/tablet/TabletAudioBuffers.qml @@ -24,7 +24,8 @@ StackView { signal sendToScript(var message); function pushSource(path) { - profileRoot.push(Qt.resolvedUrl(path)); + var item = Qt.createComponent(Qt.resolvedUrl(path)); + profileRoot.push(item); } function popSource() { diff --git a/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml b/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml index e824036587..dbe4ab12db 100644 --- a/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletAvatarPreferences.qml @@ -22,7 +22,8 @@ StackView { signal sendToScript(var message); function pushSource(path) { - profileRoot.push(Qt.reslovedUrl(path)); + var item = Qt.createComponent(Qt.resolvedUrl(path)); + profileRoot.push(item); } function popSource() { diff --git a/interface/resources/qml/hifi/tablet/TabletButton.qml b/interface/resources/qml/hifi/tablet/TabletButton.qml index 4d443fb97c..ec24caeef2 100644 --- a/interface/resources/qml/hifi/tablet/TabletButton.qml +++ b/interface/resources/qml/hifi/tablet/TabletButton.qml @@ -5,6 +5,9 @@ import TabletScriptingInterface 1.0 Item { id: tabletButton + // NOTE: These properties form part of the "TabletButtonProxy.ButtonProperties" type. + // Keep the type's JSDoc up to date with any changes. + property color defaultCaptionColor: "#ffffff" property color captionColor: defaultCaptionColor @@ -18,15 +21,15 @@ Item { property string activeText: tabletButton.text property string activeHoverText: tabletButton.activeText property bool isActive: false - property bool inDebugMode: false + property bool inDebugMode: false // tablet only property bool isEntered: false property double sortOrder: 100 property int stableOrder: 0 - property var tabletRoot; - property var flickable: null - property var gridView: null + property var tabletRoot; // tablet only + property var flickable: null // tablet only + property var gridView: null // tablet only - property int buttonIndex: -1 + property int buttonIndex: -1 // tablet only width: 129 height: 129 diff --git a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml index 4f1100f20b..254e4203ce 100644 --- a/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletGeneralPreferences.qml @@ -22,7 +22,8 @@ StackView { signal sendToScript(var message); function pushSource(path) { - profileRoot.push(Qt.resolvedUrl(path)); + var item = Qt.createComponent(Qt.resolvedUrl(path)); + profileRoot.push(item); } function popSource() { diff --git a/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml b/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml index 8d600975ed..9b9f01d031 100644 --- a/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletGraphicsPreferences.qml @@ -22,7 +22,8 @@ StackView { signal sendToScript(var message); function pushSource(path) { - profileRoot.push(Qt.resolvedUrl(path)); + var item = Qt.createComponent(Qt.resolvedUrl(path)); + profileRoot.push(item); } function popSource() { diff --git a/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml b/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml index ddc116371d..0938d0a82e 100644 --- a/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletLodPreferences.qml @@ -22,7 +22,8 @@ StackView { signal sendToScript(var message); function pushSource(path) { - profileRoot.push(Qt.resolvedUrl(path)); + var item = Qt.createComponent(Qt.resolvedUrl(path)); + profileRoot.push(item); } function popSource() { diff --git a/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml b/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml index bad546a39c..67d6e6c52e 100644 --- a/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml +++ b/interface/resources/qml/hifi/tablet/TabletNetworkingPreferences.qml @@ -22,7 +22,8 @@ StackView { signal sendToScript(var message); function pushSource(path) { - profileRoot.push(Qt.resolvedUrl(path)); + var item = Qt.createComponent(Qt.resolvedUrl(path)); + profileRoot.push(item); } function popSource() { diff --git a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml index 232973b4d3..f8fc787a2c 100644 --- a/interface/resources/qml/hifi/toolbars/ToolbarButton.qml +++ b/interface/resources/qml/hifi/toolbars/ToolbarButton.qml @@ -3,6 +3,9 @@ import QtQuick 2.5 StateImage { id: button + // NOTE: These properties form part of the "TabletButtonProxy.ButtonProperties" type. + // Keep the type's JSDoc up to date with any changes. + property color defaultCaptionColor: "#ffffff" property color captionColor: defaultCaptionColor diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index 9ce2b0cb2e..f9470782bf 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -950,6 +950,19 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) { DependencyManager::set(); DependencyManager::set(); + QString setBookmarkValue = getCmdOption(argc, constArgv, "--setBookmark"); + if (!setBookmarkValue.isEmpty()) { + // Bookmarks are expected to be in a name=url form. + // An `=` character in the name or url is unsupported. + auto parts = setBookmarkValue.split("="); + if (parts.length() != 2) { + qWarning() << "Malformed setBookmark argument: " << setBookmarkValue; + } else { + qDebug() << "Setting bookmark" << parts[0] << "to" << parts[1]; + DependencyManager::get()->insert(parts[0], parts[1]); + } + } + return previousSessionCrashed; } @@ -2695,6 +2708,7 @@ void Application::cleanupBeforeQuit() { } getEntities()->shutdown(); // tell the entities system we're shutting down, so it will stop running scripts + getEntities()->clear(); // Clear any queued processing (I/O, FBX/OBJ/Texture parsing) QThreadPool::globalInstance()->clear(); @@ -5611,7 +5625,7 @@ void Application::resumeAfterLoginDialogActionTaken() { scriptEngines->reloadLocalFiles(); // if the --scripts command-line argument was used. - if (!_defaultScriptsLocation.exists() && (arguments().indexOf(QString("--").append(SCRIPTS_SWITCH))) != -1) { + if (_defaultScriptsLocation.exists() && (arguments().indexOf(QString("--").append(SCRIPTS_SWITCH))) != -1) { scriptEngines->loadDefaultScripts(); scriptEngines->defaultScriptsLocationOverridden(true); } else { diff --git a/interface/src/Bookmarks.h b/interface/src/Bookmarks.h index 56d26b55c6..c31aee6e33 100644 --- a/interface/src/Bookmarks.h +++ b/interface/src/Bookmarks.h @@ -28,6 +28,7 @@ public: Bookmarks(); virtual void setupMenus(Menu* menubar, MenuWrapper* menu) = 0; + void insert(const QString& name, const QVariant& address); // Overwrites any existing entry with same name. QString addressForBookmark(const QString& name) const; protected: @@ -37,7 +38,6 @@ protected: virtual void addBookmarkToMenu(Menu* menubar, const QString& name, const QVariant& bookmark) = 0; void enableMenuItems(bool enabled); virtual void readFromFile(); - void insert(const QString& name, const QVariant& address); // Overwrites any existing entry with same name. void sortActions(Menu* menubar, MenuWrapper* menu); int getMenuItemLocation(QList actions, const QString& name) const; void removeBookmarkFromMenu(Menu* menubar, const QString& name); diff --git a/interface/src/LODManager.cpp b/interface/src/LODManager.cpp index b0b919fb0f..0c9587d3ae 100644 --- a/interface/src/LODManager.cpp +++ b/interface/src/LODManager.cpp @@ -351,7 +351,18 @@ float LODManager::getHMDLODTargetFPS() const { } float LODManager::getLODTargetFPS() const { - auto refreshRateFPS = qApp->getRefreshRateManager().getActiveRefreshRate(); + + // Use the current refresh rate as the recommended rate target used to cap the LOD manager control value. + // When focused, Use the Focus Inactive as the targget LOD to void abrupt changes from the lod controller. + auto& refreshRateManager = qApp->getRefreshRateManager(); + auto refreshRateRegime = refreshRateManager.getRefreshRateRegime(); + auto refreshRateProfile = refreshRateManager.getRefreshRateProfile(); + auto refreshRateUXMode = refreshRateManager.getUXMode(); + auto refreshRateFPS = refreshRateManager.getActiveRefreshRate(); + if (refreshRateRegime == RefreshRateManager::RefreshRateRegime::FOCUS_ACTIVE) { + refreshRateFPS = refreshRateManager.queryRefreshRateTarget(refreshRateProfile, RefreshRateManager::RefreshRateRegime::FOCUS_INACTIVE, refreshRateUXMode); + } + auto lodTargetFPS = getDesktopLODTargetFPS(); if (qApp->isHMDMode()) { lodTargetFPS = getHMDLODTargetFPS(); diff --git a/interface/src/LocationBookmarks.cpp b/interface/src/LocationBookmarks.cpp index b2e31c3021..fda70d379d 100644 --- a/interface/src/LocationBookmarks.cpp +++ b/interface/src/LocationBookmarks.cpp @@ -67,6 +67,10 @@ QString LocationBookmarks::getHomeLocationAddress() { return addressForBookmark(HOME_BOOKMARK); } +QString LocationBookmarks::getAddress(const QString& bookmarkName) { + return addressForBookmark(bookmarkName); +} + void LocationBookmarks::teleportToBookmark() { QAction* action = qobject_cast(sender()); QString address = action->data().toString(); diff --git a/interface/src/LocationBookmarks.h b/interface/src/LocationBookmarks.h index f9de19c626..eebaf2f383 100644 --- a/interface/src/LocationBookmarks.h +++ b/interface/src/LocationBookmarks.h @@ -34,6 +34,13 @@ public: void setupMenus(Menu* menubar, MenuWrapper* menu) override; static const QString HOME_BOOKMARK; + /**jsdoc + * @function LocationBookmarks.getAddress + * @param {string} bookmarkName Name of the bookmark to get the address for. + * @returns {string} The url for the specified bookmark. If the bookmark does not exist, the empty string will be returned. + */ + Q_INVOKABLE QString getAddress(const QString& bookmarkName); + public slots: /**jsdoc @@ -48,7 +55,7 @@ public slots: void setHomeLocationToAddress(const QVariant& address); /**jsdoc - * @function LocationBookmarksgetHomeLocationAddress + * @function LocationBookmarks.getHomeLocationAddress * @returns {string} The url for the home location bookmark */ QString getHomeLocationAddress(); diff --git a/interface/src/PerformanceManager.cpp b/interface/src/PerformanceManager.cpp index 874d92521b..0a028f95cc 100644 --- a/interface/src/PerformanceManager.cpp +++ b/interface/src/PerformanceManager.cpp @@ -13,6 +13,7 @@ #include #include "scripting/RenderScriptingInterface.h" +#include "LODManager.h" PerformanceManager::PerformanceManager() { @@ -62,17 +63,29 @@ PerformanceManager::PerformancePreset PerformanceManager::getPerformancePreset() void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformancePreset preset) { + // Ugly case that prevent us to run deferred everywhere... + bool isDeferredCapable = platform::Profiler::isRenderMethodDeferredCapable(); + switch (preset) { case PerformancePreset::HIGH: - RenderScriptingInterface::getInstance()->setRenderMethod(RenderScriptingInterface::RenderMethod::DEFERRED); + RenderScriptingInterface::getInstance()->setRenderMethod( ( isDeferredCapable ? + RenderScriptingInterface::RenderMethod::DEFERRED : + RenderScriptingInterface::RenderMethod::FORWARD ) ); + RenderScriptingInterface::getInstance()->setShadowsEnabled(true); qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::REALTIME); + DependencyManager::get()->setWorldDetailQuality(0.5f); + break; case PerformancePreset::MID: - RenderScriptingInterface::getInstance()->setRenderMethod(RenderScriptingInterface::RenderMethod::DEFERRED); + RenderScriptingInterface::getInstance()->setRenderMethod((isDeferredCapable ? + RenderScriptingInterface::RenderMethod::DEFERRED : + RenderScriptingInterface::RenderMethod::FORWARD)); + RenderScriptingInterface::getInstance()->setShadowsEnabled(false); qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::INTERACTIVE); + DependencyManager::get()->setWorldDetailQuality(0.5f); break; case PerformancePreset::LOW: @@ -80,6 +93,8 @@ void PerformanceManager::applyPerformancePreset(PerformanceManager::PerformanceP RenderScriptingInterface::getInstance()->setShadowsEnabled(false); qApp->getRefreshRateManager().setRefreshRateProfile(RefreshRateManager::RefreshRateProfile::ECO); + DependencyManager::get()->setWorldDetailQuality(0.75f); + break; case PerformancePreset::UNKNOWN: default: diff --git a/interface/src/RefreshRateManager.cpp b/interface/src/RefreshRateManager.cpp index 4963eee8cf..da6c5cbd37 100644 --- a/interface/src/RefreshRateManager.cpp +++ b/interface/src/RefreshRateManager.cpp @@ -107,9 +107,7 @@ RefreshRateManager::RefreshRateProfile RefreshRateManager::getRefreshRateProfile RefreshRateManager::RefreshRateProfile profile = RefreshRateManager::RefreshRateProfile::REALTIME; if (getUXMode() != RefreshRateManager::UXMode::VR) { - profile =(RefreshRateManager::RefreshRateProfile) _refreshRateProfileSettingLock.resultWithReadLock([&] { - return _refreshRateProfileSetting.get(); - }); + return _refreshRateProfile; } return profile; @@ -138,15 +136,17 @@ void RefreshRateManager::setUXMode(RefreshRateManager::UXMode uxMode) { } } +int RefreshRateManager::queryRefreshRateTarget(RefreshRateProfile profile, RefreshRateRegime regime, UXMode uxMode) const { + int targetRefreshRate = VR_TARGET_RATE; + if (uxMode == RefreshRateManager::UXMode::DESKTOP) { + targetRefreshRate = REFRESH_RATE_PROFILES[profile][regime]; + } + return targetRefreshRate; +} + void RefreshRateManager::updateRefreshRateController() const { if (_refreshRateOperator) { - int targetRefreshRate; - if (_uxMode == RefreshRateManager::UXMode::DESKTOP) { - targetRefreshRate = REFRESH_RATE_PROFILES[_refreshRateProfile][_refreshRateRegime]; - } else { - targetRefreshRate = VR_TARGET_RATE; - } - + int targetRefreshRate = queryRefreshRateTarget(_refreshRateProfile, _refreshRateRegime, _uxMode); _refreshRateOperator(targetRefreshRate); _activeRefreshRate = targetRefreshRate; } diff --git a/interface/src/RefreshRateManager.h b/interface/src/RefreshRateManager.h index 74dd8156e1..567a515898 100644 --- a/interface/src/RefreshRateManager.h +++ b/interface/src/RefreshRateManager.h @@ -65,6 +65,9 @@ public: int getActiveRefreshRate() const { return _activeRefreshRate; } void updateRefreshRateController() const; + // query the refresh rate target at the specified combination + int queryRefreshRateTarget(RefreshRateProfile profile, RefreshRateRegime regime, UXMode uxMode) const; + void resetInactiveTimer(); void toggleInactive(); diff --git a/interface/src/avatar/AvatarManager.cpp b/interface/src/avatar/AvatarManager.cpp index bb8b7ba7f4..efe3d59d90 100755 --- a/interface/src/avatar/AvatarManager.cpp +++ b/interface/src/avatar/AvatarManager.cpp @@ -490,7 +490,7 @@ void AvatarManager::buildPhysicsTransaction(PhysicsEngine::Transaction& transact _myAvatar->getCharacterController()->buildPhysicsTransaction(transaction); for (auto avatar : _otherAvatarsToChangeInPhysics) { bool isInPhysics = avatar->isInPhysicsSimulation(); - if (isInPhysics != avatar->shouldBeInPhysicsSimulation()) { + if (isInPhysics != avatar->shouldBeInPhysicsSimulation() || avatar->_needsReinsertion) { if (isInPhysics) { transaction.objectsToRemove.push_back(avatar->_motionState); avatar->_motionState = nullptr; diff --git a/interface/src/avatar/MyAvatar.cpp b/interface/src/avatar/MyAvatar.cpp index b91217da63..3800a330bb 100644 --- a/interface/src/avatar/MyAvatar.cpp +++ b/interface/src/avatar/MyAvatar.cpp @@ -2488,12 +2488,12 @@ QVariantList MyAvatar::getAvatarEntitiesVariant() { QVariantMap avatarEntityData; avatarEntityData["id"] = entityID; EntityItemProperties entityProperties = entity->getProperties(desiredProperties); - QScriptValue scriptProperties; { std::lock_guard guard(_scriptEngineLock); + QScriptValue scriptProperties; scriptProperties = EntityItemPropertiesToScriptValue(_scriptEngine, entityProperties); + avatarEntityData["properties"] = scriptProperties.toVariant(); } - avatarEntityData["properties"] = scriptProperties.toVariant(); avatarEntitiesData.append(QVariant(avatarEntityData)); } } diff --git a/interface/src/avatar/MyAvatar.h b/interface/src/avatar/MyAvatar.h index e87aa87bd1..d60c023caa 100644 --- a/interface/src/avatar/MyAvatar.h +++ b/interface/src/avatar/MyAvatar.h @@ -39,6 +39,23 @@ class ModelItemID; class MyHead; class DetailedMotionState; +/**jsdoc + *

Locomotion control types.

+ * + * + * + * + * + * + * + * + * + *
ValueNameDescription
0DefaultYour walking speed is constant; it doesn't change depending on how far + * forward you push your controller's joystick. Fully pushing your joystick forward makes your avatar run.
1AnalogYour walking speed changes in steps based on how far forward you push your + * controller's joystick. Fully pushing your joystick forward makes your avatar run.
2AnalogPlusYour walking speed changes proportionally to how far forward you push + * your controller's joystick. Fully pushing your joystick forward makes your avatar run.
+ * @typedef {number} MyAvatar.LocomotionControlsMode + */ enum LocomotionControlsMode { CONTROLS_DEFAULT = 0, CONTROLS_ANALOG, @@ -128,6 +145,8 @@ class MyAvatar : public Avatar { * avatar. Read-only. * @property {number} sensorToWorldScale - The scale that transforms dimensions in the user's real world to the avatar's * size in the virtual world. Read-only. + * @property {boolean} hasPriority - true if the avatar is in a "hero" zone, false if it isn't. + * Read-only. * * @comment IMPORTANT: This group of properties is copied from Avatar.h; they should NOT be edited here. * @property {Vec3} skeletonOffset - Can be used to apply a translation offset between the avatar's position and the @@ -239,9 +258,16 @@ class MyAvatar : public Avatar { * where MyAvatar.sessionUUID is not available (e.g., if not connected to a domain). Note: Likely to be deprecated. * Read-only. * - * @property {number} walkSpeed - The walk speed of your avatar. - * @property {number} walkBackwardSpeed - The walk backward speed of your avatar. - * @property {number} sprintSpeed - The sprint speed of your avatar. + * @property {number} walkSpeed - The walk speed of your avatar for the current control scheme (see + * {@link MyAvatar.getControlScheme|getControlScheme}). + * @property {number} walkBackwardSpeed - The walk backward speed of your avatar for the current control scheme (see + * {@link MyAvatar.getControlScheme|getControlScheme}). + * @property {number} sprintSpeed - The sprint (run) speed of your avatar for the current control scheme (see + * {@link MyAvatar.getControlScheme|getControlScheme}). + * @property {number} analogPlusWalkSpeed - The walk speed of your avatar for the "AnalogPlus" control scheme. + *

Warning: Setting this value also sets the value of analogPlusSprintSpeed to twice + * the value.

+ * @property {number} analogPlusSprintSpeed - The sprint speed of your avatar for the "AnalogPlus" control scheme. * @property {MyAvatar.SitStandModelType} userRecenterModel - Controls avatar leaning and recentering behavior. * @property {number} isInSittingState - true if your avatar is sitting (avatar leaning is disabled, * recenntering is enabled), false if it is standing (avatar leaning is enabled, and avatar recenters if it @@ -281,6 +307,7 @@ class MyAvatar : public Avatar { * @borrows Avatar.updateAvatarEntity as updateAvatarEntity * @borrows Avatar.clearAvatarEntity as clearAvatarEntity * @borrows Avatar.setForceFaceTrackerConnected as setForceFaceTrackerConnected + * @borrows Avatar.setSkeletonModelURL as setSkeletonModelURL * @borrows Avatar.getAttachmentData as getAttachmentData * @borrows Avatar.setAttachmentData as setAttachmentData * @borrows Avatar.attach as attach @@ -308,7 +335,6 @@ class MyAvatar : public Avatar { * @comment Avatar.setAbsoluteJointTranslationInObjectFrame as setAbsoluteJointTranslationInObjectFrame - Don't borrow because implementation is different. * @borrows Avatar.getTargetScale as getTargetScale * @borrows Avatar.resetLastSent as resetLastSent - * @borrows Avatar.hasPriority as hasPriority */ // FIXME: `glm::vec3 position` is not accessible from QML, so this exposes position in a QML-native type Q_PROPERTY(QVector3D qmlPosition READ getQmlPosition) @@ -583,14 +609,13 @@ public: * the avatar will move in unpredictable ways. For more information about avatar joint orientation standards, see * Avatar Standards.

* @function MyAvatar.overrideAnimation - * @param url {string} The URL to the animation file. Animation files need to be FBX format, but only need to contain the + * @param {string} url - The URL to the animation file. Animation files need to be FBX format, but only need to contain the * avatar skeleton and animation data. - * @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. - * @param loop {boolean} Set to true if the animation should loop. - * @param firstFrame {number} The frame the animation should start at. - * @param lastFrame {number} The frame the animation should end at. + * @param {number} fps - The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. + * @param {boolean} loop - true if the animation should loop, false if it shouldn't. + * @param {number} firstFrame - The frame to start the animation at. + * @param {number} lastFrame - The frame to end the animation at. * @example Play a clapping animation on your avatar for three seconds. - * // Clap your hands for 3 seconds then restore animation back to the avatar. * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; * MyAvatar.overrideAnimation(ANIM_URL, 30, true, 0, 53); * Script.setTimeout(function () { @@ -601,18 +626,18 @@ public: Q_INVOKABLE void overrideAnimation(const QString& url, float fps, bool loop, float firstFrame, float lastFrame); /**jsdoc - * overrideHandAnimation() Gets the overrides the default hand poses that are triggered with controller buttons. - * use {@link MyAvatar.restoreHandAnimation}.

to restore the default poses. + * Overrides the default hand poses that are triggered with controller buttons. + * Use {@link MyAvatar.restoreHandAnimation} to restore the default poses. * @function MyAvatar.overrideHandAnimation - * @param isLeft {boolean} Set true if using the left hand - * @param url {string} The URL to the animation file. Animation files need to be FBX format, but only need to contain the + * @param isLeft {boolean} true to override the left hand, false to override the right hand. + * @param {string} url - The URL of the animation file. Animation files need to be FBX format, but only need to contain the * avatar skeleton and animation data. - * @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. - * @param loop {boolean} Set to true if the animation should loop. - * @param firstFrame {number} The frame the animation should start at. - * @param lastFrame {number} The frame the animation should end at - * @example Override left hand animation for three seconds. - * // Override the left hand pose then restore the default pose. + * @param {number} fps - The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. + * @param {boolean} loop - true if the animation should loop, false if it shouldn't. + * @param {number} firstFrame - The frame to start the animation at. + * @param {number} lastFrame - The frame to end the animation at. + * @example Override left hand animation for three seconds. + * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; * MyAvatar.overrideHandAnimation(isLeft, ANIM_URL, 30, true, 0, 53); * Script.setTimeout(function () { * MyAvatar.restoreHandAnimation(); @@ -629,7 +654,6 @@ public: * animation, this function has no effect.

* @function MyAvatar.restoreAnimation * @example Play a clapping animation on your avatar for three seconds. - * // Clap your hands for 3 seconds then restore animation back to the avatar. * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; * MyAvatar.overrideAnimation(ANIM_URL, 30, true, 0, 53); * Script.setTimeout(function () { @@ -639,16 +663,15 @@ public: Q_INVOKABLE void restoreAnimation(); /**jsdoc - * Restores the default hand animation state machine that is driven by the state machine in the avatar-animation json. + * Restores the default hand animation state machine that is driven by the state machine in the avatar-animation JSON. *

The avatar animation system includes a set of default animations along with rules for how those animations are blended * together with procedural data (such as look at vectors, hand sensors etc.). Playing your own custom animations will - * override the default animations. restoreHandAnimation() is used to restore the default hand poses - * If you aren't currently playing an override hand - * animation, this function has no effect.

+ * override the default animations. restoreHandAnimation() is used to restore the default hand poses. + * If you aren't currently playing an override hand animation, this function has no effect.

* @function MyAvatar.restoreHandAnimation * @param isLeft {boolean} Set to true if using the left hand * @example Override left hand animation for three seconds. - * // Override the left hand pose then restore the default pose. + * var ANIM_URL = "https://s3.amazonaws.com/hifi-public/animations/ClapAnimations/ClapHands_Standing.fbx"; * MyAvatar.overrideHandAnimation(isLeft, ANIM_URL, 30, true, 0, 53); * Script.setTimeout(function () { * MyAvatar.restoreHandAnimation(); @@ -689,12 +712,13 @@ public: * the avatar will move in unpredictable ways. For more information about avatar joint orientation standards, see * Avatar Standards. * @function MyAvatar.overrideRoleAnimation - * @param role {string} The animation role to override - * @param url {string} The URL to the animation file. Animation files need to be in FBX format, but only need to contain the avatar skeleton and animation data. - * @param fps {number} The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. - * @param loop {boolean} Set to true if the animation should loop - * @param firstFrame {number} The frame the animation should start at - * @param lastFrame {number} The frame the animation should end at + * @param {string} role - The animation role to override + * @param {string} url - The URL to the animation file. Animation files need to be in FBX format, but only need to contain + * the avatar skeleton and animation data. + * @param {number} fps - The frames per second (FPS) rate for the animation playback. 30 FPS is normal speed. + * @param {boolean} loop - true if the animation should loop, false if it shouldn't. + * @param {number} firstFrame - The frame the animation should start at. + * @param {number} lastFrame - The frame the animation should end at. * @example The default avatar-animation.json defines an "idleStand" animation role. This role specifies that when the avatar is not moving, * an animation clip of the avatar idling with hands hanging at its side will be used. It also specifies that when the avatar moves, the animation * will smoothly blend to the walking animation used by the "walkFwd" animation role. @@ -782,33 +806,42 @@ public: * mode. */ Q_INVOKABLE bool getSnapTurn() const { return _useSnapTurn; } + /**jsdoc - * Sets whether your should do snap turns or smooth turns in HMD mode. + * Sets whether you do snap turns or smooth turns in HMD mode. * @function MyAvatar.setSnapTurn * @param {boolean} on - true to do snap turns in HMD mode; false to do smooth turns in HMD mode. */ Q_INVOKABLE void setSnapTurn(bool on) { _useSnapTurn = on; } - /** + /**jsdoc + * Gets the control scheme that is in use. * @function MyAvatar.getControlScheme - * @returns {number} - */ + * @returns {MyAvatar.LocomotionControlsMode} The control scheme that is in use. + */ Q_INVOKABLE int getControlScheme() const { return _controlSchemeIndex; } - /** + /**jsdoc + * Sets the control scheme to use. * @function MyAvatar.setControlScheme - * @param {number} index - */ + * @param {MyAvatar.LocomotionControlsMode} controlScheme - The control scheme to use. + */ Q_INVOKABLE void setControlScheme(int index) { _controlSchemeIndex = (index >= 0 && index <= 2) ? index : 0; } /**jsdoc + * Gets whether your avatar hovers when its feet are not on the ground. * @function MyAvatar.hoverWhenUnsupported - * @returns {boolean} + * @returns {boolean} true if your avatar hovers when its feet are not on the ground, false if it + * falls. */ + // FIXME: Should be named, getHoverWhenUnsupported(). Q_INVOKABLE bool hoverWhenUnsupported() const { return _hoverWhenUnsupported; } + /**jsdoc + * Sets whether your avatar hovers when its feet are not on the ground. * @function MyAvatar.setHoverWhenUnsupported - * @param {boolean} on + * @param {boolean} hover - true if your avatar hovers when its feet are not on the ground, false + * if it falls. */ Q_INVOKABLE void setHoverWhenUnsupported(bool on) { _hoverWhenUnsupported = on; } @@ -826,26 +859,31 @@ public: * @returns {string} "left" for the left hand, "right" for the right hand. */ Q_INVOKABLE QString getDominantHand() const; + /**jsdoc - * @function MyAVatar.setStrafeEnabled - * @param {bool} enabled - */ + * Sets whether strafing is enabled. + * @function MyAvatar.setStrafeEnabled + * @param {boolean} enabled - true if strafing is enabled, false if it isn't. + */ Q_INVOKABLE void setStrafeEnabled(bool enabled); + /**jsdoc - * @function MyAvatar.getStrafeEnabled - * @returns {bool} - */ + * Gets whether strafing is enabled. + * @function MyAvatar.getStrafeEnabled + * @returns {boolean} true if strafing is enabled, false if it isn't. + */ Q_INVOKABLE bool getStrafeEnabled() const; + /**jsdoc + * Sets the HMD alignment relative to your avatar. * @function MyAvatar.setHmdAvatarAlignmentType * @param {string} type - "head" to align your head and your avatar's head, "eyes" to align your * eyes and your avatar's eyes. - * */ Q_INVOKABLE void setHmdAvatarAlignmentType(const QString& type); /**jsdoc - * Gets the HMD alignment for your avatar. + * Gets the HMD alignment relative to your avatar. * @function MyAvatar.getHmdAvatarAlignmentType * @returns {string} "head" if aligning your head and your avatar's head, "eyes" if aligning your * eyes and your avatar's eyes. @@ -1495,18 +1533,8 @@ public: */ Q_INVOKABLE float getDriveGear5(); - /**jsdoc - * Choose the control scheme. - * @function MyAvatar.setControlSchemeIndex - * @param {number} Choose the control scheme to be used. - */ void setControlSchemeIndex(int index); - /**jsdoc - * Check what control scheme is in use. - * @function MyAvatar.getControlSchemeIndex - * @returns {number} Returns the index associated with a given control scheme. - */ int getControlSchemeIndex(); /**jsdoc @@ -1584,8 +1612,8 @@ public: Q_INVOKABLE bool getCharacterControllerEnabled(); // deprecated /**jsdoc - * @comment Different behavior to the Avatar version of this method. * Gets the rotation of a joint relative to the avatar. + * @comment Different behavior to the Avatar version of this method. * @function MyAvatar.getAbsoluteJointRotationInObjectFrame * @param {number} index - The index of the joint. * @returns {Quat} The rotation of the joint relative to the avatar. @@ -1597,8 +1625,8 @@ public: virtual glm::quat getAbsoluteJointRotationInObjectFrame(int index) const override; /**jsdoc - * @comment Different behavior to the Avatar version of this method. * Gets the translation of a joint relative to the avatar. + * @comment Different behavior to the Avatar version of this method. * @function MyAvatar.getAbsoluteJointTranslationInObjectFrame * @param {number} index - The index of the joint. * @returns {Vec3} The translation of the joint relative to the avatar. @@ -2441,6 +2469,9 @@ private: void updateEyeContactTarget(float deltaTime); // These are made private for MyAvatar so that you will use the "use" methods instead + /**jsdoc + * @comment Borrows the base class's JSDoc. + */ Q_INVOKABLE virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override; virtual void updatePalms() override {} diff --git a/interface/src/graphics/GraphicsEngine.cpp b/interface/src/graphics/GraphicsEngine.cpp index 4f8b86cc0c..7f9a612697 100644 --- a/interface/src/graphics/GraphicsEngine.cpp +++ b/interface/src/graphics/GraphicsEngine.cpp @@ -132,6 +132,10 @@ static const int THROTTLED_SIM_FRAME_PERIOD_MS = MSECS_PER_SECOND / THROTTLED_SI bool GraphicsEngine::shouldPaint() const { auto displayPlugin = qApp->getActiveDisplayPlugin(); + if (!displayPlugin) { + // We're shutting down + return false; + } #ifdef DEBUG_PAINT_DELAY static uint64_t paintDelaySamples{ 0 }; @@ -175,6 +179,10 @@ void GraphicsEngine::render_performFrame() { { PROFILE_RANGE(render, "/getActiveDisplayPlugin"); displayPlugin = qApp->getActiveDisplayPlugin(); + if (!displayPlugin) { + // We're shutting down + return; + } } { diff --git a/interface/src/main.cpp b/interface/src/main.cpp index 7fc4a5b651..3fd65f452c 100644 --- a/interface/src/main.cpp +++ b/interface/src/main.cpp @@ -85,6 +85,7 @@ int main(int argc, const char* argv[]) { QCommandLineOption overrideScriptsPathOption(SCRIPTS_SWITCH, "set scripts ", "path"); QCommandLineOption responseTokensOption("tokens", "set response tokens ", "json"); QCommandLineOption displayNameOption("displayName", "set user display name ", "string"); + QCommandLineOption setBookmarkOption("setBookmark", "set bookmark key=value pair", "string"); parser.addOption(urlOption); parser.addOption(noLauncherOption); @@ -97,6 +98,7 @@ int main(int argc, const char* argv[]) { parser.addOption(allowMultipleInstancesOption); parser.addOption(responseTokensOption); parser.addOption(displayNameOption); + parser.addOption(setBookmarkOption); if (!parser.parse(arguments)) { std::cout << parser.errorText().toStdString() << std::endl; // Avoid Qt log spam diff --git a/interface/src/ui/InteractiveWindow.cpp b/interface/src/ui/InteractiveWindow.cpp index 2fbb665c48..84d24ddeae 100644 --- a/interface/src/ui/InteractiveWindow.cpp +++ b/interface/src/ui/InteractiveWindow.cpp @@ -68,6 +68,16 @@ void interactiveWindowPointerFromScriptValue(const QScriptValue& object, Interac } } +void InteractiveWindow::forwardKeyPressEvent(int key, int modifiers) { + QKeyEvent* event = new QKeyEvent(QEvent::KeyPress, key, static_cast(modifiers)); + QCoreApplication::postEvent(QCoreApplication::instance(), event); +} + +void InteractiveWindow::forwardKeyReleaseEvent(int key, int modifiers) { + QKeyEvent* event = new QKeyEvent(QEvent::KeyRelease, key, static_cast(modifiers)); + QCoreApplication::postEvent(QCoreApplication::instance(), event); +} + /**jsdoc * A set of properties used when creating an InteractiveWindow. * @typedef {object} InteractiveWindow.Properties @@ -152,12 +162,16 @@ InteractiveWindow::InteractiveWindow(const QString& sourceUrl, const QVariantMap _dockWidget->getQuickView()->rootContext()->setContextProperty(EVENT_BRIDGE_PROPERTY, this); QObject::connect(rootItem, SIGNAL(sendToScript(QVariant)), this, SLOT(qmlToScript(const QVariant&)), Qt::QueuedConnection); + QObject::connect(rootItem, SIGNAL(keyPressEvent(int, int)), this, SLOT(forwardKeyPressEvent(int, int)), + Qt::QueuedConnection); + QObject::connect(rootItem, SIGNAL(keyReleaseEvent(int, int)), this, SLOT(forwardKeyReleaseEvent(int, int)), + Qt::QueuedConnection); emit mainWindow->windowGeometryChanged(qApp->getWindow()->geometry()); } }); + _dockWidget->setSource(QUrl(sourceUrl)); - mainWindow->addDockWidget(dockArea, _dockWidget.get()); } else { auto offscreenUi = DependencyManager::get(); diff --git a/interface/src/ui/InteractiveWindow.h b/interface/src/ui/InteractiveWindow.h index fd0a3376fb..c7b3631dde 100644 --- a/interface/src/ui/InteractiveWindow.h +++ b/interface/src/ui/InteractiveWindow.h @@ -287,6 +287,9 @@ protected slots: */ void qmlToScript(const QVariant& message); + void forwardKeyPressEvent(int key, int modifiers); + void forwardKeyReleaseEvent(int key, int modifiers); + private: QPointer _qmlWindow; std::shared_ptr _dockWidget { nullptr }; diff --git a/interface/src/ui/Stats.cpp b/interface/src/ui/Stats.cpp index d243fa9ebf..dc9780adf5 100644 --- a/interface/src/ui/Stats.cpp +++ b/interface/src/ui/Stats.cpp @@ -416,6 +416,8 @@ void Stats::updateStats(bool force) { gpuContext->getFrameStats(gpuFrameStats); STAT_UPDATE(drawcalls, gpuFrameStats._DSNumDrawcalls); + STAT_UPDATE(lodTargetFramerate, DependencyManager::get()->getLODTargetFPS()); + STAT_UPDATE(lodAngle, DependencyManager::get()->getLODAngleDeg()); // Incoming packets diff --git a/interface/src/ui/Stats.h b/interface/src/ui/Stats.h index cb13945320..b87e3a3dbc 100644 --- a/interface/src/ui/Stats.h +++ b/interface/src/ui/Stats.h @@ -109,6 +109,8 @@ private: \ * @property {number} shadowRendered - Read-only. * @property {string} sendingMode - Read-only. * @property {string} packetStats - Read-only. + * @property {number} lodAngle - Read-only. + * @property {number} lodTargetFramerate - Read-only. * @property {string} lodStatus - Read-only. * @property {string} timingStats - Read-only. * @property {string} gameUpdateStats - Read-only. @@ -260,7 +262,7 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, processing, 0) STATS_PROPERTY(int, processingPending, 0) STATS_PROPERTY(int, triangles, 0) - STATS_PROPERTY(int, drawcalls, 0) + STATS_PROPERTY(quint32 , drawcalls, 0) STATS_PROPERTY(int, materialSwitches, 0) STATS_PROPERTY(int, itemConsidered, 0) STATS_PROPERTY(int, itemOutOfView, 0) @@ -272,6 +274,8 @@ class Stats : public QQuickItem { STATS_PROPERTY(int, shadowRendered, 0) STATS_PROPERTY(QString, sendingMode, QString()) STATS_PROPERTY(QString, packetStats, QString()) + STATS_PROPERTY(int, lodAngle, 0) + STATS_PROPERTY(int, lodTargetFramerate, 0) STATS_PROPERTY(QString, lodStatus, QString()) STATS_PROPERTY(QString, timingStats, QString()) STATS_PROPERTY(QString, gameUpdateStats, QString()) @@ -858,6 +862,20 @@ signals: */ void packetStatsChanged(); + /**jsdoc + * Triggered when the value of the lodAngle property changes. + * @function Stats.lodAngleChanged + * @returns {Signal} + */ + void lodAngleChanged(); + + /**jsdoc + * Triggered when the value of the lodTargetFramerate property changes. + * @function Stats.lodTargetFramerateChanged + * @returns {Signal} + */ + void lodTargetFramerateChanged(); + /**jsdoc * Triggered when the value of the lodStatus property changes. * @function Stats.lodStatusChanged diff --git a/launchers/darwin/CMakeLists.txt b/launchers/darwin/CMakeLists.txt index d8baea6e3c..2de43d93cb 100644 --- a/launchers/darwin/CMakeLists.txt +++ b/launchers/darwin/CMakeLists.txt @@ -66,6 +66,10 @@ endfunction() add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files}) set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME}) set_from_env(LAUNCHER_HMAC_SECRET LAUNCHER_HMAC_SECRET "") +if (LAUNCHER_HMAC_SECRET STREQUAL "") + message(FATAL_ERROR "LAUNCHER_HMAC_SECRET is not set") +endif() + target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_HMAC_SECRET="${LAUNCHER_HMAC_SECRET}") file(GLOB NIB_FILES "nib/*.xib") diff --git a/launchers/darwin/images/interface.icns b/launchers/darwin/images/interface.icns index 4aeb8301ce..8dadfd5037 100644 Binary files a/launchers/darwin/images/interface.icns and b/launchers/darwin/images/interface.icns differ diff --git a/launchers/darwin/nib/Window.xib b/launchers/darwin/nib/Window.xib index 8260f1d3cd..48948e3825 100644 --- a/launchers/darwin/nib/Window.xib +++ b/launchers/darwin/nib/Window.xib @@ -11,7 +11,7 @@ - + diff --git a/launchers/darwin/src/CredentialsRequest.m b/launchers/darwin/src/CredentialsRequest.m index fd651bd98a..a5c24496ec 100644 --- a/launchers/darwin/src/CredentialsRequest.m +++ b/launchers/darwin/src/CredentialsRequest.m @@ -7,8 +7,9 @@ - (void) confirmCredentials:(NSString*)username :(NSString*)password { NSLog(@"web request started"); + NSString* trimmedUsername = [username stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; NSString *post = [NSString stringWithFormat:@"grant_type=password&username=%@&password=%@&scope=owner", - [username stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet alphanumericCharacterSet]], + [trimmedUsername stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet alphanumericCharacterSet]], [password stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet alphanumericCharacterSet]]]; NSData *postData = [post dataUsingEncoding:NSUTF8StringEncoding]; NSString *postLength = [NSString stringWithFormat:@"%ld", (unsigned long)[postData length]]; diff --git a/launchers/darwin/src/CustomUI.m b/launchers/darwin/src/CustomUI.m index 01e666637a..156efe04d5 100644 --- a/launchers/darwin/src/CustomUI.m +++ b/launchers/darwin/src/CustomUI.m @@ -15,6 +15,31 @@ NSString* hifiBackgroundFilename = @"hifi_window"; forObject:self]; fieldEditor.insertionPointColor = insertionPointColor; } + +- (BOOL) performKeyEquivalent:(NSEvent *)event +{ + if ([event type] == NSEventTypeKeyDown) { + if ([event modifierFlags] & NSEventModifierFlagCommand) { + if ([[event charactersIgnoringModifiers] isEqualToString:@"v"]) { + [NSApp sendAction:(NSSelectorFromString(@"paste:")) to:nil from:self]; + return TRUE; + } + + if ([[event charactersIgnoringModifiers] isEqualToString:@"c"]) { + [NSApp sendAction:(NSSelectorFromString(@"copy:")) to:nil from:self]; + return TRUE; + } + + if ([[event charactersIgnoringModifiers] isEqualToString:@"a"]) { + [NSApp sendAction:(NSSelectorFromString(@"selectAll:")) to:nil from:self]; + return TRUE; + } + } + } + + return [super performKeyEquivalent:event]; +} + - (void) mouseDown:(NSEvent *)event { NSColor *insertionPointColor = [NSColor whiteColor]; @@ -63,6 +88,30 @@ NSString* hifiBackgroundFilename = @"hifi_window"; fieldEditor.insertionPointColor = insertionPointColor; return status; } + +- (BOOL) performKeyEquivalent:(NSEvent *)event +{ + if ([event type] == NSEventTypeKeyDown) { + if ([event modifierFlags] & NSEventModifierFlagCommand) { + if ([[event charactersIgnoringModifiers] isEqualToString:@"v"]) { + [NSApp sendAction:(NSSelectorFromString(@"paste:")) to:nil from:self]; + return TRUE; + } + + if ([[event charactersIgnoringModifiers] isEqualToString:@"c"]) { + [NSApp sendAction:(NSSelectorFromString(@"copy:")) to:nil from:self]; + return TRUE; + } + + if ([[event charactersIgnoringModifiers] isEqualToString:@"a"]) { + [NSApp sendAction:(NSSelectorFromString(@"selectAll:")) to:nil from:self]; + return TRUE; + } + } + } + + return [super performKeyEquivalent:event]; +} @end diff --git a/launchers/darwin/src/DownloadDomainContent.m b/launchers/darwin/src/DownloadDomainContent.m index 7b0247a6f4..65878db356 100644 --- a/launchers/darwin/src/DownloadDomainContent.m +++ b/launchers/darwin/src/DownloadDomainContent.m @@ -54,7 +54,9 @@ -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { NSLog(@"completed; error: %@", error); + if (error) { + [[Launcher sharedLauncher] displayErrorPage]; + } } - @end diff --git a/launchers/darwin/src/DownloadInterface.m b/launchers/darwin/src/DownloadInterface.m index b957197567..7a8abf2e0a 100644 --- a/launchers/darwin/src/DownloadInterface.m +++ b/launchers/darwin/src/DownloadInterface.m @@ -66,6 +66,9 @@ -(void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error { NSLog(@"completed; error: %@", error); + if (error) { + [[Launcher sharedLauncher] displayErrorPage]; + } } diff --git a/launchers/darwin/src/Launcher.h b/launchers/darwin/src/Launcher.h index 5838e25a82..69484a378d 100644 --- a/launchers/darwin/src/Launcher.h +++ b/launchers/darwin/src/Launcher.h @@ -57,6 +57,7 @@ typedef enum LoginErrorTypes - (BOOL) loginShouldSetErrorState; - (void) displayErrorPage; - (void) showLoginScreen; +- (NSString*) getLauncherPath; - (ProcessState) currentProccessState; - (void) setCurrentProcessState:(ProcessState) aProcessState; - (void) setLoginErrorState:(LoginError) aLoginError; diff --git a/launchers/darwin/src/Launcher.m b/launchers/darwin/src/Launcher.m index 07fc8878da..d60c4080a2 100644 --- a/launchers/darwin/src/Launcher.m +++ b/launchers/darwin/src/Launcher.m @@ -46,6 +46,7 @@ static BOOL const DELETE_ZIP_FILES = TRUE; } -(void)awakeFromNib { + [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE]; [[[NSWorkspace sharedWorkspace] notificationCenter] addObserver:self selector:@selector(didTerminateApp:) name:NSWorkspaceDidTerminateApplicationNotification @@ -73,6 +74,11 @@ static BOOL const DELETE_ZIP_FILES = TRUE; return filePath; } +- (NSString*) getLauncherPath +{ + return [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/Contents/MacOS/"]; +} + - (void) extractZipFileAtDestination:(NSString *)destination :(NSString*)file { NSTask* task = [[NSTask alloc] init]; @@ -109,6 +115,7 @@ static BOOL const DELETE_ZIP_FILES = TRUE; userInfo:nil repeats:NO]; } + [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE]; } - (void) setDownloadContextFilename:(NSString *)aFilename @@ -174,7 +181,7 @@ static BOOL const DELETE_ZIP_FILES = TRUE; - (NSString*) getAppPath { - return [[[NSBundle mainBundle] bundlePath] stringByAppendingString:@"/Contents/MacOS/"]; + return [self getDownloadPathForContentAndScripts]; } - (BOOL) loginShouldSetErrorState @@ -272,6 +279,7 @@ static BOOL const DELETE_ZIP_FILES = TRUE; -(void)onSplashScreenTimerFinished:(NSTimer *)timer { + [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE]; [self showLoginScreen]; } @@ -317,7 +325,7 @@ static BOOL const DELETE_ZIP_FILES = TRUE; - (void) launchInterface { - NSString* launcherPath = [[self getAppPath] stringByAppendingString:@"HQ Launcher"]; + NSString* launcherPath = [[self getLauncherPath] stringByAppendingString:@"HQ Launcher"]; [[Settings sharedSettings] setLauncherPath:launcherPath]; [[Settings sharedSettings] save]; @@ -331,6 +339,7 @@ static BOOL const DELETE_ZIP_FILES = TRUE; NSString* scriptsPath = [[self getAppPath] stringByAppendingString:@"interface.app/Contents/Resources/scripts/simplifiedUI/"]; NSString* domainUrl = [[Settings sharedSettings] getDomainUrl]; NSString* userToken = [[Launcher sharedLauncher] getTokenString]; + NSString* homeBookmark = [[NSString stringWithFormat:@"hqhome="] stringByAppendingString:domainUrl]; NSArray* arguments; if (userToken != nil) { arguments = [NSArray arrayWithObjects: @@ -338,21 +347,21 @@ static BOOL const DELETE_ZIP_FILES = TRUE; @"--tokens", userToken, @"--cache", contentPath, @"--displayName", displayName, - @"--script", scriptsPath, + @"--scripts", scriptsPath, + @"--setBookmark", homeBookmark, @"--no-updater", @"--no-launcher", nil]; } else { arguments = [NSArray arrayWithObjects: @"--url" , domainUrl, @"--cache", contentPath, - @"--script", scriptsPath, + @"--scripts", scriptsPath, + @"--setBookmark", homeBookmark, @"--no-updater", @"--no-launcher", nil]; } [workspace launchApplicationAtURL:url options:NSWorkspaceLaunchNewInstance configuration:[NSDictionary dictionaryWithObject:arguments forKey:NSWorkspaceLaunchConfigurationArguments] error:&error]; - //NSLog(@"arguments %@", [NSDictionary dictionaryWithObject:arguments forKey:NSWorkspaceLaunchConfigurationArguments]); - [NSApp terminate:self]; } diff --git a/launchers/darwin/src/OrganizationRequest.m b/launchers/darwin/src/OrganizationRequest.m index 36a4228c96..abc104785a 100644 --- a/launchers/darwin/src/OrganizationRequest.m +++ b/launchers/darwin/src/OrganizationRequest.m @@ -4,15 +4,15 @@ #import "Launcher.h" -static NSString* const organizationURL = @"https://s3.amazonaws.com/hifi-public/huffman/organizations/"; +static NSString* const organizationURL = @"https://orgs.highfidelity.com/organizations/"; @implementation OrganizationRequest - (void) confirmOrganization:(NSString*)aOrganization :(NSString*)aUsername { self.username = aUsername; - + NSString* trimmedOrgString = [aOrganization stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]]; const char *cKey = LAUNCHER_HMAC_SECRET; - const char *cData = [[aOrganization lowercaseString] cStringUsingEncoding:NSASCIIStringEncoding]; + const char *cData = [[trimmedOrgString lowercaseString] cStringUsingEncoding:NSASCIIStringEncoding]; unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH]; CCHmac(kCCHmacAlgSHA256, cKey, strlen(cKey), cData, strlen(cData), cHMAC); NSData *HMACData = [NSData dataWithBytes:cHMAC length:sizeof(cHMAC)]; diff --git a/launchers/darwin/src/main.mm b/launchers/darwin/src/main.mm index cdcd66f963..7feab64d86 100644 --- a/launchers/darwin/src/main.mm +++ b/launchers/darwin/src/main.mm @@ -41,5 +41,6 @@ int main(int argc, const char* argv[]) { [appMenu addItem:quitMenuItem]; [appMenuItem setSubmenu:appMenu]; + [[NSApplication sharedApplication] activateIgnoringOtherApps:TRUE]; return NSApplicationMain(argc, argv); } diff --git a/launchers/win32/CMakeLists.txt b/launchers/win32/CMakeLists.txt index 8417831312..a472c68688 100644 --- a/launchers/win32/CMakeLists.txt +++ b/launchers/win32/CMakeLists.txt @@ -50,6 +50,11 @@ function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE) endfunction() set_from_env(LAUNCHER_HMAC_SECRET LAUNCHER_HMAC_SECRET "") + +if (LAUNCHER_HMAC_SECRET STREQUAL "") + message(FATAL_ERROR "LAUNCHER_HMAC_SECRET is not set") +endif() + target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_HMAC_SECRET="${LAUNCHER_HMAC_SECRET}") diff --git a/launchers/win32/LauncherApp.cpp b/launchers/win32/LauncherApp.cpp index a8c69de166..4ed20fea13 100644 --- a/launchers/win32/LauncherApp.cpp +++ b/launchers/win32/LauncherApp.cpp @@ -20,7 +20,7 @@ // CLauncherApp BEGIN_MESSAGE_MAP(CLauncherApp, CWinApp) - ON_COMMAND(ID_HELP, &CWinApp::OnHelp) + ON_COMMAND(ID_HELP, &CWinApp::OnHelp) END_MESSAGE_MAP() CLauncherApp::CLauncherApp(){} @@ -32,60 +32,60 @@ CLauncherApp theApp; // CLauncherApp initialization BOOL CLauncherApp::InitInstance() { - // don't launch if already running - CreateMutex(NULL, TRUE, _T("HQ_Launcher_Mutex")); - if (GetLastError() == ERROR_ALREADY_EXISTS) { - return FALSE; - } - int iNumOfArgs; - LPWSTR* pArgs = CommandLineToArgvW(GetCommandLine(), &iNumOfArgs); - if (iNumOfArgs > 1 && CString(pArgs[1]).Compare(_T("--uninstall")) == 0) { - _manager.uninstall(); - } else { - _manager.init(); - } - if (!_manager.installLauncher()) { - return FALSE; - } - installFont(IDR_FONT_REGULAR); - installFont(IDR_FONT_BOLD); - CWinApp::InitInstance(); + // don't launch if already running + CreateMutex(NULL, TRUE, _T("HQ_Launcher_Mutex")); + if (GetLastError() == ERROR_ALREADY_EXISTS) { + return FALSE; + } + int iNumOfArgs; + LPWSTR* pArgs = CommandLineToArgvW(GetCommandLine(), &iNumOfArgs); + if (iNumOfArgs > 1 && CString(pArgs[1]).Compare(_T("--uninstall")) == 0) { + _manager.uninstall(); + } else { + _manager.init(); + } + if (!_manager.installLauncher()) { + return FALSE; + } + installFont(IDR_FONT_REGULAR); + installFont(IDR_FONT_BOLD); + CWinApp::InitInstance(); - SetRegistryKey(_T("HQ High Fidelity")); + SetRegistryKey(_T("HQ High Fidelity")); - CLauncherDlg dlg; - m_pMainWnd = &dlg; - INT_PTR nResponse = dlg.DoModal(); + CLauncherDlg dlg; + m_pMainWnd = &dlg; + INT_PTR nResponse = dlg.DoModal(); #if !defined(_AFXDLL) && !defined(_AFX_NO_MFC_CONTROLS_IN_DIALOGS) - ControlBarCleanUp(); + ControlBarCleanUp(); #endif - - // Since the dialog has been closed, return FALSE so that we exit the - // application, rather than start the application's message pump. - return FALSE; + + // Since the dialog has been closed, return FALSE so that we exit the + // application, rather than start the application's message pump. + return FALSE; } BOOL CLauncherApp::installFont(int fontID) { - HINSTANCE hResInstance = AfxGetResourceHandle(); - HRSRC res = FindResource(hResInstance, - MAKEINTRESOURCE(fontID), L"BINARY"); - if (res) { - HGLOBAL mem = LoadResource(hResInstance, res); - void *data = LockResource(mem); - DWORD len = (DWORD)SizeofResource(hResInstance, res); + HINSTANCE hResInstance = AfxGetResourceHandle(); + HRSRC res = FindResource(hResInstance, + MAKEINTRESOURCE(fontID), L"BINARY"); + if (res) { + HGLOBAL mem = LoadResource(hResInstance, res); + void *data = LockResource(mem); + DWORD len = (DWORD)SizeofResource(hResInstance, res); - DWORD nFonts; - auto m_fonthandle = AddFontMemResourceEx( - data, // font resource - len, // number of bytes in font resource - NULL, // Reserved. Must be 0. - &nFonts // number of fonts installed - ); + DWORD nFonts; + auto m_fonthandle = AddFontMemResourceEx( + data, // font resource + len, // number of bytes in font resource + NULL, // Reserved. Must be 0. + &nFonts // number of fonts installed + ); - return (m_fonthandle != 0); - } - return FALSE; + return (m_fonthandle != 0); + } + return FALSE; } diff --git a/launchers/win32/LauncherApp.h b/launchers/win32/LauncherApp.h index 8232acb249..a1aa2073b7 100644 --- a/launchers/win32/LauncherApp.h +++ b/launchers/win32/LauncherApp.h @@ -11,22 +11,22 @@ #pragma once #ifndef __AFXWIN_H__ - #error "include 'stdafx.h' before including this file for PCH" + #error "include 'stdafx.h' before including this file for PCH" #endif -#include "resource.h" // main symbols +#include "resource.h" // main symbols #include "LauncherManager.h" class CLauncherApp : public CWinApp { public: - CLauncherApp(); - virtual BOOL InitInstance(); - void setDialogOnFront() { SetWindowPos(m_pMainWnd->GetSafeHwnd(), HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE); } - LauncherManager _manager; + CLauncherApp(); + virtual BOOL InitInstance(); + void setDialogOnFront() { SetWindowPos(m_pMainWnd->GetSafeHwnd(), HWND_TOPMOST, 0, 0, 0, 0, SWP_SHOWWINDOW | SWP_NOSIZE | SWP_NOMOVE); } + LauncherManager _manager; private: - BOOL installFont(int fontID); - DECLARE_MESSAGE_MAP() + BOOL installFont(int fontID); + DECLARE_MESSAGE_MAP() }; extern CLauncherApp theApp; diff --git a/launchers/win32/LauncherDlg.cpp b/launchers/win32/LauncherDlg.cpp index d2d149348e..d3cae39013 100644 --- a/launchers/win32/LauncherDlg.cpp +++ b/launchers/win32/LauncherDlg.cpp @@ -40,10 +40,10 @@ static CString TROUBLE_URL = _T("https://www.highfidelity.com/hq-support"); CLauncherDlg::CLauncherDlg(CWnd* pParent) - : CDialog(IDD_LAUNCHER_DIALOG, pParent) + : CDialog(IDD_LAUNCHER_DIALOG, pParent) { - m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); - EnableD2DSupport(); + m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); + EnableD2DSupport(); } CLauncherDlg::~CLauncherDlg() { @@ -52,109 +52,116 @@ CLauncherDlg::~CLauncherDlg() { void CLauncherDlg::DoDataExchange(CDataExchange* pDX) { - DDX_Control(pDX, IDC_BUTTON_NEXT, m_btnNext); - DDX_Control(pDX, IDC_TROUBLE_LINK, m_trouble_link); - DDX_Control(pDX, IDC_ORGNAME, m_orgname); - DDX_Control(pDX, IDC_USERNAME, m_username); - DDX_Control(pDX, IDC_PASSWORD, m_password); - CDialog::DoDataExchange(pDX); + DDX_Control(pDX, IDC_BUTTON_NEXT, m_btnNext); + DDX_Control(pDX, IDC_TROUBLE_LINK, m_trouble_link); + DDX_Control(pDX, IDC_ORGNAME, m_orgname); + DDX_Control(pDX, IDC_USERNAME, m_username); + DDX_Control(pDX, IDC_PASSWORD, m_password); + CDialog::DoDataExchange(pDX); } BEGIN_MESSAGE_MAP(CLauncherDlg, CDialog) - ON_WM_PAINT() - ON_WM_QUERYDRAGICON() - ON_WM_TIMER() - ON_EN_SETFOCUS(IDC_ORGNAME, &CLauncherDlg::OnOrgEditChangeFocus) - ON_EN_SETFOCUS(IDC_USERNAME, &CLauncherDlg::OnUserEditChangeFocus) - ON_EN_SETFOCUS(IDC_PASSWORD, &CLauncherDlg::OnPassEditChangeFocus) - ON_BN_CLICKED(IDC_BUTTON_NEXT, &CLauncherDlg::OnNextClicked) - ON_BN_CLICKED(IDC_TROUBLE_LINK, &CLauncherDlg::OnTroubleClicked) - ON_WM_CTLCOLOR() - ON_WM_DRAWITEM() - ON_WM_SETCURSOR() + ON_WM_PAINT() + ON_WM_QUERYDRAGICON() + ON_WM_TIMER() + ON_EN_SETFOCUS(IDC_ORGNAME, &CLauncherDlg::OnOrgEditChangeFocus) + ON_EN_SETFOCUS(IDC_USERNAME, &CLauncherDlg::OnUserEditChangeFocus) + ON_EN_SETFOCUS(IDC_PASSWORD, &CLauncherDlg::OnPassEditChangeFocus) + ON_BN_CLICKED(IDC_BUTTON_NEXT, &CLauncherDlg::OnNextClicked) + ON_BN_CLICKED(IDC_TROUBLE_LINK, &CLauncherDlg::OnTroubleClicked) + ON_WM_CTLCOLOR() + ON_WM_DRAWITEM() + ON_WM_SETCURSOR() END_MESSAGE_MAP() // CLauncherDlg message handlers BOOL CLauncherDlg::OnInitDialog() { - CDialog::OnInitDialog(); + CDialog::OnInitDialog(); - SetIcon(m_hIcon, TRUE); // Set big icon - SetIcon(m_hIcon, FALSE); // Set small icon + SetIcon(m_hIcon, TRUE); // Set big icon + SetIcon(m_hIcon, FALSE); // Set small icon - CFont editFont; - if (LauncherUtils::getFont(GRAPHIK_REGULAR, FIELDS_FONT_SIZE, true, editFont)) { - m_orgname.SetFont(&editFont); - m_username.SetFont(&editFont); - m_password.SetFont(&editFont); - } - CFont buttonFont; - if (LauncherUtils::getFont(_T("Graphik-Bold"), BUTTON_FONT_SIZE, true, buttonFont)) { - m_btnNext.SetFont(&editFont); - } + CFont editFont; + if (LauncherUtils::getFont(GRAPHIK_REGULAR, FIELDS_FONT_SIZE, true, editFont)) { + m_orgname.SetFont(&editFont); + m_username.SetFont(&editFont); + m_password.SetFont(&editFont); + } + CFont buttonFont; + if (LauncherUtils::getFont(_T("Graphik-Bold"), BUTTON_FONT_SIZE, true, buttonFont)) { + m_btnNext.SetFont(&editFont); + } - m_message_label = (CStatic *)GetDlgItem(IDC_MESSAGE_LABEL); - m_action_label = (CStatic *)GetDlgItem(IDC_ACTION_LABEL); - m_message2_label = (CStatic *)GetDlgItem(IDC_MESSAGE2_LABEL); - m_action2_label = (CStatic *)GetDlgItem(IDC_ACTION2_LABEL); + m_message_label = (CStatic *)GetDlgItem(IDC_MESSAGE_LABEL); + m_action_label = (CStatic *)GetDlgItem(IDC_ACTION_LABEL); + m_message2_label = (CStatic *)GetDlgItem(IDC_MESSAGE2_LABEL); + m_action2_label = (CStatic *)GetDlgItem(IDC_ACTION2_LABEL); - m_orgname_banner = (CStatic *)GetDlgItem(IDC_ORGNAME_BANNER); - m_username_banner = (CStatic *)GetDlgItem(IDC_USERNAME_BANNER); - m_password_banner = (CStatic *)GetDlgItem(IDC_PASSWORD_BANNER); + m_orgname_banner = (CStatic *)GetDlgItem(IDC_ORGNAME_BANNER); + m_username_banner = (CStatic *)GetDlgItem(IDC_USERNAME_BANNER); + m_password_banner = (CStatic *)GetDlgItem(IDC_PASSWORD_BANNER); - m_terms = (CStatic *)GetDlgItem(IDC_TERMS); - m_terms2 = (CStatic *)GetDlgItem(IDC_TERMS2); - m_trouble = (CStatic *)GetDlgItem(IDC_TROUBLE); + m_terms = (CStatic *)GetDlgItem(IDC_TERMS); + m_terms2 = (CStatic *)GetDlgItem(IDC_TERMS2); + m_trouble = (CStatic *)GetDlgItem(IDC_TROUBLE); - m_voxel = (CStatic *)GetDlgItem(IDC_VOXEL); + m_voxel = (CStatic *)GetDlgItem(IDC_VOXEL); - m_voxel->EnableD2DSupport(); + m_voxel->EnableD2DSupport(); - m_pRenderTarget = GetRenderTarget(); + m_pRenderTarget = GetRenderTarget(); - SetTimer(1, 2, NULL); - - return TRUE; + SetTimer(1, 2, NULL); + + return TRUE; } BOOL CLauncherDlg::PreTranslateMessage(MSG* pMsg) { - if ((pMsg->message == WM_KEYDOWN)) - { - if (pMsg->wParam == VK_RETURN) - { - OnNextClicked(); - return TRUE; - } - } - return CDialog::PreTranslateMessage(pMsg); + if ((pMsg->message == WM_KEYDOWN)) + { + if (pMsg->wParam == 'A' && GetKeyState(VK_CONTROL) < 0) { + CWnd* wnd = GetFocus(); + CWnd* myWnd = this->GetDlgItem(IDC_ORGNAME); + if (wnd && (wnd == this->GetDlgItem(IDC_ORGNAME) || + wnd == this->GetDlgItem(IDC_USERNAME) || + wnd == this->GetDlgItem(IDC_PASSWORD))) { + ((CEdit*)wnd)->SetSel(0, -1); + } + return TRUE; + } else if (pMsg->wParam == VK_RETURN) { + OnNextClicked(); + return TRUE; + } + } + return CDialog::PreTranslateMessage(pMsg); } void CLauncherDlg::setCustomDialog() { - - LONG lStyle = GetWindowLong(GetSafeHwnd(), GWL_STYLE); - lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); - SetWindowLong(GetSafeHwnd(), GWL_STYLE, lStyle); + + LONG lStyle = GetWindowLong(GetSafeHwnd(), GWL_STYLE); + lStyle &= ~(WS_CAPTION | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_SYSMENU); + SetWindowLong(GetSafeHwnd(), GWL_STYLE, lStyle); - LONG lExStyle = GetWindowLong(GetSafeHwnd(), GWL_EXSTYLE); - lExStyle &= ~(WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE); - SetWindowLong(GetSafeHwnd(), GWL_EXSTYLE, lExStyle); + LONG lExStyle = GetWindowLong(GetSafeHwnd(), GWL_EXSTYLE); + lExStyle &= ~(WS_EX_DLGMODALFRAME | WS_EX_CLIENTEDGE | WS_EX_STATICEDGE); + SetWindowLong(GetSafeHwnd(), GWL_EXSTYLE, lExStyle); - SetWindowPos(NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); - // theApp.setDialogOnFront(); + SetWindowPos(NULL, 0, 0, 0, 0, SWP_FRAMECHANGED | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER); } void CLauncherDlg::OnPaint() { - CPaintDC dc(this); - setCustomDialog(); - CDialog::OnPaint(); + CPaintDC dc(this); + setCustomDialog(); + CDialog::OnPaint(); } // The system calls this function to obtain the cursor to display while the user drags // the minimized window. HCURSOR CLauncherDlg::OnQueryDragIcon() { - return static_cast(m_hIcon); + return static_cast(m_hIcon); } void CLauncherDlg::startProcess() { @@ -202,15 +209,15 @@ void CLauncherDlg::startProcess() { } BOOL CLauncherDlg::getHQInfo(const CString& orgname) { - CString hash; + CString hash; CString lowerOrgName = orgname; lowerOrgName.MakeLower(); LauncherUtils::hMac256(lowerOrgName, LAUNCHER_HMAC_SECRET, hash); - return theApp._manager.readOrganizationJSON(hash) == LauncherUtils::ResponseError::NoError; + return theApp._manager.readOrganizationJSON(hash) == LauncherUtils::ResponseError::NoError; } afx_msg void CLauncherDlg::OnTroubleClicked() { - ShellExecute(0, NULL, TROUBLE_URL, NULL, NULL, SW_SHOWDEFAULT); + LauncherUtils::executeOnForeground(TROUBLE_URL, _T("")); } afx_msg void CLauncherDlg::OnNextClicked() { @@ -255,414 +262,421 @@ afx_msg void CLauncherDlg::OnNextClicked() { } void CLauncherDlg::drawBackground(CHwndRenderTarget* pRenderTarget) { - CD2DBitmap m_pBitmamBackground(pRenderTarget, IDB_PNG1, _T("PNG")); - auto size = pRenderTarget->GetSize(); - CD2DRectF backRec(0.0f, 0.0f, size.width, size.height); - pRenderTarget->DrawBitmap(&m_pBitmamBackground, backRec); + CD2DBitmap m_pBitmamBackground(pRenderTarget, IDB_PNG1, _T("PNG")); + auto size = pRenderTarget->GetSize(); + CD2DRectF backRec(0.0f, 0.0f, size.width, size.height); + pRenderTarget->DrawBitmap(&m_pBitmamBackground, backRec); } void CLauncherDlg::drawLogo(CHwndRenderTarget* pRenderTarget) { - CD2DBitmap m_pBitmamLogo(pRenderTarget, IDB_PNG2, _T("PNG")); - auto size = pRenderTarget->GetSize(); - int logoWidth = 231; - int logoHeight = 181; - float logoPosX = 0.5f * (size.width - logoWidth); - float logoPosY = 0.95f * (size.height - logoHeight); - CD2DRectF logoRec(logoPosX, logoPosY, logoPosX + logoWidth, logoPosY + logoHeight); - pRenderTarget->DrawBitmap(&m_pBitmamLogo, logoRec); + CD2DBitmap m_pBitmamLogo(pRenderTarget, IDB_PNG2, _T("PNG")); + auto size = pRenderTarget->GetSize(); + int logoWidth = 231; + int logoHeight = 181; + float logoPosX = 0.5f * (size.width - logoWidth); + float logoPosY = 0.95f * (size.height - logoHeight); + CD2DRectF logoRec(logoPosX, logoPosY, logoPosX + logoWidth, logoPosY + logoHeight); + pRenderTarget->DrawBitmap(&m_pBitmamLogo, logoRec); } void CLauncherDlg::drawSmallLogo(CHwndRenderTarget* pRenderTarget) { - CD2DBitmap m_pBitmamLogo(pRenderTarget, IDB_PNG5, _T("PNG")); - auto size = pRenderTarget->GetSize(); - int padding = 6; - int logoWidth = 100; - int logoHeight = 18; - float logoPosX = size.width - logoWidth - padding; - float logoPosY = size.height - logoHeight - padding; - CD2DRectF logoRec(logoPosX, logoPosY, logoPosX + logoWidth, logoPosY + logoHeight); - pRenderTarget->DrawBitmap(&m_pBitmamLogo, logoRec); + CD2DBitmap m_pBitmamLogo(pRenderTarget, IDB_PNG5, _T("PNG")); + auto size = pRenderTarget->GetSize(); + int padding = 6; + int logoWidth = 100; + int logoHeight = 18; + float logoPosX = size.width - logoWidth - padding; + float logoPosY = size.height - logoHeight - padding; + CD2DRectF logoRec(logoPosX, logoPosY, logoPosX + logoWidth, logoPosY + logoHeight); + pRenderTarget->DrawBitmap(&m_pBitmamLogo, logoRec); } void CLauncherDlg::drawVoxel(CHwndRenderTarget* pRenderTarget) { - CD2DBitmap m_pBitmamVoxel(pRenderTarget, IDB_PNG4, _T("PNG")); - auto size = pRenderTarget->GetSize(); - int logoWidth = 132; - int logoHeight = 134; - float voxelPosX = 0.5f * (size.width - logoWidth); - float voxelPosY = 0.5f * (size.height - logoHeight); - CD2DRectF voxelRec(voxelPosX, voxelPosY, voxelPosX + logoWidth, voxelPosY + logoHeight); - auto midPoint = D2D1::Point2F(0.5f * size.width, 0.5f * size.height); - _logoRotation += 2.0f; - CD2DSolidColorBrush brush(pRenderTarget, D2D1::ColorF(0.0f, 0.0f, 0.0f)); - pRenderTarget->SetTransform(D2D1::Matrix3x2F::Rotation(_logoRotation - 2.0f, midPoint)); - pRenderTarget->FillRectangle(voxelRec, &brush); - pRenderTarget->SetTransform(D2D1::Matrix3x2F::Rotation(_logoRotation, midPoint)); - pRenderTarget->DrawBitmap(&m_pBitmamVoxel, voxelRec); - pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); + CD2DBitmap m_pBitmamVoxel(pRenderTarget, IDB_PNG4, _T("PNG")); + auto size = pRenderTarget->GetSize(); + int logoWidth = 132; + int logoHeight = 134; + float voxelPosX = 0.5f * (size.width - logoWidth); + float voxelPosY = 0.5f * (size.height - logoHeight); + CD2DRectF voxelRec(voxelPosX, voxelPosY, voxelPosX + logoWidth, voxelPosY + logoHeight); + auto midPoint = D2D1::Point2F(0.5f * size.width, 0.5f * size.height); + _logoRotation += 2.0f; + CD2DSolidColorBrush brush(pRenderTarget, D2D1::ColorF(0.0f, 0.0f, 0.0f)); + pRenderTarget->SetTransform(D2D1::Matrix3x2F::Rotation(_logoRotation - 2.0f, midPoint)); + pRenderTarget->FillRectangle(voxelRec, &brush); + pRenderTarget->SetTransform(D2D1::Matrix3x2F::Rotation(_logoRotation, midPoint)); + pRenderTarget->DrawBitmap(&m_pBitmamVoxel, voxelRec); + pRenderTarget->SetTransform(D2D1::Matrix3x2F::Identity()); } void CLauncherDlg::showWindows(std::vector windows, bool show) { - for (auto window : windows) { - window->ShowWindow(show ? SW_SHOW : SW_HIDE); - } + for (auto window : windows) { + window->ShowWindow(show ? SW_SHOW : SW_HIDE); + } } void CLauncherDlg::prepareLogin(DrawStep step) { - m_voxel->ShowWindow(SW_HIDE); - m_orgname_banner->SetWindowTextW(_T("Organization Name")); - m_username_banner->SetWindowTextW(_T("Username")); - m_password_banner->SetWindowTextW(_T("Password")); - CString editText; - m_orgname.GetWindowTextW(editText); - m_orgname_banner->ShowWindow(editText.GetLength() == 0 ? SW_SHOW : SW_HIDE); - m_username.GetWindowTextW(editText); - m_username_banner->ShowWindow(editText.GetLength() == 0 ? SW_SHOW : SW_HIDE); - m_password.GetWindowTextW(editText); - m_password_banner->ShowWindow(editText.GetLength() == 0 ? SW_SHOW : SW_HIDE); - m_orgname.ShowWindow(SW_SHOW); - m_username.ShowWindow(SW_SHOW); - m_password.ShowWindow(SW_SHOW); - CString actionText = step == DrawStep::DrawLoginLogin ? _T("Please log in") : _T("Uh-oh, we have a problem"); - CString messageText = step == DrawStep::DrawLoginLogin ? _T("Be sure you've uploaded your Avatar before signing in.") : - step == DrawStep::DrawLoginErrorCred ? _T("There is a problem with your credentials\n please try again.") : _T("There is a problem with your Organization name\n please try again."); - m_action_label->SetWindowTextW(actionText); - m_message_label->SetWindowTextW(messageText); - m_action_label->ShowWindow(SW_SHOW); - m_message_label->ShowWindow(SW_SHOW); - m_btnNext.ShowWindow(SW_SHOW); - m_trouble->SetWindowTextW(_T("Having Trouble?")); - m_trouble->ShowWindow(SW_SHOW); - m_trouble_link.ShowWindow(SW_SHOW); - + m_voxel->ShowWindow(SW_HIDE); + m_orgname_banner->SetWindowTextW(_T("Organization Name")); + m_username_banner->SetWindowTextW(_T("Username")); + m_password_banner->SetWindowTextW(_T("Password")); + CString editText; + m_orgname.GetWindowTextW(editText); + m_orgname_banner->ShowWindow(editText.GetLength() == 0 ? SW_SHOW : SW_HIDE); + m_username.GetWindowTextW(editText); + m_username_banner->ShowWindow(editText.GetLength() == 0 ? SW_SHOW : SW_HIDE); + m_password.GetWindowTextW(editText); + m_password_banner->ShowWindow(editText.GetLength() == 0 ? SW_SHOW : SW_HIDE); + m_orgname.ShowWindow(SW_SHOW); + m_username.ShowWindow(SW_SHOW); + m_password.ShowWindow(SW_SHOW); + CString actionText = step == DrawStep::DrawLoginLogin ? _T("Please log in") : _T("Uh-oh, we have a problem"); + CString messageText = step == DrawStep::DrawLoginLogin ? _T("Be sure you've uploaded your Avatar before signing in.") : + step == DrawStep::DrawLoginErrorCred ? _T("There is a problem with your credentials\n please try again.") : _T("There is a problem with your Organization name\n please try again."); + m_action_label->SetWindowTextW(actionText); + m_message_label->SetWindowTextW(messageText); + m_action_label->ShowWindow(SW_SHOW); + m_message_label->ShowWindow(SW_SHOW); + m_btnNext.ShowWindow(SW_SHOW); + m_trouble->SetWindowTextW(_T("Having Trouble?")); + m_trouble->ShowWindow(SW_SHOW); + m_trouble_link.ShowWindow(SW_SHOW); + } void CLauncherDlg::prepareChoose() { - m_orgname.ShowWindow(SW_HIDE); - m_username.SetWindowTextW(_T("")); - m_username_banner->SetWindowTextW(_T("Display Name")); - CString editText; - m_username.GetWindowTextW(editText); - m_username_banner->ShowWindow(editText.GetLength() == 0 ? SW_SHOW : SW_HIDE); - m_password.ShowWindow(SW_HIDE); - m_orgname_banner->ShowWindow(SW_HIDE); - m_password_banner->ShowWindow(SW_HIDE); - m_action_label->SetWindowTextW(_T("Choose a display name")); - m_message_label->SetWindowTextW(_T("This is the name that your teammates will see.")); - m_terms->ShowWindow(SW_SHOW); - m_terms2->ShowWindow(SW_SHOW); - m_terms->SetWindowTextW(_T("By signing in, you agree to the High Fidelity")); - m_terms2->SetWindowTextW(_T("Terms of Service")); - CRect rec; - m_btnNext.GetWindowRect(&rec); - ScreenToClient(&rec); - if (rec.top > 281) { - rec.bottom -= 35; - rec.top -= 35; - m_btnNext.MoveWindow(rec, FALSE); - } - m_btnNext.ShowWindow(SW_SHOW); + m_orgname.ShowWindow(SW_HIDE); + m_username.SetWindowTextW(_T("")); + m_username_banner->SetWindowTextW(_T("Display Name")); + CString editText; + m_username.GetWindowTextW(editText); + m_username_banner->ShowWindow(editText.GetLength() == 0 ? SW_SHOW : SW_HIDE); + m_password.ShowWindow(SW_HIDE); + m_orgname_banner->ShowWindow(SW_HIDE); + m_password_banner->ShowWindow(SW_HIDE); + m_action_label->SetWindowTextW(_T("Choose a display name")); + m_message_label->SetWindowTextW(_T("This is the name that your teammates will see.")); + m_terms->ShowWindow(SW_SHOW); + m_terms2->ShowWindow(SW_SHOW); + m_terms->SetWindowTextW(_T("By signing in, you agree to the High Fidelity")); + m_terms2->SetWindowTextW(_T("Terms of Service")); + CRect rec; + m_btnNext.GetWindowRect(&rec); + ScreenToClient(&rec); + if (rec.top > 281) { + rec.bottom -= 35; + rec.top -= 35; + m_btnNext.MoveWindow(rec, FALSE); + } + m_btnNext.ShowWindow(SW_SHOW); } void CLauncherDlg::prepareProcess(DrawStep step) { - m_trouble->ShowWindow(SW_HIDE); - m_trouble_link.ShowWindow(SW_HIDE); - m_terms->ShowWindow(SW_HIDE); - m_terms2->ShowWindow(SW_HIDE); - m_orgname_banner->ShowWindow(SW_HIDE); - m_username_banner->ShowWindow(SW_HIDE); - m_password_banner->ShowWindow(SW_HIDE); - m_orgname.ShowWindow(SW_HIDE); - m_username.ShowWindow(SW_HIDE); - m_password.ShowWindow(SW_HIDE); - m_action_label->SetWindowTextW(_T("")); - m_message_label->SetWindowTextW(_T("")); - m_btnNext.ShowWindow(SW_HIDE); - m_action_label->ShowWindow(SW_HIDE); - m_message_label->ShowWindow(SW_HIDE); - m_voxel->ShowWindow(SW_SHOW); - CString actionText = _T(""); - CString messageText = _T(""); - switch (step) { - case DrawStep::DrawProcessSetup: - actionText = _T("We're building your virtual HQ"); - messageText = _T("Set up may take several minutes."); - break; - case DrawStep::DrawProcessUpdate: - actionText = _T("Getting updates..."); - messageText = _T("We're getting the latest and greatest for you, one sec."); - break; - case DrawStep::DrawProcessFinishHq: - actionText = _T("Your new HQ is all setup"); - messageText = _T("Thanks for being patient."); - break; - case DrawStep::DrawProcessFinishUpdate: - actionText = _T("You're good to go!"); - messageText = _T("Thanks for being patient."); - break; - case DrawStep::DrawProcessUninstall: - actionText = _T("Uninstalling..."); - messageText = _T("It'll take one sec."); - break; - } - m_action2_label->SetWindowTextW(actionText); - m_message2_label->SetWindowTextW(messageText); - m_action2_label->ShowWindow(SW_SHOW); - m_message2_label->ShowWindow(SW_SHOW); + m_trouble->ShowWindow(SW_HIDE); + m_trouble_link.ShowWindow(SW_HIDE); + m_terms->ShowWindow(SW_HIDE); + m_terms2->ShowWindow(SW_HIDE); + m_orgname_banner->ShowWindow(SW_HIDE); + m_username_banner->ShowWindow(SW_HIDE); + m_password_banner->ShowWindow(SW_HIDE); + m_orgname.ShowWindow(SW_HIDE); + m_username.ShowWindow(SW_HIDE); + m_password.ShowWindow(SW_HIDE); + m_action_label->SetWindowTextW(_T("")); + m_message_label->SetWindowTextW(_T("")); + m_btnNext.ShowWindow(SW_HIDE); + m_action_label->ShowWindow(SW_HIDE); + m_message_label->ShowWindow(SW_HIDE); + m_voxel->ShowWindow(SW_SHOW); + CString actionText = _T(""); + CString messageText = _T(""); + switch (step) { + case DrawStep::DrawProcessSetup: + actionText = _T("We're building your virtual HQ"); + messageText = _T("Set up may take several minutes."); + break; + case DrawStep::DrawProcessUpdate: + actionText = _T("Getting updates..."); + messageText = _T("We're getting the latest and greatest for you, one sec."); + break; + case DrawStep::DrawProcessFinishHq: + actionText = _T("Your new HQ is all setup"); + messageText = _T("Thanks for being patient."); + break; + case DrawStep::DrawProcessFinishUpdate: + actionText = _T("You're good to go!"); + messageText = _T("Thanks for being patient."); + break; + case DrawStep::DrawProcessUninstall: + actionText = _T("Uninstalling..."); + messageText = _T("It'll take one sec."); + break; + } + m_action2_label->SetWindowTextW(actionText); + m_message2_label->SetWindowTextW(messageText); + m_action2_label->ShowWindow(SW_SHOW); + m_message2_label->ShowWindow(SW_SHOW); } void CLauncherDlg::prepareError() { } BOOL CLauncherDlg::getTextFormat(int resID, TextFormat& formatOut) { - // Set default values for message - BOOL isText = TRUE; - formatOut.color = COLOR_LIGHT_GREY; - formatOut.isBold = false; - formatOut.isButton = false; - formatOut.size = MESSAGE_FONT_SIZE; - formatOut.underlined = false; - - switch (resID) { - case IDC_VOXEL: - case IDD_LAUNCHER_DIALOG: - isText = FALSE; - case IDC_MESSAGE_LABEL: - case IDC_MESSAGE2_LABEL: - // Default values - break; - case IDC_ACTION_LABEL: - case IDC_ACTION2_LABEL: - formatOut.size = ACTION_FONT_SIZE; - formatOut.isBold = true; - formatOut.color = COLOR_LIGHTER_GREY; - break; - case IDC_USERNAME: - case IDC_PASSWORD: - case IDC_ORGNAME: - formatOut.color = COLOR_WHITE; - formatOut.size = FIELDS_FONT_SIZE; - formatOut.underlined = true; - break; - case IDC_USERNAME_BANNER: - case IDC_PASSWORD_BANNER: - case IDC_ORGNAME_BANNER: - formatOut.size = FIELDS_FONT_SIZE; - formatOut.color = COLOR_GREY; - break; - case IDC_TERMS: - formatOut.size = TERMS_FONT_SIZE; - break; - case IDC_TERMS2: - formatOut.size = TERMS_FONT_SIZE; - formatOut.isBold = true; - break; - case IDC_TROUBLE: - formatOut.size = TROUBLE_FONT_SIZE; - formatOut.color = COLOR_BLUE; - break; - } - return isText; + // Set default values for message + BOOL isText = TRUE; + formatOut.color = COLOR_LIGHT_GREY; + formatOut.isBold = false; + formatOut.isButton = false; + formatOut.size = MESSAGE_FONT_SIZE; + formatOut.underlined = false; + + switch (resID) { + case IDC_VOXEL: + case IDD_LAUNCHER_DIALOG: + isText = FALSE; + case IDC_MESSAGE_LABEL: + case IDC_MESSAGE2_LABEL: + // Default values + break; + case IDC_ACTION_LABEL: + case IDC_ACTION2_LABEL: + formatOut.size = ACTION_FONT_SIZE; + formatOut.isBold = true; + formatOut.color = COLOR_LIGHTER_GREY; + break; + case IDC_USERNAME: + case IDC_PASSWORD: + case IDC_ORGNAME: + formatOut.color = COLOR_WHITE; + formatOut.size = FIELDS_FONT_SIZE; + formatOut.underlined = true; + break; + case IDC_USERNAME_BANNER: + case IDC_PASSWORD_BANNER: + case IDC_ORGNAME_BANNER: + formatOut.size = FIELDS_FONT_SIZE; + formatOut.color = COLOR_GREY; + break; + case IDC_TERMS: + formatOut.size = TERMS_FONT_SIZE; + break; + case IDC_TERMS2: + formatOut.size = TERMS_FONT_SIZE; + formatOut.isBold = true; + break; + case IDC_TROUBLE: + formatOut.size = TROUBLE_FONT_SIZE; + formatOut.color = COLOR_BLUE; + break; + } + return isText; } HBRUSH CLauncherDlg::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) { - HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); - TextFormat textFormat; - int resId = pWnd->GetDlgCtrlID(); - if (getTextFormat(resId, textFormat)) { - pDC->SetTextColor(textFormat.color); - pDC->SetBkMode(TRANSPARENT); - CFont textFont; - CString fontFamily = textFormat.isBold ? GRAPHIK_SEMIBOLD : GRAPHIK_REGULAR; - if (LauncherUtils::getFont(fontFamily, textFormat.size, textFormat.isBold, textFont)) { - pDC->SelectObject(&textFont); - } - if (textFormat.underlined) { - CRect rect; - pWnd->GetClientRect(&rect); - int borderThick = 1; - int padding = 4; - CRect lineRect = CRect(rect.left + padding, rect.bottom, rect.right - padding, rect.bottom + borderThick); - lineRect.MoveToY(lineRect.bottom + 1); - pDC->FillSolidRect(lineRect, COLOR_GREY); - } - } - return (HBRUSH)GetStockObject(BLACK_BRUSH); + HBRUSH hbr = CDialog::OnCtlColor(pDC, pWnd, nCtlColor); + TextFormat textFormat; + int resId = pWnd->GetDlgCtrlID(); + if (getTextFormat(resId, textFormat)) { + pDC->SetTextColor(textFormat.color); + pDC->SetBkMode(TRANSPARENT); + CFont textFont; + CString fontFamily = textFormat.isBold ? GRAPHIK_SEMIBOLD : GRAPHIK_REGULAR; + if (LauncherUtils::getFont(fontFamily, textFormat.size, textFormat.isBold, textFont)) { + pDC->SelectObject(&textFont); + } + if (textFormat.underlined) { + CRect rect; + pWnd->GetClientRect(&rect); + int borderThick = 1; + int padding = 4; + CRect lineRect = CRect(rect.left + padding, rect.bottom, rect.right - padding, rect.bottom + borderThick); + lineRect.MoveToY(lineRect.bottom + 1); + pDC->FillSolidRect(lineRect, COLOR_GREY); + } + } + return (HBRUSH)GetStockObject(BLACK_BRUSH); } void CLauncherDlg::OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct) { - CDC dc; - dc.Attach(lpDrawItemStruct->hDC); - CRect rect = lpDrawItemStruct->rcItem; - CRect defrect = rect; - CString btnName = _T(""); - int xpan = 0; - if (nIDCtl == IDC_BUTTON_NEXT) { - if (_drawStep == DrawStep::DrawChoose || _drawStep == DrawStep::DrawLoginLogin) { - btnName += _drawStep == DrawStep::DrawLoginLogin ? _T("NEXT") : _T("LOG IN"); - int xpan = -20; - defrect = CRect(rect.left - xpan, rect.top, rect.right + xpan, rect.bottom); - } else { - btnName += _T("TRY AGAIN"); - } - int borderThick = 2; - dc.FillSolidRect(rect, COLOR_BLACK); - dc.FillSolidRect(defrect, COLOR_WHITE); - defrect.DeflateRect(borderThick, borderThick, borderThick, borderThick); - dc.FillSolidRect(defrect, COLOR_BLACK); - UINT state = lpDrawItemStruct->itemState; - dc.SetTextColor(COLOR_WHITE); + CDC dc; + dc.Attach(lpDrawItemStruct->hDC); + CRect rect = lpDrawItemStruct->rcItem; + CRect defrect = rect; + CString btnName = _T(""); + int xpan = 0; + if (nIDCtl == IDC_BUTTON_NEXT) { + if (_drawStep == DrawStep::DrawChoose || _drawStep == DrawStep::DrawLoginLogin) { + btnName += _drawStep == DrawStep::DrawLoginLogin ? _T("NEXT") : _T("LOG IN"); + int xpan = -20; + defrect = CRect(rect.left - xpan, rect.top, rect.right + xpan, rect.bottom); + } else { + btnName += _T("TRY AGAIN"); + } + int borderThick = 2; + dc.FillSolidRect(rect, COLOR_BLACK); + dc.FillSolidRect(defrect, COLOR_WHITE); + defrect.DeflateRect(borderThick, borderThick, borderThick, borderThick); + dc.FillSolidRect(defrect, COLOR_BLACK); + UINT state = lpDrawItemStruct->itemState; + dc.SetTextColor(COLOR_WHITE); - CFont buttonFont; - if (LauncherUtils::getFont(GRAPHIK_SEMIBOLD, BUTTON_FONT_SIZE, true, buttonFont)) { - dc.SelectObject(buttonFont); - } - dc.DrawText(btnName, CRect(rect.left, rect.top + 4, rect.right, rect.bottom - 8), DT_CENTER | DT_VCENTER | DT_SINGLELINE); - dc.Detach(); - } else if (nIDCtl == IDC_TROUBLE_LINK) { - dc.FillSolidRect(rect, COLOR_BLACK); - dc.SetTextColor(COLOR_BLUE); - CFont buttonFont; - if (LauncherUtils::getFont(GRAPHIK_SEMIBOLD, TROUBLE_FONT_SIZE, true, buttonFont)) { - dc.SelectObject(buttonFont); - } - dc.DrawText(_T("Having Trouble"), CRect(rect.left, rect.top, rect.right, rect.bottom), DT_CENTER | DT_VCENTER | DT_SINGLELINE); - } + CFont buttonFont; + if (LauncherUtils::getFont(GRAPHIK_SEMIBOLD, BUTTON_FONT_SIZE, true, buttonFont)) { + dc.SelectObject(buttonFont); + } + dc.DrawText(btnName, CRect(rect.left, rect.top + 4, rect.right, rect.bottom - 8), DT_CENTER | DT_VCENTER | DT_SINGLELINE); + dc.Detach(); + } else if (nIDCtl == IDC_TROUBLE_LINK) { + dc.FillSolidRect(rect, COLOR_BLACK); + dc.SetTextColor(COLOR_BLUE); + CFont buttonFont; + if (LauncherUtils::getFont(GRAPHIK_SEMIBOLD, TROUBLE_FONT_SIZE, true, buttonFont)) { + dc.SelectObject(buttonFont); + } + dc.DrawText(_T("Having Trouble"), CRect(rect.left, rect.top, rect.right, rect.bottom), DT_CENTER | DT_VCENTER | DT_SINGLELINE); + } } void CLauncherDlg::redrawBanner(const CEdit& edit, CStatic* banner) { - CString editText; - edit.GetWindowTextW(editText); - if (editText.GetLength() == 0) { - banner->Invalidate(); - } + CString editText; + edit.GetWindowTextW(editText); + if (editText.GetLength() == 0) { + banner->Invalidate(); + } } void CLauncherDlg::OnOrgEditChangeFocus() { - redrawBanner(m_username, m_username_banner); - redrawBanner(m_password, m_password_banner); + redrawBanner(m_username, m_username_banner); + redrawBanner(m_password, m_password_banner); } void CLauncherDlg::OnUserEditChangeFocus() { - redrawBanner(m_orgname, m_orgname_banner); - redrawBanner(m_password, m_password_banner); + redrawBanner(m_orgname, m_orgname_banner); + redrawBanner(m_password, m_password_banner); } void CLauncherDlg::OnPassEditChangeFocus() { - redrawBanner(m_orgname, m_orgname_banner); - redrawBanner(m_username, m_username_banner); + redrawBanner(m_orgname, m_orgname_banner); + redrawBanner(m_username, m_username_banner); } BOOL CLauncherDlg::OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message) { - if (pWnd->IsKindOf(RUNTIME_CLASS(CButton))) { - ::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_HAND)); - return TRUE; - } - return CDialog::OnSetCursor(pWnd, nHitTest, message); + if (pWnd->IsKindOf(RUNTIME_CLASS(CButton))) { + ::SetCursor(AfxGetApp()->LoadStandardCursor(IDC_HAND)); + return TRUE; + } + return CDialog::OnSetCursor(pWnd, nHitTest, message); } void CLauncherDlg::OnTimer(UINT_PTR nIDEvent) { - const int CONSOLE_MAX_SHUTDOWN_TRY_COUNT = 10; - const int CONSOLE_DELTATIME_BETWEEN_TRYS = 10; - if (_drawStep == DrawStep::DrawProcessSetup || - _drawStep == DrawStep::DrawProcessUpdate || - _drawStep == DrawStep::DrawProcessUninstall) { - // Refresh - setDrawDialog(_drawStep, true); - } - if (_showSplash) { - if (_splashStep == 0){ + const int CONSOLE_MAX_SHUTDOWN_TRY_COUNT = 10; + const int CONSOLE_DELTATIME_BETWEEN_TRYS = 10; + if (_drawStep == DrawStep::DrawProcessSetup || + _drawStep == DrawStep::DrawProcessUpdate || + _drawStep == DrawStep::DrawProcessUninstall) { + // Refresh + setDrawDialog(_drawStep, true); + } + if (_showSplash) { + if (_splashStep == 0){ if (theApp._manager.needsUninstall()) { theApp._manager.addToLog(_T("Waiting to uninstall")); setDrawDialog(DrawStep::DrawProcessUninstall); } else { theApp._manager.addToLog(_T("Start splash screen")); setDrawDialog(DrawStep::DrawLogo); - } - } else if (_splashStep > 100) { - _showSplash = false; - if (theApp._manager.shouldShutDown()) { - if (LauncherUtils::IsProcessRunning(L"interface.exe")) { - exit(0); - } - } else if (theApp._manager.needsUpdate()) { - startProcess(); - } else if (theApp._manager.needsUninstall()) { - theApp._manager.uninstallApplication(); - exit(0); - } else { + } + } else if (_splashStep > 100) { + _showSplash = false; + if (theApp._manager.shouldShutDown()) { + if (_applicationWND != NULL) { + ::SetForegroundWindow(_applicationWND); + ::SetActiveWindow(_applicationWND); + } + if (LauncherUtils::IsProcessRunning(L"interface.exe")) { + exit(0); + } + } else if (theApp._manager.needsUpdate()) { + startProcess(); + } else if (theApp._manager.needsUninstall()) { + theApp._manager.uninstallApplication(); + exit(0); + } else { theApp._manager.addToLog(_T("Starting login")); - setDrawDialog(DrawStep::DrawLoginLogin); - } - } - _splashStep++; - } else if (theApp._manager.shouldShutDown()) { - if (LauncherUtils::IsProcessRunning(L"interface.exe")) { - exit(0); - } - } + setDrawDialog(DrawStep::DrawLoginLogin); + } + } + _splashStep++; + } else if (theApp._manager.shouldShutDown()) { + if (LauncherUtils::IsProcessRunning(L"interface.exe")) { + exit(0); + } + } + if (theApp._manager.shouldLaunch()) { + _applicationWND = theApp._manager.launchApplication(); + } } void CLauncherDlg::setDrawDialog(DrawStep step, BOOL isUpdate) { - _drawStep = step; - auto m_pRenderTarget = GetRenderTarget(); - auto m_voxelRenderTarget = m_voxel->GetRenderTarget(); - switch (_drawStep) { - case DrawStep::DrawLogo: - m_pRenderTarget->BeginDraw(); - drawBackground(m_pRenderTarget); - m_pRenderTarget->EndDraw(); - m_voxelRenderTarget->BeginDraw(); - drawLogo(m_voxelRenderTarget); - m_voxelRenderTarget->EndDraw(); - break; - case DrawStep::DrawLoginLogin: - case DrawStep::DrawLoginErrorOrg: - case DrawStep::DrawLoginErrorCred: - prepareLogin(_drawStep); - m_pRenderTarget->BeginDraw(); - drawBackground(m_pRenderTarget); - drawSmallLogo(m_pRenderTarget); - m_pRenderTarget->EndDraw(); - RedrawWindow(); - break; - case DrawStep::DrawChoose: - prepareChoose(); - m_pRenderTarget->BeginDraw(); - drawBackground(m_pRenderTarget); - drawSmallLogo(m_pRenderTarget); - m_pRenderTarget->EndDraw(); - RedrawWindow(); - break; - case DrawStep::DrawProcessFinishHq: - case DrawStep::DrawProcessFinishUpdate: - case DrawStep::DrawProcessUpdate: - case DrawStep::DrawProcessUninstall: - case DrawStep::DrawProcessSetup: - if (!isUpdate) { - m_voxelRenderTarget->BeginDraw(); - m_voxelRenderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 1.0f)); - m_voxelRenderTarget->EndDraw(); - m_pRenderTarget->BeginDraw(); - prepareProcess(_drawStep); - drawBackground(m_pRenderTarget); - drawSmallLogo(m_pRenderTarget); - m_pRenderTarget->EndDraw(); - RedrawWindow(); - } - m_voxelRenderTarget->BeginDraw(); - drawVoxel(m_voxelRenderTarget); - m_voxelRenderTarget->EndDraw(); - break; - default: - break; - } + _drawStep = step; + auto m_pRenderTarget = GetRenderTarget(); + auto m_voxelRenderTarget = m_voxel->GetRenderTarget(); + switch (_drawStep) { + case DrawStep::DrawLogo: + m_pRenderTarget->BeginDraw(); + drawBackground(m_pRenderTarget); + m_pRenderTarget->EndDraw(); + m_voxelRenderTarget->BeginDraw(); + drawLogo(m_voxelRenderTarget); + m_voxelRenderTarget->EndDraw(); + break; + case DrawStep::DrawLoginLogin: + case DrawStep::DrawLoginErrorOrg: + case DrawStep::DrawLoginErrorCred: + prepareLogin(_drawStep); + m_pRenderTarget->BeginDraw(); + drawBackground(m_pRenderTarget); + drawSmallLogo(m_pRenderTarget); + m_pRenderTarget->EndDraw(); + RedrawWindow(); + break; + case DrawStep::DrawChoose: + prepareChoose(); + m_pRenderTarget->BeginDraw(); + drawBackground(m_pRenderTarget); + drawSmallLogo(m_pRenderTarget); + m_pRenderTarget->EndDraw(); + RedrawWindow(); + break; + case DrawStep::DrawProcessFinishHq: + case DrawStep::DrawProcessFinishUpdate: + case DrawStep::DrawProcessUpdate: + case DrawStep::DrawProcessUninstall: + case DrawStep::DrawProcessSetup: + if (!isUpdate) { + m_voxelRenderTarget->BeginDraw(); + m_voxelRenderTarget->Clear(D2D1::ColorF(0.0f, 0.0f, 0.0f, 1.0f)); + m_voxelRenderTarget->EndDraw(); + m_pRenderTarget->BeginDraw(); + prepareProcess(_drawStep); + drawBackground(m_pRenderTarget); + drawSmallLogo(m_pRenderTarget); + m_pRenderTarget->EndDraw(); + RedrawWindow(); + } + m_voxelRenderTarget->BeginDraw(); + drawVoxel(m_voxelRenderTarget); + m_voxelRenderTarget->EndDraw(); + break; + default: + break; + } } diff --git a/launchers/win32/LauncherDlg.h b/launchers/win32/LauncherDlg.h index d743dd31d2..beaaedcdb6 100644 --- a/launchers/win32/LauncherDlg.h +++ b/launchers/win32/LauncherDlg.h @@ -13,109 +13,111 @@ // CLauncherDlg dialog class CLauncherDlg : public CDialog { - // Construction + // Construction public: - enum DrawStep { - DrawLogo = 0, - DrawLoginLogin, - DrawLoginErrorOrg, - DrawLoginErrorCred, - DrawChoose, - DrawProcessSetup, - DrawProcessUpdate, - DrawProcessFinishHq, - DrawProcessFinishUpdate, - DrawProcessUninstall, - DrawError - }; + enum DrawStep { + DrawLogo = 0, + DrawLoginLogin, + DrawLoginErrorOrg, + DrawLoginErrorCred, + DrawChoose, + DrawProcessSetup, + DrawProcessUpdate, + DrawProcessFinishHq, + DrawProcessFinishUpdate, + DrawProcessUninstall, + DrawError + }; - struct TextFormat { - int size; - COLORREF color; - bool isButton; - bool isBold; - bool underlined; - }; + struct TextFormat { + int size; + COLORREF color; + bool isButton; + bool isBold; + bool underlined; + }; - CLauncherDlg(CWnd* pParent = nullptr); + CLauncherDlg(CWnd* pParent = nullptr); ~CLauncherDlg(); - virtual BOOL PreTranslateMessage(MSG* pMsg); + virtual BOOL PreTranslateMessage(MSG* pMsg); - void setDrawDialog(DrawStep step, BOOL isUpdate = FALSE); + void setDrawDialog(DrawStep step, BOOL isUpdate = FALSE); // Dialog Data #ifdef AFX_DESIGN_TIME - enum { IDD = IDD_LAUNCHER_DIALOG }; + enum { IDD = IDD_LAUNCHER_DIALOG }; #endif - protected: - virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support - void startProcess(); - void setCustomDialog(); + protected: + virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support + void startProcess(); + void setCustomDialog(); // Implementation protected: - BOOL getHQInfo(const CString& orgname); - DrawStep _drawStep { DrawStep::DrawLogo }; - BOOL getTextFormat(int ResID, TextFormat& formatOut); - void showWindows(std::vector windows, bool show); + BOOL getHQInfo(const CString& orgname); + DrawStep _drawStep { DrawStep::DrawLogo }; + BOOL getTextFormat(int ResID, TextFormat& formatOut); + void showWindows(std::vector windows, bool show); - bool _isConsoleRunning{ false }; - bool _isInstalling{ false }; - bool _isFirstDraw{ false }; - bool _showSplash{ true }; - int _splashStep{ 0 }; - float _logoRotation { 0.0f }; + bool _isConsoleRunning{ false }; + bool _isInstalling{ false }; + bool _isFirstDraw{ false }; + bool _showSplash{ true }; + int _splashStep{ 0 }; + float _logoRotation { 0.0f }; - HICON m_hIcon; - CButton m_btnNext; - CButton m_trouble_link; - - CStatic* m_message_label; - CStatic* m_action_label; - CStatic* m_message2_label; - CStatic* m_action2_label; - CStatic* m_terms; - CStatic* m_terms2; - CStatic* m_trouble; - CStatic* m_voxel; + HICON m_hIcon; + CButton m_btnNext; + CButton m_trouble_link; + + CStatic* m_message_label; + CStatic* m_action_label; + CStatic* m_message2_label; + CStatic* m_action2_label; + CStatic* m_terms; + CStatic* m_terms2; + CStatic* m_trouble; + CStatic* m_voxel; - CEdit m_orgname; - CEdit m_username; - CEdit m_password; + CEdit m_orgname; + CEdit m_username; + CEdit m_password; - CStatic* m_orgname_banner; - CStatic* m_username_banner; - CStatic* m_password_banner; + CStatic* m_orgname_banner; + CStatic* m_username_banner; + CStatic* m_password_banner; - void drawBackground(CHwndRenderTarget* pRenderTarget); - void drawLogo(CHwndRenderTarget* pRenderTarget); - void drawSmallLogo(CHwndRenderTarget* pRenderTarget); - void drawVoxel(CHwndRenderTarget* pRenderTarget); + HWND _applicationWND { 0 }; - void prepareLogin(DrawStep step); - void prepareProcess(DrawStep step); - void prepareChoose(); - void prepareError(); + void drawBackground(CHwndRenderTarget* pRenderTarget); + void drawLogo(CHwndRenderTarget* pRenderTarget); + void drawSmallLogo(CHwndRenderTarget* pRenderTarget); + void drawVoxel(CHwndRenderTarget* pRenderTarget); - void redrawBanner(const CEdit& edit, CStatic* banner); + void prepareLogin(DrawStep step); + void prepareProcess(DrawStep step); + void prepareChoose(); + void prepareError(); - // Generated message map functions - virtual BOOL OnInitDialog(); - afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor); - afx_msg void OnPaint(); - afx_msg HCURSOR OnQueryDragIcon(); - afx_msg void OnNextClicked(); - afx_msg void OnTroubleClicked(); - afx_msg void OnOrgEditChangeFocus(); - afx_msg void OnUserEditChangeFocus(); - afx_msg void OnPassEditChangeFocus(); - afx_msg void OnTimer(UINT_PTR nIDEvent); - DECLARE_MESSAGE_MAP() + void redrawBanner(const CEdit& edit, CStatic* banner); + + // Generated message map functions + virtual BOOL OnInitDialog(); + afx_msg HBRUSH OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor); + afx_msg void OnPaint(); + afx_msg HCURSOR OnQueryDragIcon(); + afx_msg void OnNextClicked(); + afx_msg void OnTroubleClicked(); + afx_msg void OnOrgEditChangeFocus(); + afx_msg void OnUserEditChangeFocus(); + afx_msg void OnPassEditChangeFocus(); + afx_msg void OnTimer(UINT_PTR nIDEvent); + DECLARE_MESSAGE_MAP() public: - afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct); - afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); + afx_msg void OnDrawItem(int nIDCtl, LPDRAWITEMSTRUCT lpDrawItemStruct); + afx_msg BOOL OnSetCursor(CWnd* pWnd, UINT nHitTest, UINT message); }; diff --git a/launchers/win32/LauncherManager.cpp b/launchers/win32/LauncherManager.cpp index 3dc7ddedcc..fc79287457 100644 --- a/launchers/win32/LauncherManager.cpp +++ b/launchers/win32/LauncherManager.cpp @@ -34,7 +34,7 @@ void LauncherManager::init() { addToLog(_T("Installed version: ") + currentVersion); if (_latestVersion.Compare(currentVersion) == 0) { addToLog(_T("Already running most recent build. Launching interface.exe")); - launchApplication(); + _shouldLaunch = TRUE; _shouldShutdown = TRUE; } else { addToLog(_T("New build found. Updating")); @@ -76,396 +76,405 @@ void LauncherManager::closeLog() { BOOL LauncherManager::installLauncher() { addToLog(_T("Installing Launcher.")); - CString appPath; - BOOL result = getAndCreatePaths(PathType::Running_Path, appPath); - if (!result) { - MessageBox(NULL, L"Error getting app directory", L"Path Error", MB_OK | MB_ICONERROR); - } - CString installDirectory; - CString appDirectory = appPath.Left(appPath.ReverseFind('\\') + 1); - result = getAndCreatePaths(PathType::Launcher_Directory, installDirectory); - if (!result) { - MessageBox(NULL, L"Error getting app desired directory", L"Path Error", MB_OK | MB_ICONERROR); - } + CString appPath; + BOOL result = getAndCreatePaths(PathType::Running_Path, appPath); + if (!result) { + MessageBox(NULL, L"Error getting app directory", L"Path Error", MB_OK | MB_ICONERROR); + } + CString installDirectory; + CString appDirectory = appPath.Left(appPath.ReverseFind('\\') + 1); + result = getAndCreatePaths(PathType::Launcher_Directory, installDirectory); + if (!result) { + MessageBox(NULL, L"Error getting app desired directory", L"Path Error", MB_OK | MB_ICONERROR); + } - CString instalationPath = installDirectory + LAUNCHER_EXE_FILENAME; - if (!installDirectory.Compare(appDirectory) == 0) { - if (!_shouldUninstall) { - // The installer is not running on the desired location and has to be installed - // Kill of running before self-copy - if (LauncherUtils::IsProcessRunning(LAUNCHER_EXE_FILENAME)) { - ::ShellExecute(NULL, NULL, L"taskkill", L"/F /T /IM " + LAUNCHER_EXE_FILENAME, NULL, SW_HIDE); - } - CopyFile(appPath, instalationPath, FALSE); - } - } else if (_shouldUninstall) { + CString instalationPath = installDirectory + LAUNCHER_EXE_FILENAME; + if (!installDirectory.Compare(appDirectory) == 0) { + if (!_shouldUninstall) { + // The installer is not running on the desired location and has to be installed + // Kill of running before self-copy + if (LauncherUtils::IsProcessRunning(LAUNCHER_EXE_FILENAME)) { + ShellExecute(NULL, NULL, L"taskkill", L"/F /T /IM " + LAUNCHER_EXE_FILENAME, NULL, SW_HIDE); + } + CopyFile(appPath, instalationPath, FALSE); + } + } else if (_shouldUninstall) { addToLog(_T("Launching uninstall mode.")); - CString tempPath; - if (getAndCreatePaths(PathType::Temp_Directory, tempPath)) { - tempPath += _T("\\HQ_uninstaller_tmp.exe"); - CopyFile(instalationPath, tempPath, false); - LauncherUtils::launchApplication(tempPath, _T(" --uninstall")); - exit(0); - } - } - return TRUE; + CString tempPath; + if (getAndCreatePaths(PathType::Temp_Directory, tempPath)) { + tempPath += _T("\\HQ_uninstaller_tmp.exe"); + CopyFile(instalationPath, tempPath, false); + LauncherUtils::launchApplication(tempPath, _T(" --uninstall")); + exit(0); + } + } + return TRUE; } BOOL LauncherManager::createShortcuts() { - CString desktopLnkPath; + CString desktopLnkPath; addToLog(_T("Creating shortcuts.")); - getAndCreatePaths(PathType::Desktop_Directory, desktopLnkPath); - desktopLnkPath += _T("\\HQ Launcher.lnk"); - CString installDir; - getAndCreatePaths(PathType::Launcher_Directory, installDir); - CString installPath = installDir + LAUNCHER_EXE_FILENAME; - if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(desktopLnkPath), _T("CLick to Setup and Launch HQ."))) { - return FALSE; - } - CString startLinkPath; - getAndCreatePaths(PathType::StartMenu_Directory, startLinkPath); - CString appStartLinkPath = startLinkPath + _T("HQ Launcher.lnk"); - CString uniStartLinkPath = startLinkPath + _T("Uninstall HQ.lnk"); - CString uniLinkPath = installDir + _T("Uninstall HQ.lnk"); - if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(appStartLinkPath), _T("CLick to Setup and Launch HQ."))) { - return FALSE; - } - if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(uniStartLinkPath), _T("CLick to Uninstall HQ."), _T("--uninstall"))) { - return FALSE; - } - if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(uniLinkPath), _T("CLick to Uninstall HQ."), _T("--uninstall"))) { - return FALSE; - } - return TRUE; + getAndCreatePaths(PathType::Desktop_Directory, desktopLnkPath); + desktopLnkPath += _T("\\HQ Launcher.lnk"); + CString installDir; + getAndCreatePaths(PathType::Launcher_Directory, installDir); + CString installPath = installDir + LAUNCHER_EXE_FILENAME; + if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(desktopLnkPath), _T("CLick to Setup and Launch HQ."))) { + return FALSE; + } + CString startLinkPath; + getAndCreatePaths(PathType::StartMenu_Directory, startLinkPath); + CString appStartLinkPath = startLinkPath + _T("HQ Launcher.lnk"); + CString uniStartLinkPath = startLinkPath + _T("Uninstall HQ.lnk"); + CString uniLinkPath = installDir + _T("Uninstall HQ.lnk"); + if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(appStartLinkPath), _T("CLick to Setup and Launch HQ."))) { + return FALSE; + } + if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(uniStartLinkPath), _T("CLick to Uninstall HQ."), _T("--uninstall"))) { + return FALSE; + } + if (!LauncherUtils::CreateLink(installPath, (LPCSTR)CStringA(uniLinkPath), _T("CLick to Uninstall HQ."), _T("--uninstall"))) { + return FALSE; + } + return TRUE; } BOOL LauncherManager::deleteShortcuts() { - CString desktopLnkPath; + CString desktopLnkPath; addToLog(_T("Deleting shortcuts.")); - getAndCreatePaths(PathType::Desktop_Directory, desktopLnkPath); - desktopLnkPath += _T("\\HQ Launcher.lnk"); - BOOL success = LauncherUtils::deleteFileOrDirectory(desktopLnkPath); - CString startLinkPath; - getAndCreatePaths(PathType::StartMenu_Directory, startLinkPath); - return success && LauncherUtils::deleteFileOrDirectory(startLinkPath); + getAndCreatePaths(PathType::Desktop_Directory, desktopLnkPath); + desktopLnkPath += _T("\\HQ Launcher.lnk"); + BOOL success = LauncherUtils::deleteFileOrDirectory(desktopLnkPath); + CString startLinkPath; + getAndCreatePaths(PathType::StartMenu_Directory, startLinkPath); + return success && LauncherUtils::deleteFileOrDirectory(startLinkPath); } BOOL LauncherManager::isApplicationInstalled(CString& version, CString& domain, - CString& content, bool& loggedIn) { - CString applicationDir; - getAndCreatePaths(PathType::Launcher_Directory, applicationDir); - CString applicationPath = applicationDir + "interface\\interface.exe"; - BOOL isApplicationInstalled = PathFileExistsW(applicationPath); - BOOL configFileExist = PathFileExistsW(applicationDir + _T("interface\\config.json")); - if (isApplicationInstalled && configFileExist) { - LauncherUtils::ResponseError status = readConfigJSON(version, domain, content, loggedIn); - return status == LauncherUtils::ResponseError::NoError; - } - return FALSE; + CString& content, bool& loggedIn) { + CString applicationDir; + getAndCreatePaths(PathType::Launcher_Directory, applicationDir); + CString applicationPath = applicationDir + "interface\\interface.exe"; + BOOL isApplicationInstalled = PathFileExistsW(applicationPath); + BOOL configFileExist = PathFileExistsW(applicationDir + _T("interface\\config.json")); + if (isApplicationInstalled && configFileExist) { + LauncherUtils::ResponseError status = readConfigJSON(version, domain, content, loggedIn); + return status == LauncherUtils::ResponseError::NoError; + } + return FALSE; } BOOL LauncherManager::getAndCreatePaths(PathType type, CString& outPath) { - if (type == PathType::Running_Path) { - char appPath[MAX_PATH]; - DWORD size = GetModuleFileNameA(NULL, appPath, MAX_PATH); - if (size) { - outPath = CString(appPath); - return TRUE; - } - } else if (type == PathType::Desktop_Directory) { - TCHAR desktopPath[MAX_PATH]; - auto success = SUCCEEDED(SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, desktopPath)); - outPath = CString(desktopPath); - return success; - } else if (type == PathType::Temp_Directory) { - TCHAR tempPath[MAX_PATH]; - auto success = GetTempPath(MAX_PATH, tempPath); - outPath = CString(tempPath); - return success; - } else { - TCHAR localDataPath[MAX_PATH]; - if (type == PathType::StartMenu_Directory) { - TCHAR startMenuPath[MAX_PATH]; - auto success = SUCCEEDED(SHGetFolderPath(NULL, CSIDL_STARTMENU, NULL, 0, startMenuPath)); - outPath = CString(startMenuPath) + _T("\\Programs\\HQ\\"); - success = SHCreateDirectoryEx(NULL, outPath, NULL) || ERROR_ALREADY_EXISTS == GetLastError(); - return success; - } else if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, localDataPath))) { - _tcscat_s(localDataPath, _T("\\") + DIRECTORY_NAME_APP + _T("\\")); - outPath = CString(localDataPath); - if (type == PathType::Download_Directory) { - outPath += DIRECTORY_NAME_DOWNLOADS + _T("\\"); - } else if (type == PathType::Interface_Directory) { - outPath += DIRECTORY_NAME_INTERFACE; - } else if (type == PathType::Content_Directory) { - outPath += DIRECTORY_NAME_CONTENT; - } - return (CreateDirectory(outPath, NULL) || ERROR_ALREADY_EXISTS == GetLastError()); - } - } - return FALSE; + if (type == PathType::Running_Path) { + char appPath[MAX_PATH]; + DWORD size = GetModuleFileNameA(NULL, appPath, MAX_PATH); + if (size) { + outPath = CString(appPath); + return TRUE; + } + } else if (type == PathType::Desktop_Directory) { + TCHAR desktopPath[MAX_PATH]; + auto success = SUCCEEDED(SHGetFolderPath(NULL, CSIDL_DESKTOP, NULL, 0, desktopPath)); + outPath = CString(desktopPath); + return success; + } else if (type == PathType::Temp_Directory) { + TCHAR tempPath[MAX_PATH]; + auto success = GetTempPath(MAX_PATH, tempPath); + outPath = CString(tempPath); + return success; + } else { + TCHAR localDataPath[MAX_PATH]; + if (type == PathType::StartMenu_Directory) { + TCHAR startMenuPath[MAX_PATH]; + auto success = SUCCEEDED(SHGetFolderPath(NULL, CSIDL_STARTMENU, NULL, 0, startMenuPath)); + outPath = CString(startMenuPath) + _T("\\Programs\\HQ\\"); + success = SHCreateDirectoryEx(NULL, outPath, NULL) || ERROR_ALREADY_EXISTS == GetLastError(); + return success; + } else if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, localDataPath))) { + _tcscat_s(localDataPath, _T("\\") + DIRECTORY_NAME_APP + _T("\\")); + outPath = CString(localDataPath); + if (type == PathType::Download_Directory) { + outPath += DIRECTORY_NAME_DOWNLOADS + _T("\\"); + } else if (type == PathType::Interface_Directory) { + outPath += DIRECTORY_NAME_INTERFACE; + } else if (type == PathType::Content_Directory) { + outPath += DIRECTORY_NAME_CONTENT; + } + return (CreateDirectory(outPath, NULL) || ERROR_ALREADY_EXISTS == GetLastError()); + } + } + return FALSE; } BOOL LauncherManager::getInstalledVersion(const CString& path, CString& version) { - CStdioFile cfile; - BOOL success = cfile.Open(path, CFile::modeRead); - if (success) { - cfile.ReadString(version); - cfile.Close(); - } - return success; + CStdioFile cfile; + BOOL success = cfile.Open(path, CFile::modeRead); + if (success) { + cfile.ReadString(version); + cfile.Close(); + } + return success; } -BOOL LauncherManager::launchApplication(const CString& tokensJSON) { - CString installDir; - LauncherManager::getAndCreatePaths(PathType::Interface_Directory, installDir); - CString interfaceExe = installDir + _T("\\interface.exe"); - CString params1 = _T("--url \"") + _domainURL + ("\" "); - CString cacheDir; - LauncherManager::getAndCreatePaths(PathType::Content_Directory, cacheDir); - CString params3 = _T("--cache \"") + cacheDir + ("\" "); - CString params4 = !_displayName.IsEmpty() ? _T("--displayName \"") + _displayName + ("\" ") : _T(""); - CString parsedTokens = tokensJSON; - parsedTokens.Replace(_T("\""), _T("\\\"")); - CString params5 = !tokensJSON.IsEmpty() ? _T("--tokens \"") + parsedTokens + ("\"") : _T(""); - CString params = params1 + params3 + params4 + params5 + EXTRA_PARAMETERS; - auto rs = ShellExecute(NULL, L"open", interfaceExe, params, NULL, SW_SHOW); - return (rs != NULL); +HWND LauncherManager::launchApplication() { + CString installDir; + LauncherManager::getAndCreatePaths(PathType::Interface_Directory, installDir); + CString interfaceExe = installDir + _T("\\interface.exe"); + CString urlParam = _T("--url \"") + _domainURL + ("\" "); + CString scriptsURL = installDir + _T("\\scripts\\simplifiedUI"); + CString scriptsParam = _T("--scripts \"") + scriptsURL + ("\" "); + CString cacheDir; + LauncherManager::getAndCreatePaths(PathType::Content_Directory, cacheDir); + CString cacheParam = _T("--cache \"") + cacheDir + ("\" "); + CString nameParam = !_displayName.IsEmpty() ? _T("--displayName \"") + _displayName + ("\" ") : _T(""); + CString tokensParam = _T(""); + if (!_tokensJSON.IsEmpty()) { + CString parsedTokens = _tokensJSON; + parsedTokens.Replace(_T("\""), _T("\\\"")); + tokensParam = _T("--tokens \""); + tokensParam += parsedTokens + _T("\" "); + } + CString bookmarkParam = _T("--setBookmark hqhome=\"") + _domainURL + ("\" "); + CString params = urlParam + scriptsParam + cacheParam + nameParam + tokensParam + bookmarkParam + EXTRA_PARAMETERS; + _shouldLaunch = FALSE; + return LauncherUtils::executeOnForeground(interfaceExe, params); } BOOL LauncherManager::createConfigJSON() { - CString configPath; - LauncherManager::getAndCreatePaths(PathType::Interface_Directory, configPath); - configPath += "\\config.json"; - std::ofstream configFile(configPath, std::ofstream::binary); - if (configFile.fail()) { - return FALSE; - } - Json::Value config; - CString applicationPath; - LauncherManager::getAndCreatePaths(PathType::Launcher_Directory, applicationPath); - applicationPath += LAUNCHER_EXE_FILENAME; - config["loggedIn"] = _loggedIn; - config["launcherPath"] = LauncherUtils::cStringToStd(applicationPath); - config["version"] = LauncherUtils::cStringToStd(_latestVersion); - config["domain"] = LauncherUtils::cStringToStd(_domainURL); - CString content; - getAndCreatePaths(PathType::Content_Directory, content); - config["content"] = LauncherUtils::cStringToStd(content); - configFile << config; - configFile.close(); - return TRUE; + CString configPath; + LauncherManager::getAndCreatePaths(PathType::Interface_Directory, configPath); + configPath += "\\config.json"; + std::ofstream configFile(configPath, std::ofstream::binary); + if (configFile.fail()) { + return FALSE; + } + Json::Value config; + CString applicationPath; + LauncherManager::getAndCreatePaths(PathType::Launcher_Directory, applicationPath); + applicationPath += LAUNCHER_EXE_FILENAME; + config["loggedIn"] = _loggedIn; + config["launcherPath"] = LauncherUtils::cStringToStd(applicationPath); + config["version"] = LauncherUtils::cStringToStd(_latestVersion); + config["domain"] = LauncherUtils::cStringToStd(_domainURL); + CString content; + getAndCreatePaths(PathType::Content_Directory, content); + config["content"] = LauncherUtils::cStringToStd(content); + configFile << config; + configFile.close(); + return TRUE; } LauncherUtils::ResponseError LauncherManager::readConfigJSON(CString& version, CString& domain, CString& content, bool& loggedIn) { - CString configPath; - getAndCreatePaths(PathType::Interface_Directory, configPath); - configPath += "\\config.json"; - std::ifstream configFile(configPath, std::ifstream::binary); - if (configFile.fail()) { - return LauncherUtils::ResponseError::Open; - } - Json::Value config; - configFile >> config; - if (config["version"].isString() && config["domain"].isString() && - config["content"].isString()) { - loggedIn = config["loggedIn"].asBool(); - version = config["version"].asCString(); - domain = config["domain"].asCString(); - content = config["content"].asCString(); - configFile.close(); - return LauncherUtils::ResponseError::NoError; - } - configFile.close(); - return LauncherUtils::ResponseError::ParsingJSON; + CString configPath; + getAndCreatePaths(PathType::Interface_Directory, configPath); + configPath += "\\config.json"; + std::ifstream configFile(configPath, std::ifstream::binary); + if (configFile.fail()) { + return LauncherUtils::ResponseError::Open; + } + Json::Value config; + configFile >> config; + if (config["version"].isString() && config["domain"].isString() && + config["content"].isString()) { + loggedIn = config["loggedIn"].asBool(); + version = config["version"].asCString(); + domain = config["domain"].asCString(); + content = config["content"].asCString(); + configFile.close(); + return LauncherUtils::ResponseError::NoError; + } + configFile.close(); + return LauncherUtils::ResponseError::ParsingJSON; } LauncherUtils::ResponseError LauncherManager::readOrganizationJSON(const CString& hash) { - CString contentTypeJson = L"content-type:application/json"; - CString response; - CString url = _T("/hifi-public/huffman/organizations/") + hash + _T(".json"); - LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", L"s3.amazonaws.com", url, - contentTypeJson, CStringA(), response, false); - if (error != LauncherUtils::ResponseError::NoError) { - return error; - } - Json::Value json; - if (LauncherUtils::parseJSON(response, json)) { - if (json["content_set_url"].isString() && json["domain"].isString()) { - _contentURL = json["content_set_url"].asCString(); - _domainURL = json["domain"].asCString(); - return LauncherUtils::ResponseError::NoError; - } - } - return LauncherUtils::ResponseError::ParsingJSON; + CString contentTypeJson = L"content-type:application/json"; + CString response; + CString url = _T("/organizations/") + hash + _T(".json"); + LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", L"orgs.highfidelity.com", url, + contentTypeJson, CStringA(), response, false); + if (error != LauncherUtils::ResponseError::NoError) { + return error; + } + Json::Value json; + if (LauncherUtils::parseJSON(response, json)) { + if (json["content_set_url"].isString() && json["domain"].isString()) { + _contentURL = json["content_set_url"].asCString(); + _domainURL = json["domain"].asCString(); + return LauncherUtils::ResponseError::NoError; + } + } + return LauncherUtils::ResponseError::ParsingJSON; } LauncherUtils::ResponseError LauncherManager::getMostRecentBuild(CString& urlOut, CString& versionOut) { - CString contentTypeJson = L"content-type:application/json"; - CString response; - LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", L"thunder.highfidelity.com", L"/builds/api/tags/latest?format=json", - contentTypeJson, CStringA(), response, false); - if (error != LauncherUtils::ResponseError::NoError) { - return error; - } - Json::Value json; - if (LauncherUtils::parseJSON(response, json)) { - int count = json["count"].isInt() ? json["count"].asInt() : 0; - if (count > 0 && json["results"].isArray()) { - for (int i = 0; i < count; i++) { - if (json["results"][i].isObject()) { - Json::Value result = json["results"][i]; - if (result["latest_version"].isInt()) { - std::string version = std::to_string(result["latest_version"].asInt()); - versionOut = CString(version.c_str()); - } - if (result["installers"].isObject() && - result["installers"]["windows"].isObject() && - result["installers"]["windows"]["zip_url"].isString()) { - urlOut = result["installers"]["windows"]["zip_url"].asCString(); - return LauncherUtils::ResponseError::NoError; - } - } - } - } - } - return LauncherUtils::ResponseError::ParsingJSON; + CString contentTypeJson = L"content-type:application/json"; + CString response; + LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", L"thunder.highfidelity.com", L"/builds/api/tags/latest?format=json", + contentTypeJson, CStringA(), response, false); + if (error != LauncherUtils::ResponseError::NoError) { + return error; + } + Json::Value json; + if (LauncherUtils::parseJSON(response, json)) { + int count = json["count"].isInt() ? json["count"].asInt() : 0; + if (count > 0 && json["results"].isArray()) { + for (int i = 0; i < count; i++) { + if (json["results"][i].isObject()) { + Json::Value result = json["results"][i]; + if (result["latest_version"].isInt()) { + std::string version = std::to_string(result["latest_version"].asInt()); + versionOut = CString(version.c_str()); + } + if (result["installers"].isObject() && + result["installers"]["windows"].isObject() && + result["installers"]["windows"]["zip_url"].isString()) { + urlOut = result["installers"]["windows"]["zip_url"].asCString(); + return LauncherUtils::ResponseError::NoError; + } + } + } + } + } + return LauncherUtils::ResponseError::ParsingJSON; } LauncherUtils::ResponseError LauncherManager::getAccessTokenForCredentials(const CString& username, const CString& password) { - CStringA post = "grant_type=password&username="; - post += username; - post += "&password="; - post += password; - post += "&scope=owner"; + CStringA post = "grant_type=password&username="; + post += username; + post += "&password="; + post += password; + post += "&scope=owner"; - CString contentTypeText = L"content-type:application/x-www-form-urlencoded"; - CString response; - LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", L"metaverse.highfidelity.com", L"/oauth/token", - contentTypeText, post, response, true); - if (error != LauncherUtils::ResponseError::NoError) { - return error; - } - Json::Value json; - if (LauncherUtils::parseJSON(response, json)) { - if (json["error"].isString()) { - return LauncherUtils::ResponseError::BadCredentials; - } else if (json["access_token"].isString()) { - _tokensJSON = response; - return LauncherUtils::ResponseError::NoError; - } - } - return LauncherUtils::ResponseError::ParsingJSON; + CString contentTypeText = L"content-type:application/x-www-form-urlencoded"; + CString response; + LauncherUtils::ResponseError error = LauncherUtils::makeHTTPCall(L"HQ Launcher", L"metaverse.highfidelity.com", L"/oauth/token", + contentTypeText, post, response, true); + if (error != LauncherUtils::ResponseError::NoError) { + return error; + } + Json::Value json; + if (LauncherUtils::parseJSON(response, json)) { + if (json["error"].isString()) { + return LauncherUtils::ResponseError::BadCredentials; + } else if (json["access_token"].isString()) { + _tokensJSON = response; + return LauncherUtils::ResponseError::NoError; + } + } + return LauncherUtils::ResponseError::ParsingJSON; } BOOL LauncherManager::createApplicationRegistryKeys(int size) { - const std::string REGISTRY_PATH = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ"; - BOOL success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "DisplayName", "HQ"); - CString installDir; - getAndCreatePaths(PathType::Launcher_Directory, installDir); - success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "InstallLocation", LauncherUtils::cStringToStd(installDir)); - std::string applicationExe = LauncherUtils::cStringToStd(installDir + LAUNCHER_EXE_FILENAME); - std::string uninstallPath = '"' + applicationExe + '"' + " --uninstall"; - success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "UninstallString", uninstallPath); - success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "DisplayVersion", LauncherUtils::cStringToStd(_latestVersion)); - success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "DisplayIcon", applicationExe); - success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "Publisher", "High Fidelity"); - success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "InstallDate", LauncherUtils::cStringToStd(CTime::GetCurrentTime().Format("%Y%m%d"))); - success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "EstimatedSize", (DWORD)size); - success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "NoModify", (DWORD)1); - success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "NoRepair", (DWORD)1); - return success; + const std::string REGISTRY_PATH = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ"; + BOOL success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "DisplayName", "HQ"); + CString installDir; + getAndCreatePaths(PathType::Launcher_Directory, installDir); + success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "InstallLocation", LauncherUtils::cStringToStd(installDir)); + std::string applicationExe = LauncherUtils::cStringToStd(installDir + LAUNCHER_EXE_FILENAME); + std::string uninstallPath = '"' + applicationExe + '"' + " --uninstall"; + success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "UninstallString", uninstallPath); + success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "DisplayVersion", LauncherUtils::cStringToStd(_latestVersion)); + success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "DisplayIcon", applicationExe); + success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "Publisher", "High Fidelity"); + success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "InstallDate", LauncherUtils::cStringToStd(CTime::GetCurrentTime().Format("%Y%m%d"))); + success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "EstimatedSize", (DWORD)size); + success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "NoModify", (DWORD)1); + success = LauncherUtils::insertRegistryKey(REGISTRY_PATH, "NoRepair", (DWORD)1); + return success; } BOOL LauncherManager::deleteApplicationRegistryKeys() { - const CString REGISTRY_PATH = _T("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ"); - return LauncherUtils::deleteRegistryKey(REGISTRY_PATH); + const CString REGISTRY_PATH = _T("Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ"); + return LauncherUtils::deleteRegistryKey(REGISTRY_PATH); } BOOL LauncherManager::uninstallApplication() { - CString installDir; - getAndCreatePaths(PathType::Launcher_Directory, installDir); - BOOL success = LauncherUtils::deleteFileOrDirectory(installDir); - success = success && (deleteShortcuts()); - success = success && (deleteApplicationRegistryKeys()); - return success; + CString installDir; + getAndCreatePaths(PathType::Launcher_Directory, installDir); + BOOL success = LauncherUtils::deleteFileOrDirectory(installDir); + success = success && (deleteShortcuts()); + success = success && (deleteApplicationRegistryKeys()); + return success; } void LauncherManager::onZipExtracted(ZipType type, int size) { - if (type == ZipType::ZipContent) { + if (type == ZipType::ZipContent) { addToLog(_T("Downloading application.")); - downloadApplication(); - } else if (type == ZipType::ZipApplication) { - createShortcuts(); - CString versionPath; - getAndCreatePaths(LauncherManager::PathType::Launcher_Directory, versionPath); + downloadApplication(); + } else if (type == ZipType::ZipApplication) { + createShortcuts(); + CString versionPath; + getAndCreatePaths(LauncherManager::PathType::Launcher_Directory, versionPath); addToLog(_T("Creating config.json")); createConfigJSON(); addToLog(_T("Launching application.")); - launchApplication(_tokensJSON); - addToLog(_T("Creating registry keys.")); - createApplicationRegistryKeys(size); - _shouldShutdown = TRUE; - } + _shouldLaunch = TRUE; + if (!_shouldUpdate) { + addToLog(_T("Creating registry keys.")); + createApplicationRegistryKeys(size); + } + _shouldShutdown = TRUE; + } } BOOL LauncherManager::extractApplication() { - CString installPath; - getAndCreatePaths(LauncherManager::PathType::Interface_Directory, installPath); - BOOL success = LauncherUtils::unzipFileOnThread(ZipType::ZipApplication, LauncherUtils::cStringToStd(_applicationZipPath), - LauncherUtils::cStringToStd(installPath), [&](int type, int size) { - onZipExtracted((ZipType)type, size); - }); - return success; + CString installPath; + getAndCreatePaths(LauncherManager::PathType::Interface_Directory, installPath); + BOOL success = LauncherUtils::unzipFileOnThread(ZipType::ZipApplication, LauncherUtils::cStringToStd(_applicationZipPath), + LauncherUtils::cStringToStd(installPath), [&](int type, int size) { + onZipExtracted((ZipType)type, size); + }); + return success; } void LauncherManager::onFileDownloaded(DownloadType type) { - if (type == DownloadType::DownloadContent) { + if (type == DownloadType::DownloadContent) { addToLog(_T("Installing content.")); - installContent(); - } else if (type == DownloadType::DownloadApplication) { - addToLog(_T("Installing application.")); - extractApplication(); - } + installContent(); + } else if (type == DownloadType::DownloadApplication) { + addToLog(_T("Installing application.")); + extractApplication(); + } } BOOL LauncherManager::installContent() { - std::string contentZipFile = LauncherUtils::cStringToStd(_contentZipPath); - CString contentPath; - getAndCreatePaths(LauncherManager::PathType::Content_Directory, contentPath); - BOOL success = LauncherUtils::unzipFileOnThread(ZipType::ZipContent, contentZipFile, - LauncherUtils::cStringToStd(contentPath), [&](int type, int size) { - onZipExtracted((ZipType)type, size); - }); - return success; + std::string contentZipFile = LauncherUtils::cStringToStd(_contentZipPath); + CString contentPath; + getAndCreatePaths(LauncherManager::PathType::Content_Directory, contentPath); + BOOL success = LauncherUtils::unzipFileOnThread(ZipType::ZipContent, contentZipFile, + LauncherUtils::cStringToStd(contentPath), [&](int type, int size) { + onZipExtracted((ZipType)type, size); + }); + return success; } BOOL LauncherManager::downloadFile(DownloadType type, const CString& url, CString& outPath) { - CString fileName = url.Mid(url.ReverseFind('/') + 1); - CString downloadDirectory; - BOOL success = getAndCreatePaths(LauncherManager::PathType::Download_Directory, downloadDirectory); - outPath = downloadDirectory + fileName; - if (success) { - if (!LauncherUtils::downloadFileOnThread(type, url, outPath, [&](int type) { - onFileDownloaded((DownloadType)type); - })) { - success = FALSE; - } - } - return success; + CString fileName = url.Mid(url.ReverseFind('/') + 1); + CString downloadDirectory; + BOOL success = getAndCreatePaths(LauncherManager::PathType::Download_Directory, downloadDirectory); + outPath = downloadDirectory + fileName; + if (success) { + if (!LauncherUtils::downloadFileOnThread(type, url, outPath, [&](int type) { + onFileDownloaded((DownloadType)type); + })) { + success = FALSE; + } + } + return success; } BOOL LauncherManager::downloadContent() { addToLog(_T("Downloading content.")); - CString contentURL = getContentURL(); - return downloadFile(DownloadType::DownloadContent, contentURL, _contentZipPath); + CString contentURL = getContentURL(); + return downloadFile(DownloadType::DownloadContent, contentURL, _contentZipPath); } BOOL LauncherManager::downloadApplication() { - CString applicationURL = getLatestInterfaceURL(); - return downloadFile(DownloadType::DownloadApplication, applicationURL, _applicationZipPath); + CString applicationURL = getLatestInterfaceURL(); + return downloadFile(DownloadType::DownloadApplication, applicationURL, _applicationZipPath); } diff --git a/launchers/win32/LauncherManager.h b/launchers/win32/LauncherManager.h index 4bc09b49ca..6fa7efeeda 100644 --- a/launchers/win32/LauncherManager.h +++ b/launchers/win32/LauncherManager.h @@ -23,91 +23,93 @@ const bool INSTALL_ZIP = true; class LauncherManager { public: - enum PathType { - Running_Path = 0, - Launcher_Directory, - Download_Directory, - Interface_Directory, - Desktop_Directory, - Content_Directory, - StartMenu_Directory, - Temp_Directory - }; - enum ZipType { - ZipContent = 0, - ZipApplication - }; - enum DownloadType { - DownloadContent = 0, - DownloadApplication - }; - enum ErrorType { - ErrorNetworkAuth, - ErrorNetworkUpdate, - ErrorNetworkHq, - ErrorDownloading, - ErrorUpdating, - ErrorInstall, - ErrorIOFiles - }; - LauncherManager(); - ~LauncherManager(); - void init(); + enum PathType { + Running_Path = 0, + Launcher_Directory, + Download_Directory, + Interface_Directory, + Desktop_Directory, + Content_Directory, + StartMenu_Directory, + Temp_Directory + }; + enum ZipType { + ZipContent = 0, + ZipApplication + }; + enum DownloadType { + DownloadContent = 0, + DownloadApplication + }; + enum ErrorType { + ErrorNetworkAuth, + ErrorNetworkUpdate, + ErrorNetworkHq, + ErrorDownloading, + ErrorUpdating, + ErrorInstall, + ErrorIOFiles + }; + LauncherManager(); + ~LauncherManager(); + void init(); BOOL initLog(); BOOL addToLog(const CString& line); void closeLog(); - BOOL getAndCreatePaths(PathType type, CString& outPath); - BOOL getInstalledVersion(const CString& path, CString& version); - BOOL isApplicationInstalled(CString& version, CString& domain, - CString& content, bool& loggedIn); - LauncherUtils::ResponseError getAccessTokenForCredentials(const CString& username, const CString& password); - LauncherUtils::ResponseError getMostRecentBuild(CString& urlOut, CString& versionOut); - LauncherUtils::ResponseError readOrganizationJSON(const CString& hash); - LauncherUtils::ResponseError readConfigJSON(CString& version, CString& domain, - CString& content, bool& loggedIn); - BOOL createConfigJSON(); - BOOL createApplicationRegistryKeys(int size); - BOOL deleteApplicationRegistryKeys(); - BOOL createShortcuts(); - BOOL deleteShortcuts(); - BOOL launchApplication(const CString& tokensJSON = _T("")); - BOOL uninstallApplication(); - BOOL installLauncher(); + BOOL getAndCreatePaths(PathType type, CString& outPath); + BOOL getInstalledVersion(const CString& path, CString& version); + BOOL isApplicationInstalled(CString& version, CString& domain, + CString& content, bool& loggedIn); + LauncherUtils::ResponseError getAccessTokenForCredentials(const CString& username, const CString& password); + LauncherUtils::ResponseError getMostRecentBuild(CString& urlOut, CString& versionOut); + LauncherUtils::ResponseError readOrganizationJSON(const CString& hash); + LauncherUtils::ResponseError readConfigJSON(CString& version, CString& domain, + CString& content, bool& loggedIn); + BOOL createConfigJSON(); + BOOL createApplicationRegistryKeys(int size); + BOOL deleteApplicationRegistryKeys(); + BOOL createShortcuts(); + BOOL deleteShortcuts(); + HWND launchApplication(); + BOOL uninstallApplication(); + BOOL installLauncher(); - // getters - const CString& getContentURL() const { return _contentURL; } - const CString& getdomainURL() const { return _domainURL; } - const CString& getVersion() const { return _version; } - BOOL shouldShutDown() const { return _shouldShutdown; } - BOOL needsUpdate() { return _shouldUpdate; } - BOOL needsUninstall() { return _shouldUninstall; } - void setDisplayName(const CString& displayName) { _displayName = displayName; } - bool isLoggedIn() { return _loggedIn; } - const CString& getLatestInterfaceURL() const { return _latestApplicationURL; } - void uninstall() { _shouldUninstall = true; }; + // getters + const CString& getContentURL() const { return _contentURL; } + const CString& getdomainURL() const { return _domainURL; } + const CString& getVersion() const { return _version; } + BOOL shouldShutDown() const { return _shouldShutdown; } + BOOL shouldLaunch() const { return _shouldLaunch; } + BOOL needsUpdate() { return _shouldUpdate; } + BOOL needsUninstall() { return _shouldUninstall; } + void setDisplayName(const CString& displayName) { _displayName = displayName; } + bool isLoggedIn() { return _loggedIn; } + const CString& getLatestInterfaceURL() const { return _latestApplicationURL; } + void uninstall() { _shouldUninstall = true; }; - BOOL downloadFile(DownloadType type, const CString& url, CString& localPath); - BOOL downloadContent(); - BOOL downloadApplication(); - BOOL installContent(); - BOOL extractApplication(); - void onZipExtracted(ZipType type, int size); - void onFileDownloaded(DownloadType type); + BOOL downloadFile(DownloadType type, const CString& url, CString& localPath); + BOOL downloadContent(); + BOOL downloadApplication(); + BOOL installContent(); + BOOL extractApplication(); + void onZipExtracted(ZipType type, int size); + void onFileDownloaded(DownloadType type); private: - CString _latestApplicationURL; - CString _latestVersion; - CString _contentURL; - CString _domainURL; - CString _version; - CString _displayName; - CString _tokensJSON; - CString _applicationZipPath; - CString _contentZipPath; - bool _loggedIn{ false }; - BOOL _shouldUpdate{ FALSE }; - BOOL _shouldUninstall{ FALSE }; - BOOL _shouldShutdown{ FALSE }; + CString _latestApplicationURL; + CString _latestVersion; + CString _contentURL; + CString _domainURL; + CString _version; + CString _displayName; + CString _tokensJSON; + CString _applicationZipPath; + CString _contentZipPath; + bool _loggedIn{ false }; + BOOL _shouldUpdate{ FALSE }; + BOOL _shouldUninstall{ FALSE }; + BOOL _shouldShutdown{ FALSE }; + BOOL _shouldLaunch{ FALSE }; CStdioFile _logFile; }; diff --git a/launchers/win32/LauncherUtils.cpp b/launchers/win32/LauncherUtils.cpp index 22724ac499..cfb8b765c5 100644 --- a/launchers/win32/LauncherUtils.cpp +++ b/launchers/win32/LauncherUtils.cpp @@ -13,6 +13,7 @@ #include #include #include + #pragma comment(lib, "winhttp") #include "LauncherUtils.h" @@ -36,360 +37,360 @@ CString LauncherUtils::urlEncodeString(const CString& url) { } BOOL LauncherUtils::IsProcessRunning(const wchar_t *processName) { - bool exists = false; - PROCESSENTRY32 entry; - entry.dwSize = sizeof(PROCESSENTRY32); + bool exists = false; + PROCESSENTRY32 entry; + entry.dwSize = sizeof(PROCESSENTRY32); - HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); + HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL); - if (Process32First(snapshot, &entry)) { - while (Process32Next(snapshot, &entry)) { - if (!_wcsicmp(entry.szExeFile, processName)) { - exists = true; - break; - } - } - } - CloseHandle(snapshot); - return exists; + if (Process32First(snapshot, &entry)) { + while (Process32Next(snapshot, &entry)) { + if (!_wcsicmp(entry.szExeFile, processName)) { + exists = true; + break; + } + } + } + CloseHandle(snapshot); + return exists; } HRESULT LauncherUtils::CreateLink(LPCWSTR lpszPathObj, LPCSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszArgs) { - IShellLink* psl; + IShellLink* psl; - // Get a pointer to the IShellLink interface. It is assumed that CoInitialize - // has already been called. - CoInitialize(NULL); - HRESULT hres = E_INVALIDARG; - if ((lpszPathObj != NULL) && (wcslen(lpszPathObj) > 0) && - (lpszDesc != NULL) && - (lpszPathLink != NULL) && (strlen(lpszPathLink) > 0)) { - hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); - if (SUCCEEDED(hres)) { - IPersistFile* ppf; + // Get a pointer to the IShellLink interface. It is assumed that CoInitialize + // has already been called. + CoInitialize(NULL); + HRESULT hres = E_INVALIDARG; + if ((lpszPathObj != NULL) && (wcslen(lpszPathObj) > 0) && + (lpszDesc != NULL) && + (lpszPathLink != NULL) && (strlen(lpszPathLink) > 0)) { + hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl); + if (SUCCEEDED(hres)) { + IPersistFile* ppf; - // Set the path to the shortcut target and add the description. - psl->SetPath(lpszPathObj); - psl->SetDescription(lpszDesc); - psl->SetArguments(lpszArgs); + // Set the path to the shortcut target and add the description. + psl->SetPath(lpszPathObj); + psl->SetDescription(lpszDesc); + psl->SetArguments(lpszArgs); - // Query IShellLink for the IPersistFile interface, used for saving the - // shortcut in persistent storage. - hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); + // Query IShellLink for the IPersistFile interface, used for saving the + // shortcut in persistent storage. + hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf); - if (SUCCEEDED(hres)) { - WCHAR wsz[MAX_PATH]; + if (SUCCEEDED(hres)) { + WCHAR wsz[MAX_PATH]; - // Ensure that the string is Unicode. - MultiByteToWideChar(CP_ACP, 0, lpszPathLink, -1, wsz, MAX_PATH); + // Ensure that the string is Unicode. + MultiByteToWideChar(CP_ACP, 0, lpszPathLink, -1, wsz, MAX_PATH); - // Add code here to check return value from MultiByteWideChar - // for success. + // Add code here to check return value from MultiByteWideChar + // for success. - // Save the link by calling IPersistFile::Save. - hres = ppf->Save(wsz, TRUE); - ppf->Release(); - } - psl->Release(); - } - } - CoUninitialize(); - return SUCCEEDED(hres); + // Save the link by calling IPersistFile::Save. + hres = ppf->Save(wsz, TRUE); + ppf->Release(); + } + psl->Release(); + } + } + CoUninitialize(); + return SUCCEEDED(hres); } std::string LauncherUtils::cStringToStd(CString cstring) { - CT2CA convertedString(cstring); - std::string strStd(convertedString); - return strStd; + CT2CA convertedString(cstring); + std::string strStd(convertedString); + return strStd; } BOOL LauncherUtils::launchApplication(LPCWSTR lpApplicationName, LPTSTR cmdArgs) { - // additional information - STARTUPINFO si; - PROCESS_INFORMATION pi; + // additional information + STARTUPINFO si; + PROCESS_INFORMATION pi; - // set the size of the structures - ZeroMemory(&si, sizeof(si)); - si.cb = sizeof(si); - ZeroMemory(&pi, sizeof(pi)); + // set the size of the structures + ZeroMemory(&si, sizeof(si)); + si.cb = sizeof(si); + ZeroMemory(&pi, sizeof(pi)); - // start the program up - BOOL success = CreateProcess( - lpApplicationName, // the path - cmdArgs, // Command line - NULL, // Process handle not inheritable - NULL, // Thread handle not inheritable - FALSE, // Set handle inheritance to FALSE - CREATE_NEW_CONSOLE, // Opens file in a separate console - NULL, // Use parent's environment block - NULL, // Use parent's starting directory - &si, // Pointer to STARTUPINFO structure - &pi // Pointer to PROCESS_INFORMATION structure - ); - // Close process and thread handles. - CloseHandle(pi.hProcess); - CloseHandle(pi.hThread); - return success; + // start the program up + BOOL success = CreateProcess( + lpApplicationName, // the path + cmdArgs, // Command line + NULL, // Process handle not inheritable + NULL, // Thread handle not inheritable + FALSE, // Set handle inheritance to FALSE + CREATE_NEW_CONSOLE, // Opens file in a separate console + NULL, // Use parent's environment block + NULL, // Use parent's starting directory + &si, // Pointer to STARTUPINFO structure + &pi // Pointer to PROCESS_INFORMATION structure + ); + // Close process and thread handles. + CloseHandle(pi.hProcess); + CloseHandle(pi.hThread); + return success; } BOOL LauncherUtils::deleteRegistryKey(const CString& registryPath) { - TCHAR szDelKey[MAX_PATH * 2]; - StringCchCopy(szDelKey, MAX_PATH * 2, registryPath); - auto status = RegDeleteKey(HKEY_CURRENT_USER, szDelKey); - if (status == ERROR_SUCCESS) { - return TRUE; - } - return FALSE; + TCHAR szDelKey[MAX_PATH * 2]; + StringCchCopy(szDelKey, MAX_PATH * 2, registryPath); + auto status = RegDeleteKey(HKEY_CURRENT_USER, szDelKey); + if (status == ERROR_SUCCESS) { + return TRUE; + } + return FALSE; } LauncherUtils::ResponseError LauncherUtils::makeHTTPCall(const CString& callerName, - const CString& mainUrl, const CString& dirUrl, - const CString& contentType, CStringA& postData, - CString& response, bool isPost = false) { + const CString& mainUrl, const CString& dirUrl, + const CString& contentType, CStringA& postData, + CString& response, bool isPost = false) { - HINTERNET hopen = WinHttpOpen(callerName, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, - WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); - if (!hopen) { - return ResponseError::Open; - } - HINTERNET hconnect = WinHttpConnect(hopen, mainUrl, INTERNET_DEFAULT_HTTPS_PORT, 0); - if (!hconnect) { - return ResponseError::Connect; - } - HINTERNET hrequest = WinHttpOpenRequest(hconnect, isPost ? L"POST" : L"GET", dirUrl, - NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); - if (!hrequest) { - return ResponseError::OpenRequest; - } - if (isPost) { - if (!WinHttpSendRequest(hrequest, contentType, -1, - postData.GetBuffer(postData.GetLength()), (DWORD)strlen(postData), (DWORD)strlen(postData), NULL)) { - return ResponseError::SendRequest; - } - } else { - if (!WinHttpSendRequest(hrequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) { - return ResponseError::SendRequest; - } - } - if (!WinHttpReceiveResponse(hrequest, 0)) { - return ResponseError::ReceiveRequest; - } - // query remote file size, set haveContentLength on success and dwContentLength to the length - wchar_t szContentLength[32]; - DWORD bufferBytes = 4096; - DWORD dwHeaderIndex = WINHTTP_NO_HEADER_INDEX; + HINTERNET hopen = WinHttpOpen(callerName, WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, + WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); + if (!hopen) { + return ResponseError::Open; + } + HINTERNET hconnect = WinHttpConnect(hopen, mainUrl, INTERNET_DEFAULT_HTTPS_PORT, 0); + if (!hconnect) { + return ResponseError::Connect; + } + HINTERNET hrequest = WinHttpOpenRequest(hconnect, isPost ? L"POST" : L"GET", dirUrl, + NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE); + if (!hrequest) { + return ResponseError::OpenRequest; + } + if (isPost) { + if (!WinHttpSendRequest(hrequest, contentType, -1, + postData.GetBuffer(postData.GetLength()), (DWORD)strlen(postData), (DWORD)strlen(postData), NULL)) { + return ResponseError::SendRequest; + } + } else { + if (!WinHttpSendRequest(hrequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) { + return ResponseError::SendRequest; + } + } + if (!WinHttpReceiveResponse(hrequest, 0)) { + return ResponseError::ReceiveRequest; + } + // query remote file size, set haveContentLength on success and dwContentLength to the length + wchar_t szContentLength[32]; + DWORD bufferBytes = 4096; + DWORD dwHeaderIndex = WINHTTP_NO_HEADER_INDEX; - BOOL haveContentLength = WinHttpQueryHeaders(hrequest, WINHTTP_QUERY_CONTENT_LENGTH, NULL, - &szContentLength, &bufferBytes, &dwHeaderIndex); + BOOL haveContentLength = WinHttpQueryHeaders(hrequest, WINHTTP_QUERY_CONTENT_LENGTH, NULL, + &szContentLength, &bufferBytes, &dwHeaderIndex); - DWORD dwContentLength; - if (haveContentLength) { - dwContentLength = _wtoi(szContentLength); - } - byte p[4096]; - if (!WinHttpQueryDataAvailable(hrequest, &bufferBytes)) { - return ResponseError::ReadResponse; - } - WinHttpReadData(hrequest, p, bufferBytes, &bufferBytes); - response = CString((const char*)p, (int)bufferBytes); - return ResponseError::NoError; + DWORD dwContentLength; + if (haveContentLength) { + dwContentLength = _wtoi(szContentLength); + } + byte p[4096]; + if (!WinHttpQueryDataAvailable(hrequest, &bufferBytes)) { + return ResponseError::ReadResponse; + } + WinHttpReadData(hrequest, p, bufferBytes, &bufferBytes); + response = CString((const char*)p, (int)bufferBytes); + return ResponseError::NoError; } BOOL LauncherUtils::parseJSON(const CString& jsonTxt, Json::Value& root) { - Json::CharReaderBuilder CharBuilder; - std::string jsonString = cStringToStd(jsonTxt); - std::string errs; - Json::CharReader* nreader = CharBuilder.newCharReader(); - bool parsingSuccessful = false; - if (nreader != NULL) { - parsingSuccessful = nreader->parse(jsonString.c_str(), jsonString.c_str() + jsonString.length(), &root, &errs); - delete nreader; - } - return parsingSuccessful; + Json::CharReaderBuilder CharBuilder; + std::string jsonString = cStringToStd(jsonTxt); + std::string errs; + Json::CharReader* nreader = CharBuilder.newCharReader(); + bool parsingSuccessful = false; + if (nreader != NULL) { + parsingSuccessful = nreader->parse(jsonString.c_str(), jsonString.c_str() + jsonString.length(), &root, &errs); + delete nreader; + } + return parsingSuccessful; } BOOL LauncherUtils::getFont(const CString& fontName, int fontSize, bool isBold, CFont& fontOut) { - LOGFONT lf; - memset(&lf, 0, sizeof(lf)); - lf.lfHeight = fontSize; - lf.lfWeight = isBold ? FW_BOLD : FW_NORMAL; - lf.lfOutPrecision = OUT_TT_ONLY_PRECIS; - lf.lfQuality = ANTIALIASED_QUALITY; - - wcscpy_s(lf.lfFaceName, fontName); - if (!fontOut.CreateFontIndirect(&lf)) { - return FALSE; - } - return TRUE; + LOGFONT lf; + memset(&lf, 0, sizeof(lf)); + lf.lfHeight = fontSize; + lf.lfWeight = isBold ? FW_BOLD : FW_NORMAL; + lf.lfOutPrecision = OUT_TT_ONLY_PRECIS; + lf.lfQuality = ANTIALIASED_QUALITY; + + wcscpy_s(lf.lfFaceName, fontName); + if (!fontOut.CreateFontIndirect(&lf)) { + return FALSE; + } + return TRUE; } uint64_t LauncherUtils::extractZip(const std::string& zipFile, const std::string& path, std::vector& files) { - mz_zip_archive zip_archive; - memset(&zip_archive, 0, sizeof(zip_archive)); + mz_zip_archive zip_archive; + memset(&zip_archive, 0, sizeof(zip_archive)); - auto status = mz_zip_reader_init_file(&zip_archive, zipFile.c_str(), 0); + auto status = mz_zip_reader_init_file(&zip_archive, zipFile.c_str(), 0); - if (!status) return 0; - int fileCount = (int)mz_zip_reader_get_num_files(&zip_archive); - if (fileCount == 0) { - mz_zip_reader_end(&zip_archive); - return 0; - } - mz_zip_archive_file_stat file_stat; - if (!mz_zip_reader_file_stat(&zip_archive, 0, &file_stat)) { - mz_zip_reader_end(&zip_archive); - return 0; - } - // Get root folder - CString lastDir = _T(""); - uint64_t totalSize = 0; - for (int i = 0; i < fileCount; i++) { - if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue; - std::string filename = file_stat.m_filename; - std::replace(filename.begin(), filename.end(), '/', '\\'); - CString fullFilename = CString(path.c_str()) + "\\" + CString(filename.c_str()); - if (mz_zip_reader_is_file_a_directory(&zip_archive, i)) { - if (SHCreateDirectoryEx(NULL, fullFilename, NULL) || ERROR_ALREADY_EXISTS == GetLastError()) { - break; - } else { - continue; - } - } - CT2A destFile(fullFilename); - if (mz_zip_reader_extract_to_file(&zip_archive, i, destFile, 0)) { - totalSize += (uint64_t)file_stat.m_uncomp_size; - files.emplace_back(destFile); - } - } + if (!status) return 0; + int fileCount = (int)mz_zip_reader_get_num_files(&zip_archive); + if (fileCount == 0) { + mz_zip_reader_end(&zip_archive); + return 0; + } + mz_zip_archive_file_stat file_stat; + if (!mz_zip_reader_file_stat(&zip_archive, 0, &file_stat)) { + mz_zip_reader_end(&zip_archive); + return 0; + } + // Get root folder + CString lastDir = _T(""); + uint64_t totalSize = 0; + for (int i = 0; i < fileCount; i++) { + if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue; + std::string filename = file_stat.m_filename; + std::replace(filename.begin(), filename.end(), '/', '\\'); + CString fullFilename = CString(path.c_str()) + "\\" + CString(filename.c_str()); + if (mz_zip_reader_is_file_a_directory(&zip_archive, i)) { + if (SHCreateDirectoryEx(NULL, fullFilename, NULL) || ERROR_ALREADY_EXISTS == GetLastError()) { + break; + } else { + continue; + } + } + CT2A destFile(fullFilename); + if (mz_zip_reader_extract_to_file(&zip_archive, i, destFile, 0)) { + totalSize += (uint64_t)file_stat.m_uncomp_size; + files.emplace_back(destFile); + } + } - // Close the archive, freeing any resources it was using - mz_zip_reader_end(&zip_archive); - return totalSize; + // Close the archive, freeing any resources it was using + mz_zip_reader_end(&zip_archive); + return totalSize; } BOOL LauncherUtils::insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value) { - HKEY key; - auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL); - if (status == ERROR_SUCCESS) { - status = RegSetValueExA(key, name.c_str(), 0, REG_SZ, (const BYTE*)value.c_str(), (DWORD)(value.size() + 1)); - return status == ERROR_SUCCESS; - } - RegCloseKey(key); - return FALSE; + HKEY key; + auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL); + if (status == ERROR_SUCCESS) { + status = RegSetValueExA(key, name.c_str(), 0, REG_SZ, (const BYTE*)value.c_str(), (DWORD)(value.size() + 1)); + return status == ERROR_SUCCESS; + } + RegCloseKey(key); + return FALSE; } BOOL LauncherUtils::insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value) { - HKEY key; - auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL); - if (status == ERROR_SUCCESS) { - status = RegSetValueExA(key, name.c_str(), 0, REG_DWORD, (const BYTE*)&value, sizeof(value)); - return TRUE; - } - RegCloseKey(key); - return FALSE; + HKEY key; + auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL); + if (status == ERROR_SUCCESS) { + status = RegSetValueExA(key, name.c_str(), 0, REG_DWORD, (const BYTE*)&value, sizeof(value)); + return TRUE; + } + RegCloseKey(key); + return FALSE; } BOOL LauncherUtils::deleteFileOrDirectory(const CString& dirPath, bool noRecycleBin) { - CString dir = dirPath; - // Add extra null to string - dir.AppendChar(0); - SHFILEOPSTRUCT fileop; - fileop.hwnd = NULL; // no status display - fileop.wFunc = FO_DELETE; // delete operation - fileop.pFrom = dir; // source file name as double null terminated string - fileop.pTo = NULL; // no destination needed - fileop.fFlags = FOF_NOCONFIRMATION | FOF_SILENT; // do not prompt the user + CString dir = dirPath; + // Add extra null to string + dir.AppendChar(0); + SHFILEOPSTRUCT fileop; + fileop.hwnd = NULL; // no status display + fileop.wFunc = FO_DELETE; // delete operation + fileop.pFrom = dir; // source file name as double null terminated string + fileop.pTo = NULL; // no destination needed + fileop.fFlags = FOF_NOCONFIRMATION | FOF_SILENT; // do not prompt the user - if (!noRecycleBin) { - fileop.fFlags |= FOF_ALLOWUNDO; - } + if (!noRecycleBin) { + fileop.fFlags |= FOF_ALLOWUNDO; + } - fileop.fAnyOperationsAborted = FALSE; - fileop.lpszProgressTitle = NULL; - fileop.hNameMappings = NULL; + fileop.fAnyOperationsAborted = FALSE; + fileop.lpszProgressTitle = NULL; + fileop.hNameMappings = NULL; - int ret = SHFileOperation(&fileop); - return (ret == 0); + int ret = SHFileOperation(&fileop); + return (ret == 0); } BOOL LauncherUtils::hMac256(const CString& cmessage, const char* keystr, CString& hashOut) { - char message[256]; - strcpy_s(message, CStringA(cmessage).GetString()); - char key[256]; - strcpy_s(key, keystr); - HCRYPTPROV hProv = 0; - HCRYPTHASH hHash = 0; - HCRYPTKEY hKey = 0; - HCRYPTHASH hHmacHash = 0; - BYTE pbHash[32]; - HMAC_INFO HmacInfo; - int err = 0; - typedef struct blob { - BLOBHEADER header; - DWORD len; - BYTE key[1]; - } m_blob; + char message[256]; + strcpy_s(message, CStringA(cmessage).GetString()); + char key[256]; + strcpy_s(key, keystr); + HCRYPTPROV hProv = 0; + HCRYPTHASH hHash = 0; + HCRYPTKEY hKey = 0; + HCRYPTHASH hHmacHash = 0; + BYTE pbHash[32]; + HMAC_INFO HmacInfo; + int err = 0; + typedef struct blob { + BLOBHEADER header; + DWORD len; + BYTE key[1]; + } m_blob; - ZeroMemory(&HmacInfo, sizeof(HmacInfo)); - HmacInfo.HashAlgid = CALG_SHA_256; - ZeroMemory(&pbHash, 32); - m_blob* kb = NULL; - DWORD kbSize = DWORD(sizeof(m_blob) + strlen(key)); + ZeroMemory(&HmacInfo, sizeof(HmacInfo)); + HmacInfo.HashAlgid = CALG_SHA_256; + ZeroMemory(&pbHash, 32); + m_blob* kb = NULL; + DWORD kbSize = DWORD(sizeof(m_blob) + strlen(key)); - kb = (m_blob*)malloc(kbSize); - kb->header.bType = PLAINTEXTKEYBLOB; - kb->header.bVersion = CUR_BLOB_VERSION; - kb->header.reserved = 0; - kb->header.aiKeyAlg = CALG_RC2; - memcpy(&kb->key, key, strlen(key)); - kb->len = (DWORD)strlen(key); - BOOL success = false; - DWORD datalen = (DWORD)32; - if (CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_NEWKEYSET) - && CryptImportKey(hProv, (BYTE*)kb, kbSize, 0, CRYPT_IPSEC_HMAC_KEY, &hKey) - && CryptCreateHash(hProv, CALG_HMAC, hKey, 0, &hHmacHash) - && CryptSetHashParam(hHmacHash, HP_HMAC_INFO, (BYTE*)&HmacInfo, 0) - && CryptHashData(hHmacHash, (BYTE*)message, (DWORD)strlen(message), 0) - && CryptGetHashParam(hHmacHash, HP_HASHVAL, pbHash, &datalen, 0)) { - char *Hex = "0123456789abcdef"; - char HashString[65] = { 0 }; - for (int Count = 0; Count < 32; Count++) - { - HashString[Count * 2] = Hex[pbHash[Count] >> 4]; - HashString[(Count * 2) + 1] = Hex[pbHash[Count] & 0xF]; - } - hashOut = CString(HashString); - success = true; - } + kb = (m_blob*)malloc(kbSize); + kb->header.bType = PLAINTEXTKEYBLOB; + kb->header.bVersion = CUR_BLOB_VERSION; + kb->header.reserved = 0; + kb->header.aiKeyAlg = CALG_RC2; + memcpy(&kb->key, key, strlen(key)); + kb->len = (DWORD)strlen(key); + BOOL success = false; + DWORD datalen = (DWORD)32; + if (CryptAcquireContext(&hProv, NULL, MS_ENHANCED_PROV, PROV_RSA_FULL, CRYPT_VERIFYCONTEXT | CRYPT_NEWKEYSET) + && CryptImportKey(hProv, (BYTE*)kb, kbSize, 0, CRYPT_IPSEC_HMAC_KEY, &hKey) + && CryptCreateHash(hProv, CALG_HMAC, hKey, 0, &hHmacHash) + && CryptSetHashParam(hHmacHash, HP_HMAC_INFO, (BYTE*)&HmacInfo, 0) + && CryptHashData(hHmacHash, (BYTE*)message, (DWORD)strlen(message), 0) + && CryptGetHashParam(hHmacHash, HP_HASHVAL, pbHash, &datalen, 0)) { + char *Hex = "0123456789abcdef"; + char HashString[65] = { 0 }; + for (int Count = 0; Count < 32; Count++) + { + HashString[Count * 2] = Hex[pbHash[Count] >> 4]; + HashString[(Count * 2) + 1] = Hex[pbHash[Count] & 0xF]; + } + hashOut = CString(HashString); + success = true; + } - free(kb); - if (hHmacHash) - CryptDestroyHash(hHmacHash); - if (hKey) - CryptDestroyKey(hKey); - if (hHash) - CryptDestroyHash(hHash); - if (hProv) - CryptReleaseContext(hProv, 0); - return success; + free(kb); + if (hHmacHash) + CryptDestroyHash(hHmacHash); + if (hKey) + CryptDestroyKey(hKey); + if (hHash) + CryptDestroyHash(hHash); + if (hProv) + CryptReleaseContext(hProv, 0); + return success; } DWORD WINAPI LauncherUtils::unzipThread(LPVOID lpParameter) { - UnzipThreadData& data = *((UnzipThreadData*)lpParameter); - uint64_t size = LauncherUtils::extractZip(data._zipFile, data._path, std::vector()); - int mb_size = (int)(size * 0.001f); - data.callback(data._type, mb_size); - delete &data; - return 0; + UnzipThreadData& data = *((UnzipThreadData*)lpParameter); + uint64_t size = LauncherUtils::extractZip(data._zipFile, data._path, std::vector()); + int mb_size = (int)(size * 0.001f); + data.callback(data._type, mb_size); + delete &data; + return 0; } DWORD WINAPI LauncherUtils::downloadThread(LPVOID lpParameter) { - DownloadThreadData& data = *((DownloadThreadData*)lpParameter); - auto hr = URLDownloadToFile(0, data._url, data._file, 0, NULL); - data.callback(data._type); - return 0; + DownloadThreadData& data = *((DownloadThreadData*)lpParameter); + auto hr = URLDownloadToFile(0, data._url, data._file, 0, NULL); + data.callback(data._type); + return 0; } DWORD WINAPI LauncherUtils::deleteDirectoriesThread(LPVOID lpParameter) { @@ -406,33 +407,33 @@ DWORD WINAPI LauncherUtils::deleteDirectoriesThread(LPVOID lpParameter) { } BOOL LauncherUtils::unzipFileOnThread(int type, const std::string& zipFile, const std::string& path, std::function callback) { - DWORD myThreadID; - UnzipThreadData* unzipThreadData = new UnzipThreadData(); - unzipThreadData->_type = type; - unzipThreadData->_zipFile = zipFile; - unzipThreadData->_path = path; - unzipThreadData->setCallback(callback); - HANDLE myHandle = CreateThread(0, 0, unzipThread, unzipThreadData, 0, &myThreadID); - if (myHandle) { - CloseHandle(myHandle); - return TRUE; - } - return FALSE; + DWORD myThreadID; + UnzipThreadData* unzipThreadData = new UnzipThreadData(); + unzipThreadData->_type = type; + unzipThreadData->_zipFile = zipFile; + unzipThreadData->_path = path; + unzipThreadData->setCallback(callback); + HANDLE myHandle = CreateThread(0, 0, unzipThread, unzipThreadData, 0, &myThreadID); + if (myHandle) { + CloseHandle(myHandle); + return TRUE; + } + return FALSE; } BOOL LauncherUtils::downloadFileOnThread(int type, const CString& url, const CString& file, std::function callback) { - DWORD myThreadID; - DownloadThreadData* downloadThreadData = new DownloadThreadData(); - downloadThreadData->_type = type; - downloadThreadData->_url = url; - downloadThreadData->_file = file; - downloadThreadData->setCallback(callback); - HANDLE myHandle = CreateThread(0, 0, downloadThread, downloadThreadData, 0, &myThreadID); - if (myHandle) { - CloseHandle(myHandle); - return TRUE; - } - return FALSE; + DWORD myThreadID; + DownloadThreadData* downloadThreadData = new DownloadThreadData(); + downloadThreadData->_type = type; + downloadThreadData->_url = url; + downloadThreadData->_file = file; + downloadThreadData->setCallback(callback); + HANDLE myHandle = CreateThread(0, 0, downloadThread, downloadThreadData, 0, &myThreadID); + if (myHandle) { + CloseHandle(myHandle); + return TRUE; + } + return FALSE; } BOOL LauncherUtils::deleteDirectoriesOnThread(const CString& applicationDir, @@ -450,3 +451,38 @@ BOOL LauncherUtils::deleteDirectoriesOnThread(const CString& applicationDir, } return FALSE; } + +HWND LauncherUtils::executeOnForeground(const CString& path, const CString& params) { + SHELLEXECUTEINFO info; + info.cbSize = sizeof(SHELLEXECUTEINFO); + info.lpVerb = _T("open"); + info.fMask = SEE_MASK_NOCLOSEPROCESS | SEE_MASK_NOASYNC | SEE_MASK_WAITFORINPUTIDLE; + info.hwnd = NULL; + info.lpVerb = NULL; + info.lpParameters = NULL; + info.lpDirectory = NULL; + info.nShow = SW_SHOWNORMAL; + info.hInstApp = NULL; + info.lpFile = path; + info.lpParameters = params; + HWND hwnd = NULL; + if (!ShellExecuteEx(&info)) { + return FALSE; + } else { + DWORD infopid = GetProcessId(info.hProcess); + AllowSetForegroundWindow(infopid); + hwnd = GetTopWindow(0); + while (hwnd) { + DWORD pid; + DWORD dwTheardId = ::GetWindowThreadProcessId(hwnd, &pid); + if (pid == infopid) { + SetForegroundWindow(hwnd); + SetActiveWindow(hwnd); + break; + } + hwnd = ::GetNextWindow(hwnd, GW_HWNDNEXT); + } + CloseHandle(info.hProcess); + } + return hwnd; +} diff --git a/launchers/win32/LauncherUtils.h b/launchers/win32/LauncherUtils.h index 47a8fc56c8..a1cc553128 100644 --- a/launchers/win32/LauncherUtils.h +++ b/launchers/win32/LauncherUtils.h @@ -18,17 +18,17 @@ class LauncherUtils { public: - enum ResponseError { - Open = 0, - Connect, - OpenRequest, - SendRequest, - ReceiveRequest, - ReadResponse, - ParsingJSON, - BadCredentials, - NoError - }; + enum ResponseError { + Open = 0, + Connect, + OpenRequest, + SendRequest, + ReceiveRequest, + ReadResponse, + ParsingJSON, + BadCredentials, + NoError + }; enum DeleteDirError { NoErrorDeleting = 0, @@ -37,27 +37,27 @@ public: ErrorDeletingBothDirs }; - struct DownloadThreadData { - int _type; - CString _url; - CString _file; - std::function callback; - // function(type) - void setCallback(std::function fn) { - callback = std::bind(fn, std::placeholders::_1); - } - }; + struct DownloadThreadData { + int _type; + CString _url; + CString _file; + std::function callback; + // function(type) + void setCallback(std::function fn) { + callback = std::bind(fn, std::placeholders::_1); + } + }; - struct UnzipThreadData { - int _type; - std::string _zipFile; - std::string _path; - // function(type, size) - std::function callback; - void setCallback(std::function fn) { - callback = std::bind(fn, std::placeholders::_1, std::placeholders::_2); - } - }; + struct UnzipThreadData { + int _type; + std::string _zipFile; + std::string _path; + // function(type, size) + std::function callback; + void setCallback(std::function fn) { + callback = std::bind(fn, std::placeholders::_1, std::placeholders::_2); + } + }; struct DeleteThreadData { CString _applicationDir; @@ -66,31 +66,32 @@ public: void setCallback(std::function fn) { callback = std::bind(fn, std::placeholders::_1); } }; - static BOOL parseJSON(const CString& jsonTxt, Json::Value& jsonObject); - static ResponseError makeHTTPCall(const CString& callerName, const CString& mainUrl, - const CString& dirUrl, const CString& contentType, - CStringA& postData, CString& response, bool isPost); - static std::string cStringToStd(CString cstring); - static BOOL getFont(const CString& fontName, int fontSize, bool isBold, CFont& fontOut); - static BOOL launchApplication(LPCWSTR lpApplicationName, LPTSTR cmdArgs = _T("")); - static BOOL IsProcessRunning(const wchar_t *processName); - static BOOL insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value); - static BOOL insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value); - static BOOL deleteFileOrDirectory(const CString& dirPath, bool noRecycleBin = true); - static HRESULT CreateLink(LPCWSTR lpszPathObj, LPCSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszArgs = _T("")); - static BOOL hMac256(const CString& message, const char* key, CString& hashOut); - static uint64_t extractZip(const std::string& zipFile, const std::string& path, std::vector& files); - static BOOL deleteRegistryKey(const CString& registryPath); - static BOOL unzipFileOnThread(int type, const std::string& zipFile, const std::string& path, std::function callback); - static BOOL downloadFileOnThread(int type, const CString& url, const CString& file, std::function callback); + static BOOL parseJSON(const CString& jsonTxt, Json::Value& jsonObject); + static ResponseError makeHTTPCall(const CString& callerName, const CString& mainUrl, + const CString& dirUrl, const CString& contentType, + CStringA& postData, CString& response, bool isPost); + static std::string cStringToStd(CString cstring); + static BOOL getFont(const CString& fontName, int fontSize, bool isBold, CFont& fontOut); + static BOOL launchApplication(LPCWSTR lpApplicationName, LPTSTR cmdArgs = _T("")); + static BOOL IsProcessRunning(const wchar_t *processName); + static BOOL insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value); + static BOOL insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value); + static BOOL deleteFileOrDirectory(const CString& dirPath, bool noRecycleBin = true); + static HRESULT CreateLink(LPCWSTR lpszPathObj, LPCSTR lpszPathLink, LPCWSTR lpszDesc, LPCWSTR lpszArgs = _T("")); + static BOOL hMac256(const CString& message, const char* key, CString& hashOut); + static uint64_t extractZip(const std::string& zipFile, const std::string& path, std::vector& files); + static BOOL deleteRegistryKey(const CString& registryPath); + static BOOL unzipFileOnThread(int type, const std::string& zipFile, const std::string& path, std::function callback); + static BOOL downloadFileOnThread(int type, const CString& url, const CString& file, std::function callback); static BOOL deleteDirectoriesOnThread(const CString& applicationDir, const CString& downloadsDir, std::function callback); static CString urlEncodeString(const CString& url); + static HWND executeOnForeground(const CString& path, const CString& params); private: - // Threads - static DWORD WINAPI unzipThread(LPVOID lpParameter); - static DWORD WINAPI downloadThread(LPVOID lpParameter); + // Threads + static DWORD WINAPI unzipThread(LPVOID lpParameter); + static DWORD WINAPI downloadThread(LPVOID lpParameter); static DWORD WINAPI deleteDirectoriesThread(LPVOID lpParameter); }; \ No newline at end of file diff --git a/launchers/win32/res/button.png b/launchers/win32/res/button.png deleted file mode 100644 index 64d7cc8df9..0000000000 Binary files a/launchers/win32/res/button.png and /dev/null differ diff --git a/launchers/win32/res/interface.ico b/launchers/win32/res/interface.ico index cdd4792f56..09a97956a7 100644 Binary files a/launchers/win32/res/interface.ico and b/launchers/win32/res/interface.ico differ diff --git a/libraries/avatars/src/AvatarData.cpp b/libraries/avatars/src/AvatarData.cpp index 942b13c237..a2dde3d651 100755 --- a/libraries/avatars/src/AvatarData.cpp +++ b/libraries/avatars/src/AvatarData.cpp @@ -396,9 +396,10 @@ QByteArray AvatarData::toByteArray(AvatarDataDetail dataDetail, quint64 lastSent } const size_t byteArraySize = AvatarDataPacket::MAX_CONSTANT_HEADER_SIZE + NUM_BYTES_RFC4122_UUID + - AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) + - AvatarDataPacket::maxJointDataSize(_jointData.size()) + - AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()); + AvatarDataPacket::maxFaceTrackerInfoSize(_headData->getBlendshapeCoefficients().size()) + + AvatarDataPacket::maxJointDataSize(_jointData.size()) + + AvatarDataPacket::maxJointDefaultPoseFlagsSize(_jointData.size()) + + AvatarDataPacket::FAR_GRAB_JOINTS_SIZE; if (maxDataSize == 0) { maxDataSize = (int)byteArraySize; diff --git a/libraries/avatars/src/AvatarData.h b/libraries/avatars/src/AvatarData.h index e5131ff94b..498f39cdf9 100755 --- a/libraries/avatars/src/AvatarData.h +++ b/libraries/avatars/src/AvatarData.h @@ -302,6 +302,15 @@ namespace AvatarDataPacket { const size_t AVATAR_LOCAL_POSITION_SIZE = 12; static_assert(sizeof(AvatarLocalPosition) == AVATAR_LOCAL_POSITION_SIZE, "AvatarDataPacket::AvatarLocalPosition size doesn't match."); + PACKED_BEGIN struct HandControllers { + SixByteQuat leftHandRotation; + SixByteTrans leftHandTranslation; + SixByteQuat rightHandRotation; + SixByteTrans rightHandTranslation; + } PACKED_END; + static const size_t HAND_CONTROLLERS_SIZE = 24; + static_assert(sizeof(HandControllers) == HAND_CONTROLLERS_SIZE, "AvatarDataPacket::HandControllers size doesn't match."); + const size_t MAX_CONSTANT_HEADER_SIZE = HEADER_SIZE + AVATAR_GLOBAL_POSITION_SIZE + AVATAR_BOUNDING_BOX_SIZE + @@ -312,17 +321,8 @@ namespace AvatarDataPacket { SENSOR_TO_WORLD_SIZE + ADDITIONAL_FLAGS_SIZE + PARENT_INFO_SIZE + - AVATAR_LOCAL_POSITION_SIZE; - - PACKED_BEGIN struct HandControllers { - SixByteQuat leftHandRotation; - SixByteTrans leftHandTranslation; - SixByteQuat rightHandRotation; - SixByteTrans rightHandTranslation; - } PACKED_END; - static const size_t HAND_CONTROLLERS_SIZE = 24; - static_assert(sizeof(HandControllers) == HAND_CONTROLLERS_SIZE, "AvatarDataPacket::HandControllers size doesn't match."); - + AVATAR_LOCAL_POSITION_SIZE + + HAND_CONTROLLERS_SIZE; // variable length structure follows @@ -1211,6 +1211,12 @@ public: const QString& getDisplayName() const { return _displayName; } const QString& getSessionDisplayName() const { return _sessionDisplayName; } bool getLookAtSnappingEnabled() const { return _lookAtSnappingEnabled; } + + /**jsdoc + * Sets the avatar's skeleton model. + * @function Avatar.setSkeletonModelURL + * @param {string} url - The avatar's FST file. + */ Q_INVOKABLE virtual void setSkeletonModelURL(const QUrl& skeletonModelURL); virtual void setDisplayName(const QString& displayName); diff --git a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h index fa6fff98d7..9139c260ea 100644 --- a/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h +++ b/libraries/entities-renderer/src/RenderablePolyLineEntityItem.h @@ -55,7 +55,7 @@ protected: bool _faceCamera { false }; bool _glow { false }; - size_t _numVertices; + size_t _numVertices { 0 }; gpu::BufferPointer _polylineDataBuffer; gpu::BufferPointer _polylineGeometryBuffer; static std::map, gpu::PipelinePointer> _pipelines; diff --git a/libraries/entities/src/WebEntityItem.cpp b/libraries/entities/src/WebEntityItem.cpp index 0748790df9..b40de7e42a 100644 --- a/libraries/entities/src/WebEntityItem.cpp +++ b/libraries/entities/src/WebEntityItem.cpp @@ -343,4 +343,4 @@ PulsePropertyGroup WebEntityItem::getPulseProperties() const { return resultWithReadLock([&] { return _pulseProperties; }); -} \ No newline at end of file +} diff --git a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp index 1e811653f9..e94d2986ee 100644 --- a/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp +++ b/libraries/gpu-gl-common/src/gpu/gl/GLBackendPipeline.cpp @@ -309,7 +309,7 @@ void GLBackend::setResourceTexture(unsigned int slot, const TexturePointer& reso glActiveTexture(GL_TEXTURE0 + slot); glBindTexture(textureState._target, to); (void)CHECK_GL_ERROR(); - _stats._RSAmountTextureMemoryBounded += (int)object->size(); + _stats._RSAmountTextureMemoryBounded += (uint64_t)object->size(); } else { releaseResourceTexture(slot); diff --git a/libraries/gpu/src/gpu/Context.cpp b/libraries/gpu/src/gpu/Context.cpp index 45ee4263a3..927e872fbc 100644 --- a/libraries/gpu/src/gpu/Context.cpp +++ b/libraries/gpu/src/gpu/Context.cpp @@ -8,6 +8,7 @@ // 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 "Context.h" #include @@ -18,19 +19,28 @@ using namespace gpu; +template +T subWrap(T endValue, T beginValue) { + if (endValue >= beginValue) { + return endValue - beginValue; + } else { + return endValue + ((std::numeric_limits::max() - beginValue) + 1); + } +} + void ContextStats::evalDelta(const ContextStats& begin, const ContextStats& end) { - _ISNumFormatChanges = end._ISNumFormatChanges - begin._ISNumFormatChanges; - _ISNumInputBufferChanges = end._ISNumInputBufferChanges - begin._ISNumInputBufferChanges; - _ISNumIndexBufferChanges = end._ISNumIndexBufferChanges - begin._ISNumIndexBufferChanges; + _ISNumFormatChanges = subWrap(end._ISNumFormatChanges, begin._ISNumFormatChanges); + _ISNumInputBufferChanges = subWrap(end._ISNumInputBufferChanges, begin._ISNumInputBufferChanges); + _ISNumIndexBufferChanges = subWrap(end._ISNumIndexBufferChanges, begin._ISNumIndexBufferChanges); - _RSNumTextureBounded = end._RSNumTextureBounded - begin._RSNumTextureBounded; - _RSAmountTextureMemoryBounded = end._RSAmountTextureMemoryBounded - begin._RSAmountTextureMemoryBounded; + _RSNumTextureBounded = subWrap(end._RSNumTextureBounded, begin._RSNumTextureBounded); + _RSAmountTextureMemoryBounded = subWrap(end._RSAmountTextureMemoryBounded, begin._RSAmountTextureMemoryBounded); - _DSNumAPIDrawcalls = end._DSNumAPIDrawcalls - begin._DSNumAPIDrawcalls; - _DSNumDrawcalls = end._DSNumDrawcalls - begin._DSNumDrawcalls; - _DSNumTriangles= end._DSNumTriangles - begin._DSNumTriangles; + _DSNumAPIDrawcalls = subWrap(end._DSNumAPIDrawcalls, begin._DSNumAPIDrawcalls); + _DSNumDrawcalls = subWrap(end._DSNumDrawcalls, begin._DSNumDrawcalls); + _DSNumTriangles= subWrap(end._DSNumTriangles, begin._DSNumTriangles); - _PSNumSetPipelines = end._PSNumSetPipelines - begin._PSNumSetPipelines; + _PSNumSetPipelines = subWrap(end._PSNumSetPipelines, begin._PSNumSetPipelines); } diff --git a/libraries/gpu/src/gpu/Context.h b/libraries/gpu/src/gpu/Context.h index 7109b3dfeb..1946f447f8 100644 --- a/libraries/gpu/src/gpu/Context.h +++ b/libraries/gpu/src/gpu/Context.h @@ -32,19 +32,19 @@ namespace gpu { struct ContextStats { public: - int _ISNumFormatChanges = 0; - int _ISNumInputBufferChanges = 0; - int _ISNumIndexBufferChanges = 0; + uint32_t _ISNumFormatChanges { 0 }; + uint32_t _ISNumInputBufferChanges { 0 }; + uint32_t _ISNumIndexBufferChanges { 0 }; - int _RSNumResourceBufferBounded = 0; - int _RSNumTextureBounded = 0; - int _RSAmountTextureMemoryBounded = 0; + uint32_t _RSNumResourceBufferBounded { 0 }; + uint32_t _RSNumTextureBounded { 0 }; + uint64_t _RSAmountTextureMemoryBounded { 0 }; - int _DSNumAPIDrawcalls = 0; - int _DSNumDrawcalls = 0; - int _DSNumTriangles = 0; + uint32_t _DSNumAPIDrawcalls { 0 }; + uint32_t _DSNumDrawcalls { 0 }; + uint32_t _DSNumTriangles { 0 }; - int _PSNumSetPipelines = 0; + uint32_t _PSNumSetPipelines { 0 }; ContextStats() {} ContextStats(const ContextStats& stats) = default; diff --git a/libraries/networking/src/LimitedNodeList.cpp b/libraries/networking/src/LimitedNodeList.cpp index 9b1f011680..92a5692ca9 100644 --- a/libraries/networking/src/LimitedNodeList.cpp +++ b/libraries/networking/src/LimitedNodeList.cpp @@ -632,6 +632,7 @@ void LimitedNodeList::processKillNode(ReceivedMessage& message) { } void LimitedNodeList::handleNodeKill(const SharedNodePointer& node, ConnectionID nextConnectionID) { + _nodeDisconnectTimestamp = usecTimestampNow(); qCDebug(networking) << "Killed" << *node; node->stopPingTimer(); emit nodeKilled(node); diff --git a/libraries/networking/src/LimitedNodeList.h b/libraries/networking/src/LimitedNodeList.h index f7ea0ec2ad..bd5e6bd013 100644 --- a/libraries/networking/src/LimitedNodeList.h +++ b/libraries/networking/src/LimitedNodeList.h @@ -108,6 +108,13 @@ public: }; Q_ENUM(ConnectionStep); + + enum ConnectReason : quint32 { + Connect = 0, + SilentDomainDisconnect + }; + Q_ENUM(ConnectReason); + QUuid getSessionUUID() const; void setSessionUUID(const QUuid& sessionUUID); Node::LocalID getSessionLocalID() const; @@ -461,6 +468,9 @@ protected: } std::unordered_map _connectionIDs; + quint64 _nodeConnectTimestamp{ 0 }; + quint64 _nodeDisconnectTimestamp{ 0 }; + ConnectReason _connectReason { Connect }; private slots: void flagTimeForConnectionStep(ConnectionStep connectionStep, quint64 timestamp); diff --git a/libraries/networking/src/NodeList.cpp b/libraries/networking/src/NodeList.cpp index 3a896afe9b..be63956191 100644 --- a/libraries/networking/src/NodeList.cpp +++ b/libraries/networking/src/NodeList.cpp @@ -113,6 +113,8 @@ NodeList::NodeList(char newOwnerType, int socketListenPort, int dtlsListenPort) connect(&_domainHandler, SIGNAL(connectedToDomain(QUrl)), &_keepAlivePingTimer, SLOT(start())); connect(&_domainHandler, &DomainHandler::disconnectedFromDomain, &_keepAlivePingTimer, &QTimer::stop); + connect(&_domainHandler, &DomainHandler::limitOfSilentDomainCheckInsReached, this, [this]() { _connectReason = LimitedNodeList::SilentDomainDisconnect; }); + // 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)); @@ -304,7 +306,8 @@ void NodeList::sendDomainServerCheckIn() { // may be called by multiple threads. if (!_sendDomainServerCheckInEnabled) { - qCDebug(networking_ice) << "Refusing to send a domain-server check in while it is disabled."; + static const QString DISABLED_CHECKIN_DEBUG{ "Refusing to send a domain-server check in while it is disabled." }; + HIFI_FCDEBUG(networking_ice(), DISABLED_CHECKIN_DEBUG); return; } @@ -414,6 +417,16 @@ void NodeList::sendDomainServerCheckIn() { // now add the machine fingerprint auto accountManager = DependencyManager::get(); packetStream << FingerprintUtils::getMachineFingerprint(); + + packetStream << _connectReason; + + if (_nodeDisconnectTimestamp < _nodeConnectTimestamp) { + _nodeDisconnectTimestamp = usecTimestampNow(); + } + quint64 previousConnectionUptime = _nodeConnectTimestamp ? _nodeDisconnectTimestamp - _nodeConnectTimestamp : 0; + + packetStream << previousConnectionUptime; + } packetStream << quint64(duration_cast(system_clock::now().time_since_epoch()).count()); @@ -439,6 +452,16 @@ void NodeList::sendDomainServerCheckIn() { // Send duplicate check-ins in the exponentially increasing sequence 1, 1, 2, 4, ... static const int MAX_CHECKINS_TOGETHER = 20; int outstandingCheckins = _domainHandler.getCheckInPacketsSinceLastReply(); + /* + static const int WARNING_CHECKIN_COUNT = 2; + if (outstandingCheckins > WARNING_CHECKIN_COUNT) { + // We may be headed for a disconnect, as we've written two DomainListRequests without getting anything back. + // In some cases, we've found that nothing is going out on the wire despite not getting any errors from + // sendPacket => writeDatagram, below. In at least some such cases, we've found that the DomainDisconnectRequest + // does go through, so let's at least try to mix it up with a different safe packet. + // TODO: send ICEPing, and later on tell the other nodes to shut up for a moment. + + }*/ int checkinCount = outstandingCheckins > 1 ? std::pow(2, outstandingCheckins - 2) : 1; checkinCount = std::min(checkinCount, MAX_CHECKINS_TOGETHER); for (int i = 1; i < checkinCount; ++i) { @@ -660,6 +683,14 @@ void NodeList::processDomainServerList(QSharedPointer message) quint64 domainServerCheckinProcessingTime; packetStream >> domainServerCheckinProcessingTime; + bool newConnection; + packetStream >> newConnection; + + if (newConnection) { + _nodeConnectTimestamp = usecTimestampNow(); + _connectReason = Connect; + } + qint64 pingLagTime = (now - qint64(connectRequestTimestamp)) / qint64(USECS_PER_MSEC); qint64 domainServerRequestLag = (qint64(domainServerPingSendTime - domainServerCheckinProcessingTime) - qint64(connectRequestTimestamp)) / qint64(USECS_PER_MSEC);; @@ -673,13 +704,6 @@ void NodeList::processDomainServerList(QSharedPointer message) // refuse to process this packet if we aren't currently connected to the DS return; } -#ifdef DEBUG_EVENT_QUEUE - { - int nodeListQueueSize = ::hifi::qt::getEventQueueSize(thread()); - qCDebug(networking) << "DomainList received, pending count =" << _domainHandler.getCheckInPacketsSinceLastReply() - << "NodeList thread event queue size =" << nodeListQueueSize; - } -#endif // warn if ping lag is getting long if (pingLagTime > qint64(MSECS_PER_SECOND)) { diff --git a/libraries/networking/src/ResourceCache.cpp b/libraries/networking/src/ResourceCache.cpp index 44d3d1ee4d..ef8a7b577d 100644 --- a/libraries/networking/src/ResourceCache.cpp +++ b/libraries/networking/src/ResourceCache.cpp @@ -346,7 +346,7 @@ void ResourceCache::setRequestLimit(uint32_t limit) { QSharedPointer ResourceCache::getResource(const QUrl& url, const QUrl& fallback, void* extra, size_t extraHash) { QSharedPointer resource; { - QReadLocker locker(&_resourcesLock); + QWriteLocker locker(&_resourcesLock); auto& resourcesWithExtraHash = _resources[url]; auto resourcesWithExtraHashIter = resourcesWithExtraHash.find(extraHash); if (resourcesWithExtraHashIter != resourcesWithExtraHash.end()) { diff --git a/libraries/networking/src/udt/PacketHeaders.cpp b/libraries/networking/src/udt/PacketHeaders.cpp index 566e1e4946..30066b68e8 100644 --- a/libraries/networking/src/udt/PacketHeaders.cpp +++ b/libraries/networking/src/udt/PacketHeaders.cpp @@ -27,7 +27,7 @@ PacketVersion versionForPacketType(PacketType packetType) { case PacketType::StunResponse: return 17; case PacketType::DomainList: - return static_cast(DomainListVersion::HasTimestamp); + return static_cast(DomainListVersion::HasConnectReason); case PacketType::EntityAdd: case PacketType::EntityClone: case PacketType::EntityEdit: @@ -72,7 +72,7 @@ PacketVersion versionForPacketType(PacketType packetType) { return static_cast(DomainConnectionDeniedVersion::IncludesExtraInfo); case PacketType::DomainConnectRequest: - return static_cast(DomainConnectRequestVersion::HasTimestamp); + return static_cast(DomainConnectRequestVersion::HasReason); case PacketType::DomainServerAddedNode: return static_cast(DomainServerAddedNodeVersion::PermissionsGrid); diff --git a/libraries/networking/src/udt/PacketHeaders.h b/libraries/networking/src/udt/PacketHeaders.h index 903c1f4c93..93a5d4e2b4 100644 --- a/libraries/networking/src/udt/PacketHeaders.h +++ b/libraries/networking/src/udt/PacketHeaders.h @@ -345,7 +345,8 @@ enum class DomainConnectRequestVersion : PacketVersion { HasMACAddress, HasMachineFingerprint, AlwaysHasMachineFingerprint, - HasTimestamp + HasTimestamp, + HasReason }; enum class DomainConnectionDeniedVersion : PacketVersion { @@ -365,7 +366,8 @@ enum class DomainListVersion : PacketVersion { GetUsernameFromUUIDSupport, GetMachineFingerprintFromUUIDSupport, AuthenticationOptional, - HasTimestamp + HasTimestamp, + HasConnectReason }; enum class AudioVersion : PacketVersion { diff --git a/libraries/networking/src/udt/Socket.cpp b/libraries/networking/src/udt/Socket.cpp index bcc2293e1f..c56f276560 100644 --- a/libraries/networking/src/udt/Socket.cpp +++ b/libraries/networking/src/udt/Socket.cpp @@ -61,10 +61,13 @@ void Socket::bind(const QHostAddress& address, quint16 port) { auto sd = _udpSocket.socketDescriptor(); int val = IP_PMTUDISC_DONT; setsockopt(sd, IPPROTO_IP, IP_MTU_DISCOVER, &val, sizeof(val)); -#elif defined(Q_OS_WINDOWS) +#elif defined(Q_OS_WIN) auto sd = _udpSocket.socketDescriptor(); int val = 0; // false - setsockopt(sd, IPPROTO_IP, IP_DONTFRAGMENT, &val, sizeof(val)); + if (setsockopt(sd, IPPROTO_IP, IP_DONTFRAGMENT, (const char *)&val, sizeof(val))) { + auto wsaErr = WSAGetLastError(); + qCWarning(networking) << "Socket::bind Cannot setsockopt IP_DONTFRAGMENT" << wsaErr; + } #endif } } @@ -225,19 +228,25 @@ qint64 Socket::writeDatagram(const char* data, qint64 size, const HifiSockAddr& qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& sockAddr) { + // don't attempt to write the datagram if we're unbound. Just drop it. + // _udpSocket.writeDatagram will return an error anyway, but there are + // potential crashes in Qt when that happens. + if (_udpSocket.state() != QAbstractSocket::BoundState) { + qCDebug(networking) << "Attempt to writeDatagram when in unbound state to" << sockAddr; + return -1; + } qint64 bytesWritten = _udpSocket.writeDatagram(datagram, sockAddr.getAddress(), sockAddr.getPort()); - - if (bytesWritten < 0) { - qCDebug(networking) << "udt::writeDatagram (" << _udpSocket.state() << ") error - " << _udpSocket.error() << "(" << _udpSocket.errorString() << ")"; - + int pending = _udpSocket.bytesToWrite(); + if (bytesWritten < 0 || pending) { + int wsaError = 0; #ifdef WIN32 - int wsaError = WSAGetLastError(); - qCDebug(networking) << "windows socket error " << wsaError; + wsaError = WSAGetLastError(); #endif - + qCDebug(networking) << "udt::writeDatagram (" << _udpSocket.state() << sockAddr << ") error - " << wsaError << _udpSocket.error() << "(" << _udpSocket.errorString() << ")" + << (pending ? "pending bytes:" : "pending:") << pending; #ifdef DEBUG_EVENT_QUEUE int nodeListQueueSize = ::hifi::qt::getEventQueueSize(thread()); - qCDebug(networking) << "Networking queue size - " << nodeListQueueSize; + qCDebug(networking) << "Networking queue size - " << nodeListQueueSize << "writing datagram to" << sockAddr; #endif // DEBUG_EVENT_QUEUE } @@ -245,6 +254,7 @@ qint64 Socket::writeDatagram(const QByteArray& datagram, const HifiSockAddr& soc } Connection* Socket::findOrCreateConnection(const HifiSockAddr& sockAddr, bool filterCreate) { + Lock connectionsLock(_connectionsHashMutex); auto it = _connectionsHash.find(sockAddr); if (it == _connectionsHash.end()) { @@ -284,6 +294,7 @@ void Socket::clearConnections() { return; } + Lock connectionsLock(_connectionsHashMutex); if (_connectionsHash.size() > 0) { // clear all of the current connections in the socket qCDebug(networking) << "Clearing all remaining connections in Socket."; @@ -292,6 +303,7 @@ void Socket::clearConnections() { } void Socket::cleanupConnection(HifiSockAddr sockAddr) { + Lock connectionsLock(_connectionsHashMutex); auto numErased = _connectionsHash.erase(sockAddr); if (numErased > 0) { @@ -449,6 +461,7 @@ void Socket::readPendingDatagrams() { } void Socket::connectToSendSignal(const HifiSockAddr& destinationAddr, QObject* receiver, const char* slot) { + Lock connectionsLock(_connectionsHashMutex); auto it = _connectionsHash.find(destinationAddr); if (it != _connectionsHash.end()) { connect(it->second.get(), SIGNAL(packetSent()), receiver, slot); @@ -465,6 +478,7 @@ void Socket::setConnectionMaxBandwidth(int maxBandwidth) { qInfo() << "Setting socket's maximum bandwith to" << maxBandwidth << "bps. (" << _connectionsHash.size() << "live connections)"; _maxBandwidth = maxBandwidth; + Lock connectionsLock(_connectionsHashMutex); for (auto& pair : _connectionsHash) { auto& connection = pair.second; connection->setMaxBandwidth(_maxBandwidth); @@ -482,6 +496,8 @@ ConnectionStats::Stats Socket::sampleStatsForConnection(const HifiSockAddr& dest Socket::StatsVector Socket::sampleStatsForAllConnections() { StatsVector result; + Lock connectionsLock(_connectionsHashMutex); + result.reserve(_connectionsHash.size()); for (const auto& connectionPair : _connectionsHash) { result.emplace_back(connectionPair.first, connectionPair.second->sampleStats()); @@ -492,6 +508,8 @@ Socket::StatsVector Socket::sampleStatsForAllConnections() { std::vector Socket::getConnectionSockAddrs() { std::vector addr; + Lock connectionsLock(_connectionsHashMutex); + addr.reserve(_connectionsHash.size()); for (const auto& connectionPair : _connectionsHash) { @@ -501,7 +519,13 @@ std::vector Socket::getConnectionSockAddrs() { } void Socket::handleSocketError(QAbstractSocket::SocketError socketError) { - qCDebug(networking) << "udt::Socket (" << _udpSocket.state() << ") error - " << socketError << "(" << _udpSocket.errorString() << ")"; + int wsaError = 0; +#ifdef WIN32 + wsaError = WSAGetLastError(); +#endif + int pending = _udpSocket.bytesToWrite(); + qCDebug(networking) << "udt::Socket (" << _udpSocket.state() << ") error - " << wsaError << socketError << "(" << _udpSocket.errorString() << ")" + << (pending ? "pending bytes:" : "pending:") << pending; #ifdef DEBUG_EVENT_QUEUE int nodeListQueueSize = ::hifi::qt::getEventQueueSize(thread()); qCDebug(networking) << "Networking queue size - " << nodeListQueueSize; diff --git a/libraries/networking/src/udt/Socket.h b/libraries/networking/src/udt/Socket.h index 99266e105e..ad9d6de8b8 100644 --- a/libraries/networking/src/udt/Socket.h +++ b/libraries/networking/src/udt/Socket.h @@ -129,6 +129,7 @@ private: ConnectionCreationFilterOperator _connectionCreationFilterOperator; Mutex _unreliableSequenceNumbersMutex; + Mutex _connectionsHashMutex; std::unordered_map _unfilteredHandlers; std::unordered_map _unreliableSequenceNumbers; diff --git a/libraries/physics/src/PhysicsEngine.cpp b/libraries/physics/src/PhysicsEngine.cpp index 976c547c5e..655edd4e13 100644 --- a/libraries/physics/src/PhysicsEngine.cpp +++ b/libraries/physics/src/PhysicsEngine.cpp @@ -35,7 +35,7 @@ static bool flipNormalsMyAvatarVsBackfacingTriangles(btManifoldPoint& cp, const btVector3* v = triShape->m_vertices1; btVector3 faceNormal = colObj1Wrap->getWorldTransform().getBasis() * btCross(v[1] - v[0], v[2] - v[0]); float nDotF = btDot(faceNormal, cp.m_normalWorldOnB); - if (nDotF <= 0.0f) { + if (nDotF <= 0.0f && faceNormal.length2() > EPSILON) { faceNormal.normalize(); // flip the contact normal to be aligned with the face normal cp.m_normalWorldOnB += -2.0f * nDotF * faceNormal; diff --git a/libraries/platform/src/platform/Profiler.cpp b/libraries/platform/src/platform/Profiler.cpp index f77bbec46b..bd8efb9097 100644 --- a/libraries/platform/src/platform/Profiler.cpp +++ b/libraries/platform/src/platform/Profiler.cpp @@ -12,6 +12,7 @@ #include "Platform.h" #include "PlatformKeys.h" +#include using namespace platform; @@ -124,4 +125,35 @@ bool filterOnProcessors(const platform::json& computer, const platform::json& cp // Not able to profile return false; -} \ No newline at end of file +} + +// Ugly very adhoc capability check to know if a particular hw can REnder with Deferred method or not +// YES for PC windows and linux +// NO for android +// YES on macos EXCEPT for macbookair with gpu intel iris or intel HD 6000 +bool Profiler::isRenderMethodDeferredCapable() { +#if defined(Q_OS_MAC) + auto computer = platform::getComputer(); + const auto computerModel = (computer.count(keys::computer::model) ? computer[keys::computer::model].get() : ""); + + auto gpuInfo = platform::getGPU(0); + const auto gpuModel = (gpuInfo.count(keys::gpu::model) ? gpuInfo[keys::gpu::model].get() : ""); + + + // Macbook air 2018 are a problem + if ((computerModel.find("MacBookAir") != std::string::npos) && (gpuModel.find("Intel HD Graphics 6000") != std::string::npos)) { + return false; + } + + // We know for fact that one INtel Iris is problematic, not enough info yet for sure + // if ((gpuModel.find("Intel Iris ....") != std::string::npos)) { + // return false; + //} + + return true; +#elif defined(Q_OS_ANDROID) + return false; +#else + return true; +#endif +} diff --git a/libraries/platform/src/platform/Profiler.h b/libraries/platform/src/platform/Profiler.h index fea0622c89..c47f2587f2 100644 --- a/libraries/platform/src/platform/Profiler.h +++ b/libraries/platform/src/platform/Profiler.h @@ -28,6 +28,9 @@ public: static const std::array TierNames; static Tier profilePlatform(); + + // Ugly very adhoc capability check to know if a particular hw can REnder with Deferred method or not + static bool isRenderMethodDeferredCapable(); }; } diff --git a/libraries/platform/src/platform/backend/MACOSPlatform.cpp b/libraries/platform/src/platform/backend/MACOSPlatform.cpp index 2607c47d5b..7dbc403783 100644 --- a/libraries/platform/src/platform/backend/MACOSPlatform.cpp +++ b/libraries/platform/src/platform/backend/MACOSPlatform.cpp @@ -18,6 +18,9 @@ #include #include #include + +#include +#include #endif using namespace platform; @@ -33,18 +36,75 @@ void MACOSInstance::enumerateCpu() { } void MACOSInstance::enumerateGpu() { +#ifdef Q_OS_MAC + GPUIdent* ident = GPUIdent::getInstance(); json gpu = {}; + gpu[keys::gpu::vendor] = ident->getName().toUtf8().constData(); gpu[keys::gpu::model] = ident->getName().toUtf8().constData(); gpu[keys::gpu::videoMemory] = ident->getMemory(); gpu[keys::gpu::driver] = ident->getDriver().toUtf8().constData(); _gpu.push_back(gpu); - _display = ident->getOutput(); + +#endif } +void MACOSInstance::enumerateDisplays() { +#ifdef Q_OS_MAC + auto displayID = CGMainDisplayID(); + auto displaySize = CGDisplayScreenSize(displayID); + + const auto MM_TO_IN = 0.0393701; + auto displaySizeWidthInches = displaySize.width * MM_TO_IN; + auto displaySizeHeightInches = displaySize.height * MM_TO_IN; + auto displaySizeDiagonalInches = sqrt(displaySizeWidthInches * displaySizeWidthInches + displaySizeHeightInches * displaySizeHeightInches); + + auto displayBounds = CGDisplayBounds(displayID); + auto displayMaster =CGDisplayIsMain(displayID); + + auto displayUnit =CGDisplayUnitNumber(displayID); + auto displayModel =CGDisplayModelNumber(displayID); + auto displayVendor = CGDisplayVendorNumber(displayID); + auto displaySerial = CGDisplaySerialNumber(displayID); + + auto displayMode = CGDisplayCopyDisplayMode(displayID); + auto displayModeWidth = CGDisplayModeGetPixelWidth(displayMode); + auto displayModeHeight = CGDisplayModeGetPixelHeight(displayMode); + auto displayRefreshrate = CGDisplayModeGetRefreshRate(displayMode); + + CGDisplayModeRelease(displayMode); + + json display = {}; + + display["physicalWidth"] = displaySizeWidthInches; + display["physicalHeight"] = displaySizeHeightInches; + display["physicalDiagonal"] = displaySizeDiagonalInches; + + display["ppi"] = sqrt(displayModeHeight * displayModeHeight + displayModeWidth * displayModeWidth) / displaySizeDiagonalInches; + + display["coordLeft"] = displayBounds.origin.x; + display["coordRight"] = displayBounds.origin.x + displayBounds.size.width; + display["coordTop"] = displayBounds.origin.y; + display["coordBottom"] = displayBounds.origin.y + displayBounds.size.height; + + display["isMaster"] = displayMaster; + + display["unit"] = displayUnit; + display["vendor"] = displayVendor; + display["model"] = displayModel; + display["serial"] = displaySerial; + + display["refreshrate"] =displayRefreshrate; + display["modeWidth"] = displayModeWidth; + display["modeHeight"] = displayModeHeight; + + _display.push_back(display); +#endif +} + void MACOSInstance::enumerateMemory() { json ram = {}; diff --git a/libraries/platform/src/platform/backend/MACOSPlatform.h b/libraries/platform/src/platform/backend/MACOSPlatform.h index 1c66f5d742..4a257d8be5 100644 --- a/libraries/platform/src/platform/backend/MACOSPlatform.h +++ b/libraries/platform/src/platform/backend/MACOSPlatform.h @@ -18,6 +18,7 @@ namespace platform { void enumerateCpu() override; void enumerateMemory() override; void enumerateGpu() override; + void enumerateDisplays() override; void enumerateComputer () override; }; diff --git a/libraries/platform/src/platform/backend/PlatformInstance.cpp b/libraries/platform/src/platform/backend/PlatformInstance.cpp index 164fdb924f..3dd3e5f592 100644 --- a/libraries/platform/src/platform/backend/PlatformInstance.cpp +++ b/libraries/platform/src/platform/backend/PlatformInstance.cpp @@ -18,6 +18,7 @@ bool Instance::enumeratePlatform() { enumerateComputer(); enumerateCpu(); enumerateGpu(); + enumerateDisplays(); enumerateMemory(); // And profile the platform and put the tier in "computer" diff --git a/libraries/platform/src/platform/backend/PlatformInstance.h b/libraries/platform/src/platform/backend/PlatformInstance.h index 52fa9ec3f2..95eb2ef25e 100644 --- a/libraries/platform/src/platform/backend/PlatformInstance.h +++ b/libraries/platform/src/platform/backend/PlatformInstance.h @@ -37,6 +37,7 @@ public: void virtual enumerateCpu()=0; void virtual enumerateMemory()=0; void virtual enumerateGpu()=0; + void virtual enumerateDisplays() {} void virtual enumerateComputer()=0; virtual ~Instance(); diff --git a/libraries/render-utils/src/GeometryCache.cpp b/libraries/render-utils/src/GeometryCache.cpp index 7bd6f88d71..88cca1693b 100644 --- a/libraries/render-utils/src/GeometryCache.cpp +++ b/libraries/render-utils/src/GeometryCache.cpp @@ -939,6 +939,11 @@ void GeometryCache::renderWireSphere(gpu::Batch& batch, const glm::vec4& color) void GeometryCache::renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, const glm::vec2& maxCorner, int majorRows, int majorCols, float majorEdge, int minorRows, int minorCols, float minorEdge, const glm::vec4& color, bool forward, int id) { + + if (majorRows == 0 || majorCols == 0) { + return; + } + Vec2FloatPair majorKey(glm::vec2(majorRows, majorCols), majorEdge); Vec2FloatPair minorKey(glm::vec2(minorRows, minorCols), minorEdge); Vec2FloatPairPair key(majorKey, minorKey); @@ -962,8 +967,8 @@ void GeometryCache::renderGrid(gpu::Batch& batch, const glm::vec2& minCorner, co gridBuffer.edit().period = glm::vec4(majorRows, majorCols, minorRows, minorCols); gridBuffer.edit().offset.x = -(majorEdge / majorRows) / 2; gridBuffer.edit().offset.y = -(majorEdge / majorCols) / 2; - gridBuffer.edit().offset.z = -(minorEdge / minorRows) / 2; - gridBuffer.edit().offset.w = -(minorEdge / minorCols) / 2; + gridBuffer.edit().offset.z = minorRows == 0 ? 0 : -(minorEdge / minorRows) / 2; + gridBuffer.edit().offset.w = minorCols == 0 ? 0 : -(minorEdge / minorCols) / 2; gridBuffer.edit().edge = glm::vec4(glm::vec2(majorEdge), // If rows or columns are not set, do not draw minor gridlines glm::vec2((minorRows != 0 && minorCols != 0) ? minorEdge : 0.0f)); diff --git a/libraries/render/src/render/EngineStats.h b/libraries/render/src/render/EngineStats.h index 46be372d5d..3ccbd40715 100644 --- a/libraries/render/src/render/EngineStats.h +++ b/libraries/render/src/render/EngineStats.h @@ -56,7 +56,7 @@ namespace render { Q_PROPERTY(quint32 frameTextureCount MEMBER frameTextureCount NOTIFY dirty) Q_PROPERTY(quint32 frameTextureRate MEMBER frameTextureRate NOTIFY dirty) - Q_PROPERTY(quint32 frameTextureMemoryUsage MEMBER frameTextureMemoryUsage NOTIFY dirty) + Q_PROPERTY(quint64 frameTextureMemoryUsage MEMBER frameTextureMemoryUsage NOTIFY dirty) Q_PROPERTY(quint32 frameSetPipelineCount MEMBER frameSetPipelineCount NOTIFY dirty) Q_PROPERTY(quint32 frameSetInputFormatCount MEMBER frameSetInputFormatCount NOTIFY dirty) @@ -96,7 +96,7 @@ namespace render { quint32 frameTextureCount{ 0 }; quint32 frameTextureRate{ 0 }; - qint64 frameTextureMemoryUsage{ 0 }; + quint64 frameTextureMemoryUsage{ 0 }; quint32 frameSetPipelineCount{ 0 }; @@ -124,4 +124,4 @@ namespace render { }; } -#endif \ No newline at end of file +#endif diff --git a/libraries/shared/src/GPUIdent.cpp b/libraries/shared/src/GPUIdent.cpp index d5c2f3ec6c..f092a56c17 100644 --- a/libraries/shared/src/GPUIdent.cpp +++ b/libraries/shared/src/GPUIdent.cpp @@ -47,6 +47,7 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) GLint rendererInfoCount; CGLError err = CGLQueryRendererInfo(cglDisplayMask, &rendererInfo, &rendererInfoCount); GLint j, numRenderers = 0, deviceVRAM, bestVRAM = 0; + int bestGPUid = 0; err = CGLQueryRendererInfo(cglDisplayMask, &rendererInfo, &numRenderers); if (0 == err) { // Iterate over all of them and use the figure for the one with the most VRAM, @@ -55,6 +56,7 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) for (j = 0; j < numRenderers; j++) { CGLDescribeRenderer(rendererInfo, j, kCGLRPVideoMemoryMegabytes, &deviceVRAM); if (deviceVRAM > bestVRAM) { + bestGPUid = j; bestVRAM = deviceVRAM; _isValid = true; } @@ -78,6 +80,8 @@ GPUIdent* GPUIdent::ensureQuery(const QString& vendor, const QString& renderer) for (int i = 0; i < parts.size(); ++i) { if (parts[i].toLower().contains("radeon") || parts[i].toLower().contains("nvidia")) { _name=parts[i]; + } else if (i == bestGPUid) { + _name=parts[i]; } } diff --git a/libraries/ui/src/OffscreenUi.cpp b/libraries/ui/src/OffscreenUi.cpp index 1e6a01c187..f0b27904ae 100644 --- a/libraries/ui/src/OffscreenUi.cpp +++ b/libraries/ui/src/OffscreenUi.cpp @@ -27,6 +27,7 @@ #include "VrMenu.h" #include "ui/Logging.h" +#include "ui/ToolbarScriptingInterface.h" #include #include "MainWindow.h" @@ -688,6 +689,10 @@ void OffscreenUi::createDesktop(const QUrl& url) { menuInitializer(_vrMenu); } + + auto toolbarScriptingInterface = DependencyManager::get(); + connect(_desktop, SIGNAL(toolbarVisibleChanged(bool, QString)), toolbarScriptingInterface.data(), SIGNAL(toolbarVisibleChanged(bool, QString))); + auto keyboardFocus = new KeyboardFocusHack(); connect(_desktop, SIGNAL(showDesktop()), this, SIGNAL(showDesktop())); emit desktopReady(); diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index ec0fad5ff0..34cac90a05 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -686,43 +686,52 @@ void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool n return; } -#if !defined(Q_OS_ANDROID) - // if HMD is being worn, allow keyboard to open. allow it to close, HMD or not. - if (!raised || qApp->property(hifi::properties::HMD).toBool()) { - QQuickItem* item = dynamic_cast(object); - if (!item) { - return; - } + bool android = false; +#if defined(Q_OS_ANDROID) + android = true; +#endif - // for future probably makes sense to consider one of the following: - // 1. make keyboard a singleton, which will be dynamically re-parented before showing - // 2. track currently visible keyboard somewhere, allow to subscribe for this signal - // any of above should also eliminate need in duplicated properties and code below + bool hmd = qApp->property(hifi::properties::HMD).toBool(); - while (item) { - // Numeric value may be set in parameter from HTML UI; for QML UI, detect numeric fields here. - 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)); - } - if (item->property("passwordField").isValid()) { - item->setProperty("passwordField", QVariant(passwordField)); - } - - if (raised) { - item->setProperty("keyboardRaised", QVariant(!raised)); - } - - item->setProperty("keyboardRaised", QVariant(raised)); + if (!android || hmd) { + // if HMD is being worn, allow keyboard to open. allow it to close, HMD or not. + if (!raised || hmd) { + QQuickItem* item = dynamic_cast(object); + if (!item) { return; } - item = dynamic_cast(item->parentItem()); + + // for future probably makes sense to consider one of the following: + // 1. make keyboard a singleton, which will be dynamically re-parented before showing + // 2. track currently visible keyboard somewhere, allow to subscribe for this signal + // any of above should also eliminate need in duplicated properties and code below + + while (item) { + // Numeric value may be set in parameter from HTML UI; for QML UI, detect numeric fields here. + numeric = numeric || QString(item->metaObject()->className()).left(7) == "SpinBox"; + + if (item->property("keyboardRaised").isValid()) { + + if (item->property("punctuationMode").isValid()) { + item->setProperty("punctuationMode", QVariant(numeric)); + } + if (item->property("passwordField").isValid()) { + item->setProperty("passwordField", QVariant(passwordField)); + } + + if (hmd && item->property("keyboardEnabled").isValid()) { + item->setProperty("keyboardEnabled", true); + } + + item->setProperty("keyboardRaised", QVariant(raised)); + + return; + } + item = dynamic_cast(item->parentItem()); + } } } -#endif + } void OffscreenQmlSurface::emitScriptEvent(const QVariant& message) { diff --git a/libraries/ui/src/ui/TabletScriptingInterface.cpp b/libraries/ui/src/ui/TabletScriptingInterface.cpp index b8895b6714..a8f319286b 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.cpp +++ b/libraries/ui/src/ui/TabletScriptingInterface.cpp @@ -969,6 +969,46 @@ const QString OBJECT_NAME_KEY = "objectName"; const QString STABLE_ORDER_KEY = "stableOrder"; static int s_stableOrder = 1; +/**jsdoc + * Properties of a tablet button. + * + * @typedef {object} TabletButtonProxy.ButtonProperties + * + * @property {Uuid} uuid - The button ID. Read-only. + * @property {Uuid} objectName - Synonym for uuid. + * @property {number} stableOrder - The order in which the button was created: each button created gets a value incremented by + * one. + * + * @property {string} icon - The url of the default button icon displayed. (50 x 50 pixels. SVG, PNG, or other image format.) + * @property {string} hoverIcon - The url of the button icon displayed when the button is hovered and not active. + * @property {string} activeIcon - The url of the button icon displayed when the button is active. + * @property {string} activeHoverIcon - The url of the button icon displayed when the button is hovered and active. + * @property {string} text - The button caption. + * @property {string} hoverText - The button caption when the button is hovered and not active. + * @property {string} activeText - The button caption when the button is active. + * @property {string} activeHoverText - The button caption when the button is hovered and active. + * @comment {string} defaultCaptionColor="#ffffff" - Internal property. + * @property {string} captionColor="#ffffff" - The color of the button caption. + + * @property {boolean} isActive=false - true if the button is active, false if it isn't. + * @property {boolean} isEntered - true if the button is being hovered, false if it isn't. + * @property {boolean} buttonEnabled=true - true if the button is enabled, false if it is disabled. + * @property {number} sortOrder=100 - Determines the order of the buttons: buttons with lower numbers appear before buttons + * with larger numbers. + * + * @property {boolean} inDebugMode - If true and the tablet is being used, the button's isActive + * state toggles each time the button is clicked. Tablet only. + * + * @comment {object} tabletRoot - Internal tablet-only property. + * @property {object} flickable - Internal tablet-only property. + * @property {object} gridView - Internal tablet-only property. + * @property {number} buttonIndex - Internal tablet-only property. + * + * @comment {number} imageOffOut - Internal toolbar-only property. + * @comment {number} imageOffIn - Internal toolbar-only property. + * @comment {number} imageOnOut - Internal toolbar-only property. + * @comment {number} imageOnIn - Internal toolbar-only property. + */ TabletButtonProxy::TabletButtonProxy(const QVariantMap& properties) : _uuid(QUuid::createUuid()), _stableOrder(++s_stableOrder), @@ -977,6 +1017,7 @@ TabletButtonProxy::TabletButtonProxy(const QVariantMap& properties) : _properties[UUID_KEY] = _uuid; _properties[OBJECT_NAME_KEY] = _uuid.toString(); _properties[STABLE_ORDER_KEY] = _stableOrder; + // Other properties are defined in TabletButton.qml and ToolbarButton.qml. if (QThread::currentThread() != qApp->thread()) { qCWarning(uiLogging) << "Creating tablet button proxy on wrong thread"; } diff --git a/libraries/ui/src/ui/TabletScriptingInterface.h b/libraries/ui/src/ui/TabletScriptingInterface.h index 0b8dc95fa4..afd77a15f6 100644 --- a/libraries/ui/src/ui/TabletScriptingInterface.h +++ b/libraries/ui/src/ui/TabletScriptingInterface.h @@ -39,6 +39,10 @@ class QmlWindowClass; class OffscreenQmlSurface; /**jsdoc + * The Tablet API provides the facilities to work with the system or other tablet. In toolbar mode (Developer > + * UI > Tablet Becomes Toolbar), the tablet's menu buttons are displayed in a toolbar and other tablet content is displayed + * in a dialog. + * * @namespace Tablet * * @hifi-interface @@ -46,6 +50,8 @@ class OffscreenQmlSurface; * @hifi-avatar */ /**jsdoc + * The tabletInterface API provides the facilities to work with the system or other tablet. + * * @namespace tabletInterface * * @hifi-interface @@ -53,12 +59,17 @@ class OffscreenQmlSurface; * @hifi-avatar * * @deprecated This API is deprecated and will be removed. Use {@link Tablet} instead. + * + * @borrows Tablet.getTablet as getTablet + * @borrows Tablet.playSound as playSound + * @borrows Tablet.tabletNotification as tabletNotification */ class TabletScriptingInterface : public QObject, public Dependency { Q_OBJECT public: /**jsdoc + * Standard tablet sounds. * * * @@ -69,7 +80,6 @@ public: * * * - * * *
ValueDescription
2Tablet open.
3Tablet hands in.
4Tablet hands out.
5Last.
* @typedef {number} Tablet.AudioEvents @@ -88,28 +98,26 @@ public: void setToolbarScriptingInterface(ToolbarScriptingInterface* toolbarScriptingInterface) { _toolbarScriptingInterface = toolbarScriptingInterface; } /**jsdoc - * Creates or returns a new TabletProxy and returns it. + * Gets an instance of a tablet. A new tablet is created if one with the specified ID doesn't already exist. * @function Tablet.getTablet - * @param {string} name - Tablet name. - * @returns {TabletProxy} Tablet instance. - */ - /**jsdoc - * Creates or returns a new TabletProxy and returns it. - * @function tabletInterface.getTablet - * @param {string} name - Tablet name. - * @returns {TabletProxy} Tablet instance. + * @param {string} name - A unique name that identifies the tablet. + * @returns {TabletProxy} The tablet instance. + * @example Display the High Fidelity home page on the system tablet. + * var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + * tablet.gotoWebScreen("https://highfidelity.com/"); */ Q_INVOKABLE TabletProxy* getTablet(const QString& tabletId); void preloadSounds(); /**jsdoc + * Plays a standard tablet sound. The sound is played locally (only the user running the script hears it) without a + * position. * @function Tablet.playSound - * @param {Tablet.AudioEvents} sound - */ - /**jsdoc - * @function tabletInterface.playSound - * @param {Tablet.AudioEvents} sound + * @param {Tablet.AudioEvents} sound - The tablet sound to play. + * @example Play a tablet sound. + * var TABLET_BUTTON_CLICK = 0; + * Tablet.playSound(TABLET_BUTTON_CLICK); */ Q_INVOKABLE void playSound(TabletAudioEvents aEvent); @@ -124,15 +132,12 @@ public: QObject* getFlags(); signals: /**jsdoc - * Triggered when a tablet message or dialog is created. + * Triggered when a tablet message or dialog is displayed on the tablet that needs the user's attention. + *

Note: Only triggered if the script is running in the same script engine as the script that created + * the tablet. By default, this means in scripts included as part of the default scripts.

* @function Tablet.tabletNotification * @returns {Signal} */ - /**jsdoc - * Triggered when a tablet message or dialog is created. - * @function tabletInterface.tabletNotification - * @returns {Signal} - */ void tabletNotification(); private: @@ -149,7 +154,9 @@ protected: }; /**jsdoc - * @typedef {object} TabletProxy#ButtonList + * Information on the buttons in the tablet main menu (toolbar in toolbar mode) for use in QML. Has properties and functions + * per http://doc.qt.io/qt-5/qabstractlistmodel.html. + * @typedef {object} TabletProxy.TabletButtonListModel */ class TabletButtonListModel : public QAbstractListModel { Q_OBJECT @@ -203,18 +210,27 @@ private: Q_DECLARE_METATYPE(TabletButtonsProxyModel*); /**jsdoc + * An instance of a tablet. In toolbar mode (Developer > + * UI > Tablet Becomes Toolbar), the tablet's menu buttons are displayed in a toolbar and other tablet content is displayed + * in a dialog. + * + *

Create a new tablet or retrieve an existing tablet using {@link Tablet.getTablet}.

+ * * @class TabletProxy - * + * * @hifi-interface * @hifi-client-entity * @hifi-avatar * - * @property {string} name - Name of this tablet. Read-only. - * @property {boolean} toolbarMode - Used to transition this tablet into and out of toolbar mode. - * When tablet is in toolbar mode, all its buttons will appear in a floating toolbar. - * @property {boolean} landscape - * @property {boolean} tabletShown Read-only. - * @property {TabletProxy#ButtonList} buttons Read-only. + * @property {string} name - A unique name that identifies the tablet. Read-only. + * @property {boolean} toolbarMode - true if the tablet is in toolbar mode, false if it isn't. + * @property {boolean} landscape - true if the tablet is displayed in landscape mode, false if it is + * displayed in portrait mode. + *

Note: This property isn't used in toolbar mode.

+ * @property {boolean} tabletShown - true if the tablet is currently displayed, false if it isn't. + *

Note: This property isn't used in toolbar mode.

+ * @property {TabletProxy.TabletButtonListModel} buttons - Information on the buttons in the tablet main menu (or toolbar in + * toolbar mode) for use in QML. Read-only. */ class TabletProxy : public QObject { Q_OBJECT @@ -235,140 +251,190 @@ public: void unfocus(); /**jsdoc + * Displays the tablet menu. The tablet is opened if it isn't already open. * @function TabletProxy#gotoMenuScreen - * @param {string} [submenu=""] + * @param {string} [submenu=""] - The name of a submenu to display, if any. + * @example Go to the "View" menu. + * tablet.gotoMenuScreen("View"); */ Q_INVOKABLE void gotoMenuScreen(const QString& submenu = ""); /**jsdoc * @function TabletProxy#initialScreen - * @param {string} url + * @param {string} url - URL. + * @deprecated This function is deprecated and will be removed. */ Q_INVOKABLE void initialScreen(const QVariant& url); /**jsdoc - * Transition to the home screen. + * Displays the tablet home screen, if the tablet is open. * @function TabletProxy#gotoHomeScreen */ Q_INVOKABLE void gotoHomeScreen(); /**jsdoc - * Show the specified Web url on the tablet. + * Opens a web page or app on the tablet. * @function TabletProxy#gotoWebScreen - * @param {string} url - URL of web page. - * @param {string} [injectedJavaScriptUrl=""] - URL to an additional JS script to inject into the web page. - * @param {boolean} [loadOtherBase=false] + * @param {string} url - The URL of the web page or app. + * @param {string} [injectedJavaScriptUrl=""] - The URL of JavaScript to inject into the web page. + * @param {boolean} [loadOtherBase=false] - If true, the web page or app is displayed in a frame with "back" + * and "close" buttons. */ Q_INVOKABLE void gotoWebScreen(const QString& url); Q_INVOKABLE void gotoWebScreen(const QString& url, const QString& injectedJavaScriptUrl, bool loadOtherBase = false); /**jsdoc + * Opens a QML app or dialog on the tablet. * @function TabletProxy#loadQMLSource - * @param {string} path - * @param {boolean} [resizable=false] + * @param {string} path - The path of the QML app or dialog. + * @param {boolean} [resizable=false] - true to make the dialog resizable in toolbar mode, false + * to have it not resizable. */ Q_INVOKABLE void loadQMLSource(const QVariant& path, bool resizable = false); // FIXME: This currently relies on a script initializing the tablet (hence the bool denoting success); // it should be initialized internally so it cannot fail /**jsdoc + * Displays a QML dialog over the top of the current dialog, without closing the current dialog. Use + * {@link TabletProxy#popFromStack|popFromStack} to close the dialog. + *

If the current dialog or its ancestors contain a QML StackView with objectName: "stack" and + * function pushSource(path), that function is called; otherwise, + * {@link TabletProxy#loadQMLSource|loadQMLSource} is called. The Create app provides an example of using a QML + * StackView.

* @function TabletProxy#pushOntoStack - * @param {string} path - * @returns {boolean} + * @param {string} path - The path to the dialog's QML. + * @returns {boolean} true if the dialog was successfully opened, false if it wasn't. */ + // edit.js provides an example of using this outside of main menu. Q_INVOKABLE bool pushOntoStack(const QVariant& path); /**jsdoc + * Closes a QML dialog that was displayed using {@link Tablet#pushOntoStack|pushOntoStack} with a dialog implementing a QML + * StackView; otherwise, no action is taken. + *

If using a QML StackView, its popSource() function is called.

* @function TabletProxy#popFromStack */ Q_INVOKABLE void popFromStack(); /**jsdoc + * Opens a QML app or dialog in addition to any current app. In tablet mode, the app or dialog is displayed over the top of + * the current app; in toolbar mode, the app or dialog is opened in a new window. If in tablet mode, the app can be closed + * using {@link TabletProxy#returnToPreviousApp}. * @function TabletProxy#loadQMLOnTop - * @param {string} path + * @param {string} path - The path to the app's QML. */ Q_INVOKABLE void loadQMLOnTop(const QVariant& path); /**jsdoc + * Opens a web app or page in addition to any current app. In tablet mode, the app or page is displayed over the top of the + * current app; in toolbar mode, the app is opened in a new window. If in tablet mode, the app or page can be closed using + * {@link TabletProxy#returnToPreviousApp}. * @function TabletProxy#loadWebScreenOnTop - * @param {string} path - * @param {string} [injectedJavaScriptURL=""] + * @param {string} path - The URL of the web page or HTML app. + * @param {string} [injectedJavaScriptURL=""] - The URL of JavaScript to inject into the web page. */ Q_INVOKABLE void loadWebScreenOnTop(const QVariant& url); Q_INVOKABLE void loadWebScreenOnTop(const QVariant& url, const QString& injectedJavaScriptUrl); /**jsdoc + * Closes the current app and returns to the previous app, if in tablet mode and the current app was loaded using + * {@link TabletProxy#loadQMLOnTop|loadQMLOnTop} or {@link TabletProxy#loadWebScreenOnTop|loadWebScreenOnTop}. * @function TabletProxy#returnToPreviousApp */ Q_INVOKABLE void returnToPreviousApp(); /**jsdoc - * Check if the tablet has a message dialog open. + * Checks if the tablet has a modal, non-modal, or message dialog open. * @function TabletProxy#isMessageDialogOpen - * @returns {boolean} + * @returns {boolean} true if a modal, non-modal, or message dialog is open, false if there isn't. */ Q_INVOKABLE bool isMessageDialogOpen(); /**jsdoc - * Close any open dialogs. - * @function TabletProxy#closeDialog - */ + * Closes any open modal, non-modal, or message dialog, opened by {@link Window.prompt}, {@link Window.promptAsync}, + * {@link Window.openMessageBox}, or similar. + * @function TabletProxy#closeDialog + */ Q_INVOKABLE void closeDialog(); /**jsdoc - * Creates a new button, adds it to this and returns it. + * Adds a new button to the tablet menu. * @function TabletProxy#addButton - * @param {object} properties - Button properties. - * @returns {TabletButtonProxy} + * @param {TabletButtonProxy.ButtonProperties} properties - Button properties. + * @returns {TabletButtonProxy} The button added. + * @example Add a menu button. + * var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + * var button = tablet.addButton({ text: "TEST" }); + * + * button.clicked.connect(function () { + * print("TEST button clicked"); + * }); + * + * Script.scriptEnding.connect(function () { + * tablet.removeButton(button); + * }); */ //FIXME: UI_TABLET_HACK: enumerate the button properties when we figure out what they should be! Q_INVOKABLE TabletButtonProxy* addButton(const QVariant& properties); /**jsdoc - * Removes a button from the tablet. + * Removes a button from the tablet menu. * @function TabletProxy#removeButton - * @param {TabletButtonProxy} button - The button to be removed + * @param {TabletButtonProxy} button - The button to remove. */ Q_INVOKABLE void removeButton(TabletButtonProxy* tabletButtonProxy); /**jsdoc - * Used to send an event to the HTML/JavaScript embedded in the tablet. + * Sends a message to the current web page. To receive the message, the web page's script must connect to the + * EventBridge that is automatically provided to the script: + *
EventBridge.scriptEventReceived.connect(function(message) {
+     *     ...
+     * });
* @function TabletProxy#emitScriptEvent - * @param {object|string} message + * @param {string|object} message - The message to send to the web page. */ Q_INVOKABLE void emitScriptEvent(const QVariant& msg); /**jsdoc - * Used to send an event to the QML embedded in the tablet. + * Sends a message to the current QML page. To receive the message, the QML page must implement a function: + *
function fromScript(message) {
+     *   ...
+     * }
* @function TabletProxy#sendToQml - * @param {object|string} message + * @param {string|object} message - The message to send to the QML page. */ Q_INVOKABLE void sendToQml(const QVariant& msg); /**jsdoc - * Check if the tablet is on the home screen. + * Checks if the tablet is on the home screen. * @function TabletProxy#onHomeScreen - * @returns {boolean} + * @returns {boolean} true if the tablet is on the home screen, false if it isn't. */ Q_INVOKABLE bool onHomeScreen(); /**jsdoc - * Set tablet into or out of landscape mode. + * Sets whether the tablet is displayed in landscape or portrait mode. + *

Note: The setting isn't used in toolbar mode.

* @function TabletProxy#setLandscape - * @param {boolean} landscape - true for landscape, false for portrait. + * @param {boolean} landscape - true to display the tablet in landscape mode, false to display it + * in portrait mode. */ Q_INVOKABLE void setLandscape(bool landscape) { _landscape = landscape; } /**jsdoc + * Gets whether the tablet is displayed in landscape or portrait mode. + *

Note: The setting isn't used in toolbar mode.

* @function TabletProxy#getLandscape - * @returns {boolean} + * @returns {boolean} true if the tablet is displayed in landscape mode, false if it is displayed + * in portrait mode. */ Q_INVOKABLE bool getLandscape() { return _landscape; } /**jsdoc + * Checks if a path is the current app or dialog displayed. * @function TabletProxy#isPathLoaded - * @param {string} path - * @returns {boolean} + * @param {string} path - The path to test. + * @returns {boolean} true if path is the current app or dialog, false if it isn't. */ Q_INVOKABLE bool isPathLoaded(const QVariant& path); @@ -384,44 +450,74 @@ public: signals: /**jsdoc - * Signaled when this tablet receives an event from the html/js embedded in the tablet. + * Triggered when a message from the current HTML web page displayed on the tablet is received. The HTML web page can send + * a message by calling: + *
EventBridge.emitWebEvent(message);
* @function TabletProxy#webEventReceived - * @param {object|string} message + * @param {string|object} message - The message received. * @returns {Signal} */ void webEventReceived(QVariant msg); /**jsdoc - * Signaled when this tablet receives an event from the qml embedded in the tablet. + * Triggered when a message from the current QML page displayed on the tablet is received. The QML page can send a message + * (string or object) by calling:
sendToScript(message);
* @function TabletProxy#fromQml - * @param {object|string} message + * @param {string|object} message - The message received. * @returns {Signal} */ void fromQml(QVariant msg); /**jsdoc - * Signaled when this tablet screen changes. + * Triggered when the tablet's screen changes. * @function TabletProxy#screenChanged - * @param type {string} - "Home", "Web", "Menu", "QML", "Closed". - * @param url {string} - Only valid for Web and QML. + * @param type {string} - The type of the new screen or change: "Home", "Menu", + * "QML", "Web", "Closed", or "Unknown". + * @param url {string} - The url of the page displayed. Only valid for Web and QML. + * @returns {Signal} */ void screenChanged(QVariant type, QVariant url); /**jsdoc - * Signaled when the tablet becomes visible or becomes invisible. - * @function TabletProxy#isTabletShownChanged + * Triggered when the tablet is opened or closed. + *

Note: Doesn't apply in toolbar mode.

+ * @function TabletProxy#tabletShownChanged * @returns {Signal} */ void tabletShownChanged(); /**jsdoc + * Triggered when the tablet's toolbar mode changes. * @function TabletProxy#toolbarModeChanged + * @returns {Signal} + * @example Report when the system tablet's toolbar mode changes. + * var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + * tablet.toolbarModeChanged.connect(function () { + * print("Tablet toolbar mode changed to: " + tablet.toolbarMode); + * }); + * // Use Developer > UI > Tablet Becomes Toolbar to change the toolbar mode. */ void toolbarModeChanged(); protected slots: + + /**jsdoc + * @function TabletProxy#desktopWindowClosed + * @deprecated This function is deprecated and will be removed. + */ void desktopWindowClosed(); + + /**jsdoc + * @function TabletProxy#emitWebEvent + * @param {object|string} message - Message + * @deprecated This function is deprecated and will be removed. + */ void emitWebEvent(const QVariant& msg); + + /**jsdoc + * @function TabletProxy#onTabletShown + * @deprecated This function is deprecated and will be removed. + */ void onTabletShown(); protected: @@ -452,14 +548,20 @@ private: Q_DECLARE_METATYPE(TabletProxy*); /**jsdoc + * A tablet button. In toolbar mode (Developer > UI > Tablet Becomes Toolbar), the tablet button is displayed on the + * toolbar. + * + *

Create a new button using {@link TabletProxy#addButton}.

+ * * @class TabletButtonProxy * * @hifi-interface * @hifi-client-entity * @hifi-avatar * - * @property {Uuid} uuid - Uniquely identifies this button. Read-only. - * @property {TabletButtonProxy.ButtonProperties} properties + * @property {Uuid} uuid - The ID of the button. Read-only. + * @property {TabletButtonProxy.ButtonProperties} properties - The current values of the button's properties. Only properties + * that have been set during button creation or subsequently edited are returned. Read-only. */ class TabletButtonProxy : public QObject { Q_OBJECT @@ -472,28 +574,66 @@ public: QUuid getUuid() const { return _uuid; } /**jsdoc - * Returns the current value of this button's properties. + * Gets the current values of the button's properties. Only properties that have been set during button creation or + * subsequently edited are returned. * @function TabletButtonProxy#getProperties - * @returns {TabletButtonProxy.ButtonProperties} + * @returns {TabletButtonProxy.ButtonProperties} The button properties. + * @example Report a test button's properties. + * var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + * var button = tablet.addButton({ text: "TEST" }); + * + * var properties = button.getProperties(); + * print("TEST button properties: " + JSON.stringify(properties)); + * + * Script.scriptEnding.connect(function () { + * tablet.removeButton(button); + * }); */ Q_INVOKABLE QVariantMap getProperties(); /**jsdoc - * Replace the values of some of this button's properties. + * Changes the values of the button's properties. * @function TabletButtonProxy#editProperties - * @param {TabletButtonProxy.ButtonProperties} properties - Set of properties to change. + * @param {TabletButtonProxy.ButtonProperties} properties - The properties to change. + * @example Set a button's hover text after a delay. + * var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + * var button = tablet.addButton({ text: "TEST" }); + * + * button.propertiesChanged.connect(function () { + * print("TEST button properties changed"); + * }); + * + * Script.setTimeout(function () { + * button.editProperties({ text: "CHANGED" }); + * }, 2000); + * + * Script.scriptEnding.connect(function () { + * tablet.removeButton(button); + * }); */ Q_INVOKABLE void editProperties(const QVariantMap& properties); signals: /**jsdoc - * Triggered when this button has been clicked on by the user. + * Triggered when the button is clicked. * @function TabletButtonProxy#clicked * @returns {Signal} + * @example Report a menu button click. + * var tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system"); + * var button = tablet.addButton({ text: "TEST" }); + * + * button.clicked.connect(function () { + * print("TEST button clicked"); + * }); + * + * Script.scriptEnding.connect(function () { + * tablet.removeButton(button); + * }); */ void clicked(); /**jsdoc + * Triggered when a button's properties are changed. * @function TabletButtonProxy#propertiesChanged * @returns {Signal} */ @@ -503,20 +643,6 @@ protected: QUuid _uuid; int _stableOrder; - /**jsdoc - * @typedef {object} TabletButtonProxy.ButtonProperties - * @property {string} icon - URL to button icon. (50 x 50) - * @property {string} hoverIcon - URL to button icon, displayed during mouse hover. (50 x 50) - * @property {string} activeHoverIcon - URL to button icon used when button is active, and during mouse hover. (50 x 50) - * @property {string} activeIcon - URL to button icon used when button is active. (50 x 50) - * @property {string} text - Button caption. - * @property {string} hoverText - Button caption when button is not-active but during mouse hover. - * @property {string} activeText - Button caption when button is active. - * @property {string} activeHoverText - Button caption when button is active and during mouse hover. - * @property {boolean} isActive - true when button is active. - * @property {number} sortOrder - Determines sort order on tablet. lower numbers will appear before larger numbers. - * Default is 100. - */ // FIXME: There are additional properties. QVariantMap _properties; }; diff --git a/libraries/ui/src/ui/ToolbarScriptingInterface.h b/libraries/ui/src/ui/ToolbarScriptingInterface.h index 409ea28fdc..952d3cce95 100644 --- a/libraries/ui/src/ui/ToolbarScriptingInterface.h +++ b/libraries/ui/src/ui/ToolbarScriptingInterface.h @@ -150,6 +150,9 @@ public: * @returns {ToolbarProxy} */ Q_INVOKABLE ToolbarProxy* getToolbar(const QString& toolbarId); + +signals: + void toolbarVisibleChanged(bool isVisible, QString toolbarName); }; diff --git a/scripts/developer/utilities/render/lodWindow.js b/scripts/developer/utilities/render/lodWindow.js new file mode 100644 index 0000000000..2f6b403a48 --- /dev/null +++ b/scripts/developer/utilities/render/lodWindow.js @@ -0,0 +1,5 @@ +var window = Desktop.createWindow(Script.resolvePath('./lod.qml'), { + title: "LOD Setup", + presentationMode: Desktop.PresentationMode.NATIVE, + size: {x: 350, y: 500} +}); diff --git a/scripts/developer/utilities/render/luci.qml b/scripts/developer/utilities/render/luci.qml index 2dc8fda081..98fd3039d1 100644 --- a/scripts/developer/utilities/render/luci.qml +++ b/scripts/developer/utilities/render/luci.qml @@ -26,8 +26,9 @@ Rectangle { color: global.color ScrollView { - id: control + id: scrollView anchors.fill: parent + contentWidth: parent.width clip: true Column { diff --git a/scripts/developer/utilities/render/performanceSetup.qml b/scripts/developer/utilities/render/performanceSetup.qml index ab00d31f2b..4654736f72 100644 --- a/scripts/developer/utilities/render/performanceSetup.qml +++ b/scripts/developer/utilities/render/performanceSetup.qml @@ -24,9 +24,12 @@ Rectangle { color: global.colorBack ScrollView { + id: scrollView anchors.fill: parent + contentWidth: parent.width clip: true - Column { + + Column { anchors.left: parent.left anchors.right: parent.right @@ -35,8 +38,6 @@ Rectangle { isUnfold: true panelFrameData: Component { PerformanceSettings { - anchors.left: parent.left - anchors.right: parent.right } } } @@ -45,8 +46,6 @@ Rectangle { isUnfold: true panelFrameData: Component { RenderSettings { - anchors.left: parent.left - anchors.right: parent.right } } } @@ -54,8 +53,6 @@ Rectangle { label: "Platform" panelFrameData: Component { Platform { - anchors.left: parent.left - anchors.right: parent.right } } } diff --git a/scripts/simplifiedUI/system/progress.js b/scripts/simplifiedUI/system/progress.js index b373612790..a641dd4556 100644 --- a/scripts/simplifiedUI/system/progress.js +++ b/scripts/simplifiedUI/system/progress.js @@ -83,9 +83,7 @@ // The initial delay cooldown keeps us from tracking progress before the allotted time // has passed. INITIAL_DELAY_COOLDOWN_TIME = 1000, - initialDelayCooldown = 0, - - isInInterstitialMode = false; + initialDelayCooldown = 0; function fade() { @@ -267,7 +265,7 @@ // Update state if (!visible) { // Not visible because no recent downloads - if ((displayProgress < 100 || gpuTextures > 0) && !isInInterstitialMode && !isInterstitialOverlaysVisible) { // Have started downloading so fade in + if (displayProgress < 100 || gpuTextures > 0) { // Have started downloading so fade in visible = true; alphaDelta = ALPHA_DELTA_IN; fadeTimer = Script.setInterval(fade, FADE_INTERVAL); @@ -307,9 +305,6 @@ } else { x = x * BAR_HMD_REPEAT; } - if (isInInterstitialMode || isInterstitialOverlaysVisible) { - visible = false; - } // Update progress bar Overlays.editOverlay(barDesktop.overlay, { @@ -349,10 +344,6 @@ } } - function interstitialModeChanged(inMode) { - isInInterstitialMode = inMode; - } - function setUp() { var is4k = Window.innerWidth > 3000; @@ -378,7 +369,6 @@ } setUp(); - Window.interstitialModeChanged.connect(interstitialModeChanged); GlobalServices.downloadInfoChanged.connect(onDownloadInfoChanged); GlobalServices.updateDownloadInfo(); Script.setInterval(update, 1000 / 60); diff --git a/scripts/simplifiedUI/ui/simplifiedUI.js b/scripts/simplifiedUI/ui/simplifiedUI.js index cdf6a9591a..70679b09bd 100644 --- a/scripts/simplifiedUI/ui/simplifiedUI.js +++ b/scripts/simplifiedUI/ui/simplifiedUI.js @@ -415,7 +415,7 @@ function getInputDeviceMutedOverlayTopY() { var inputDeviceMutedOverlay = false; var INPUT_DEVICE_MUTED_OVERLAY_DEFAULT_X_PX = 353; var INPUT_DEVICE_MUTED_OVERLAY_DEFAULT_Y_PX = 95; -var INPUT_DEVICE_MUTED_MARGIN_BOTTOM_PX = 20; +var INPUT_DEVICE_MUTED_MARGIN_BOTTOM_PX = 20 + TOP_BAR_HEIGHT_PX; function updateInputDeviceMutedOverlay(isMuted) { if (isMuted) { var props = { @@ -457,12 +457,21 @@ function onGeometryChanged(rect) { } } -function ensureFirstPersonCameraInHMD(isHMDMode) { +function onDisplayModeChanged(isHMDMode) { if (isHMDMode) { Camera.setModeString("first person"); } } +function onToolbarVisibleChanged(isVisible, toolbarName) { + if (isVisible && toolbarName == TOOLBAR_NAME && !Settings.getValue("simplifiedUI/keepExistingUIAndScripts", false)) { + var toolbar = Toolbars.getToolbar(toolbarName); + if (toolbar) { + toolbar.writeProperty("visible", false); + } + } +} + function onStatusChanged() { sendLocalStatusToQml(); @@ -490,7 +499,9 @@ function startup() { if (!HMD.active) { var toolbar = Toolbars.getToolbar(TOOLBAR_NAME); - toolbar.writeProperty("visible", false); + if (toolbar) { + toolbar.writeProperty("visible", false); + } } } @@ -505,11 +516,12 @@ function startup() { updateOutputDeviceMutedOverlay(isOutputMuted()); Audio.mutedDesktopChanged.connect(onDesktopInputDeviceMutedChanged); Window.geometryChanged.connect(onGeometryChanged); - HMD.displayModeChanged.connect(ensureFirstPersonCameraInHMD); + HMD.displayModeChanged.connect(onDisplayModeChanged); Audio.avatarGainChanged.connect(maybeUpdateOutputDeviceMutedOverlay); Audio.localInjectorGainChanged.connect(maybeUpdateOutputDeviceMutedOverlay); Audio.serverInjectorGainChanged.connect(maybeUpdateOutputDeviceMutedOverlay); Audio.systemInjectorGainChanged.connect(maybeUpdateOutputDeviceMutedOverlay); + Toolbars.toolbarVisibleChanged.connect(onToolbarVisibleChanged); oldShowAudioTools = AvatarInputs.showAudioTools; AvatarInputs.showAudioTools = false; @@ -535,7 +547,9 @@ function shutdown() { if (!HMD.active) { var toolbar = Toolbars.getToolbar(TOOLBAR_NAME); - toolbar.writeProperty("visible", true); + if (toolbar) { + toolbar.writeProperty("visible", true); + } } } @@ -559,11 +573,12 @@ function shutdown() { Audio.mutedDesktopChanged.disconnect(onDesktopInputDeviceMutedChanged); Window.geometryChanged.disconnect(onGeometryChanged); - HMD.displayModeChanged.disconnect(ensureFirstPersonCameraInHMD); + HMD.displayModeChanged.disconnect(onDisplayModeChanged); Audio.avatarGainChanged.disconnect(maybeUpdateOutputDeviceMutedOverlay); Audio.localInjectorGainChanged.disconnect(maybeUpdateOutputDeviceMutedOverlay); Audio.serverInjectorGainChanged.disconnect(maybeUpdateOutputDeviceMutedOverlay); Audio.systemInjectorGainChanged.disconnect(maybeUpdateOutputDeviceMutedOverlay); + Toolbars.toolbarVisibleChanged.disconnect(onToolbarVisibleChanged); AvatarInputs.showAudioTools = oldShowAudioTools; AvatarInputs.showBubbleTools = oldShowBubbleTools; diff --git a/scripts/system/libraries/entityList.js b/scripts/system/libraries/entityList.js index d19da2b342..6498c92f17 100644 --- a/scripts/system/libraries/entityList.js +++ b/scripts/system/libraries/entityList.js @@ -10,7 +10,7 @@ /* global EntityListTool, Tablet, selectionManager, Entities, Camera, MyAvatar, Vec3, Menu, Messages, cameraManager, MENU_EASE_ON_FOCUS, deleteSelectedEntities, toggleSelectedEntitiesLocked, toggleSelectedEntitiesVisible, - keyUpEventFromUIWindow */ + keyUpEventFromUIWindow, Script, SelectionDisplay, SelectionManager, Clipboard */ var PROFILING_ENABLED = false; var profileIndent = ''; @@ -148,6 +148,20 @@ EntityListTool = function(shouldUseEditTabletApp) { return value !== undefined ? value : ""; } + function entityIsBaked(properties) { + if (properties.type === "Model") { + var lowerModelURL = properties.modelURL.toLowerCase(); + return lowerModelURL.endsWith(".baked.fbx") || lowerModelURL.endsWith(".baked.fst"); + } else if (properties.type === "Zone") { + var lowerSkyboxURL = properties.skybox ? properties.skybox.url.toLowerCase() : ""; + var lowerAmbientURL = properties.ambientLight ? properties.ambientLight.ambientURL.toLowerCase() : ""; + return (lowerSkyboxURL === "" || lowerSkyboxURL.endsWith(".texmeta.json")) && + (lowerAmbientURL === "" || lowerAmbientURL.endsWith(".texmeta.json")); + } else { + return false; + } + } + that.sendUpdate = function() { PROFILE('Script-sendUpdate', function() { var entities = []; @@ -164,7 +178,8 @@ EntityListTool = function(shouldUseEditTabletApp) { var cameraPosition = Camera.position; PROFILE("getMultipleProperties", function () { var multipleProperties = Entities.getMultipleEntityProperties(ids, ['name', 'type', 'locked', - 'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script', 'certificateID']); + 'visible', 'renderInfo', 'modelURL', 'materialURL', 'imageURL', 'script', 'certificateID', + 'skybox.url', 'ambientLight.url']); for (var i = 0; i < multipleProperties.length; i++) { var properties = multipleProperties[i]; @@ -193,7 +208,7 @@ EntityListTool = function(shouldUseEditTabletApp) { valueIfDefined(properties.renderInfo.texturesSize) : ""), hasTransparent: (properties.renderInfo !== undefined ? valueIfDefined(properties.renderInfo.hasTransparent) : ""), - isBaked: properties.type === "Model" ? url.toLowerCase().endsWith(".baked.fbx") : false, + isBaked: entityIsBaked(properties), drawCalls: (properties.renderInfo !== undefined ? valueIfDefined(properties.renderInfo.drawCalls) : ""), hasScript: properties.script !== "" diff --git a/tools/ci-scripts/postbuild.py b/tools/ci-scripts/postbuild.py index b6593b1cf3..00b3007104 100644 --- a/tools/ci-scripts/postbuild.py +++ b/tools/ci-scripts/postbuild.py @@ -3,6 +3,7 @@ import os import sys import shutil import zipfile +import base64 SOURCE_PATH = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '..', '..')) # FIXME move the helper python modules somewher other than the root of the repo @@ -111,10 +112,43 @@ def fixupWinZip(filename): print("Replacing {} with fixed {}".format(fullPath, outFullPath)) shutil.move(outFullPath, fullPath) -def buildLightLauncher(): - # FIXME remove once MFC is enabled on the windows build hosts - if sys.platform == 'win32': +def signBuild(executablePath): + if sys.platform != 'win32': + print('Skipping signing because platform is not win32') return + + RELEASE_TYPE = os.getenv("RELEASE_TYPE", "") + if RELEASE_TYPE != "PRODUCTION": + print('Skipping signing because RELEASE_TYPE "{}" != "PRODUCTION"'.format(RELEASE_TYPE)) + return + + HF_PFX_FILE = os.getenv("HF_PFX_FILE", "") + if HF_PFX_FILE == "": + print('Skipping signing because HF_PFX_FILE is empty') + return + + HF_PFX_PASSPHRASE = os.getenv("HF_PFX_PASSPHRASE", "") + if HF_PFX_PASSPHRASE == "": + print('Skipping signing because HF_PFX_PASSPHRASE is empty') + return + + # FIXME use logic similar to the SetPackagingParameteres.cmake to locate the executable + SIGN_TOOL = "C:/Program Files (x86)/Windows Kits/10/bin/10.0.17763.0/x64/signtool.exe" + # sign the launcher executable + print("Signing {}".format(executablePath)) + hifi_utils.executeSubprocess([ + SIGN_TOOL, + 'sign', + '/fd', 'sha256', + '/f', HF_PFX_FILE, + '/p', HF_PFX_PASSPHRASE, + '/tr', 'http://sha256timestamp.ws.symantec.com/sha256/timestamp', + '/td', 'SHA256', + executablePath + ]) + + +def buildLightLauncher(): launcherSourcePath = os.path.join(SOURCE_PATH, 'launchers', sys.platform) launcherBuildPath = os.path.join(BUILD_PATH, 'launcher') if not os.path.exists(launcherBuildPath): @@ -144,13 +178,16 @@ def buildLightLauncher(): launcherDestFile = os.path.join(BUILD_PATH, "{}.dmg".format(computeArchiveName('Launcher'))) launcherSourceFile = os.path.join(launcherBuildPath, "HQ Launcher.dmg") elif sys.platform == 'win32': - # FIXME launcherDestFile = os.path.join(BUILD_PATH, "{}.exe".format(computeArchiveName('Launcher'))) - launcherSourceFile = os.path.join(launcherBuildPath, "Launcher.exe") + launcherSourceFile = os.path.join(launcherBuildPath, "Release", "HQLauncher.exe") + print("Moving {} to {}".format(launcherSourceFile, launcherDestFile)) shutil.move(launcherSourceFile, launcherDestFile) + signBuild(launcherDestFile) + +# Main for wipePath in WIPE_PATHS: wipeClientBuildPath(wipePath)