diff --git a/interface/resources/qml/controls-uit/Keyboard.qml b/interface/resources/qml/controls-uit/Keyboard.qml index f2ccb6973f..8d6634c9b4 100644 --- a/interface/resources/qml/controls-uit/Keyboard.qml +++ b/interface/resources/qml/controls-uit/Keyboard.qml @@ -13,6 +13,7 @@ import "." Rectangle { id: keyboardBase + objectName: "keyboard" anchors.left: parent.left anchors.right: parent.right @@ -27,6 +28,8 @@ Rectangle { readonly property int mirrorTextHeight: keyboardRowHeight + property bool password: false + property alias mirroredText: mirrorText.text property bool showMirrorText: true readonly property int raisedHeight: 200 @@ -112,16 +115,20 @@ Rectangle { color: "#252525" anchors.horizontalCenter: parent.horizontalCenter - TextEdit { + TextInput { id: mirrorText visible: showMirrorText - size: 13.5 + FontLoader { id: ralewaySemiBold; source: "../../fonts/Raleway-SemiBold.ttf"; } + font.family: ralewaySemiBold.name + font.pointSize: 13.5 + verticalAlignment: Text.AlignVCenter horizontalAlignment: Text.AlignHCenter color: "#FFFFFF"; anchors.fill: parent wrapMode: Text.WordWrap - readOnly: false // we need to leave this property read-only to allow control to accept QKeyEvent + readOnly: false // we need this to allow control to accept QKeyEvent selectByMouse: false + echoMode: password ? TextInput.Password : TextInput.Normal Keys.onPressed: { if (event.key == Qt.Key_Return || event.key == Qt.Key_Space) { diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp index e5d5e696a1..f9ad6ec7ff 100644 --- a/interface/src/Application.cpp +++ b/interface/src/Application.cpp @@ -207,6 +207,17 @@ #if defined(Q_OS_WIN) #include +#ifdef DEBUG_EVENT_QUEUE +// This is a HACK that uses private headers included with the qt source distrubution. +// To use this feature you need to add these directores to your include path: +// E:/Qt/5.9.1/Src/qtbase/include/QtCore/5.9.1/QtCore +// E:/Qt/5.9.1/Src/qtbase/include/QtCore/5.9.1 +#define QT_BOOTSTRAPPED +#include +#include +#undef QT_BOOTSTRAPPED +#endif + extern "C" { _declspec(dllexport) DWORD NvOptimusEnablement = 0x00000001; } @@ -264,9 +275,7 @@ private: switch ((int)event->type()) { case ApplicationEvent::Render: render(); - // Ensure we never back up the render events. Each render should be triggered only in response - // to the NEXT render event after the last render occured - QCoreApplication::removePostedEvents(this, ApplicationEvent::Render); + qApp->_pendingRenderEvent.store(false); return true; default: @@ -2712,9 +2721,14 @@ bool Application::importFromZIP(const QString& filePath) { return true; } +// thread-safe void Application::onPresent(quint32 frameCount) { - postEvent(this, new QEvent((QEvent::Type)ApplicationEvent::Idle), Qt::HighEventPriority); - if (_renderEventHandler && !isAboutToQuit()) { + bool expected = false; + if (_pendingIdleEvent.compare_exchange_strong(expected, true)) { + postEvent(this, new QEvent((QEvent::Type)ApplicationEvent::Idle), Qt::HighEventPriority); + } + expected = false; + if (_renderEventHandler && !isAboutToQuit() && _pendingRenderEvent.compare_exchange_strong(expected, true)) { postEvent(_renderEventHandler, new QEvent((QEvent::Type)ApplicationEvent::Render)); } } @@ -2781,7 +2795,26 @@ bool Application::handleFileOpenEvent(QFileOpenEvent* fileEvent) { return false; } +#ifdef DEBUG_EVENT_QUEUE +static int getEventQueueSize(QThread* thread) { + auto threadData = QThreadData::get2(thread); + QMutexLocker locker(&threadData->postEventList.mutex); + return threadData->postEventList.size(); +} + +static void dumpEventQueue(QThread* thread) { + auto threadData = QThreadData::get2(thread); + QMutexLocker locker(&threadData->postEventList.mutex); + qDebug() << "AJT: event list, size =" << threadData->postEventList.size(); + for (auto& postEvent : threadData->postEventList) { + QEvent::Type type = (postEvent.event ? postEvent.event->type() : QEvent::None); + qDebug() << "AJT: " << type; + } +} +#endif // DEBUG_EVENT_QUEUE + bool Application::event(QEvent* event) { + if (!Menu::getInstance()) { return false; } @@ -2801,8 +2834,18 @@ bool Application::event(QEvent* event) { // see (windowMinimizedChanged) case ApplicationEvent::Idle: idle(); - // Don't process extra idle events that arrived in the event queue while we were doing this idle - QCoreApplication::removePostedEvents(this, ApplicationEvent::Idle); + +#ifdef DEBUG_EVENT_QUEUE + { + int count = getEventQueueSize(QThread::currentThread()); + if (count > 400) { + dumpEventQueue(QThread::currentThread()); + } + } +#endif // DEBUG_EVENT_QUEUE + + _pendingIdleEvent.store(false); + return true; case QEvent::MouseMove: @@ -7203,7 +7246,7 @@ void Application::updateDisplayMode() { _offscreenContext->makeCurrent(); getApplicationCompositor().setDisplayPlugin(newDisplayPlugin); _displayPlugin = newDisplayPlugin; - connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent); + connect(_displayPlugin.get(), &DisplayPlugin::presented, this, &Application::onPresent, Qt::DirectConnection); auto desktop = offscreenUi->getDesktop(); if (desktop) { desktop->setProperty("repositionLocked", wasRepositionLocked); diff --git a/interface/src/Application.h b/interface/src/Application.h index 772646f379..b6c09bbd87 100644 --- a/interface/src/Application.h +++ b/interface/src/Application.h @@ -717,5 +717,8 @@ private: LaserPointerManager _laserPointerManager; friend class RenderEventHandler; + + std::atomic _pendingIdleEvent { false }; + std::atomic _pendingRenderEvent { false }; }; #endif // hifi_Application_h diff --git a/interface/src/Application_render.cpp b/interface/src/Application_render.cpp index 541197a660..44d9dfee03 100644 --- a/interface/src/Application_render.cpp +++ b/interface/src/Application_render.cpp @@ -72,6 +72,12 @@ void Application::paintGL() { { QMutexLocker viewLocker(&_renderArgsMutex); renderArgs = _appRenderArgs._renderArgs; + + // don't render if there is no context. + if (!_appRenderArgs._renderArgs._context) { + return; + } + HMDSensorPose = _appRenderArgs._headPose; eyeToWorld = _appRenderArgs._eyeToWorld; sensorToWorld = _appRenderArgs._sensorToWorld; diff --git a/libraries/audio/src/InboundAudioStream.cpp b/libraries/audio/src/InboundAudioStream.cpp index da8997895c..72acc7fcf6 100644 --- a/libraries/audio/src/InboundAudioStream.cpp +++ b/libraries/audio/src/InboundAudioStream.cpp @@ -46,6 +46,10 @@ static const int STATS_FOR_STATS_PACKET_WINDOW_SECONDS = 30; // _currentJitterBufferFrames is updated with the time-weighted avg and the running time-weighted avg is reset. static const quint64 FRAMES_AVAILABLE_STAT_WINDOW_USECS = 10 * USECS_PER_SECOND; +// When the audio codec is switched, temporary codec mismatch is expected due to packets in-flight. +// A SelectedAudioFormat packet is not sent until this threshold is exceeded. +static const int MAX_MISMATCHED_AUDIO_CODEC_COUNT = 10; + InboundAudioStream::InboundAudioStream(int numChannels, int numFrames, int numBlocks, int numStaticJitterBlocks) : _ringBuffer(numChannels * numFrames, numBlocks), _numChannels(numChannels), @@ -153,6 +157,7 @@ int InboundAudioStream::parseData(ReceivedMessage& message) { // If we recieved a SilentAudioFrame from our sender, we might want to drop // some of the samples in order to catch up to our desired jitter buffer size. writeDroppableSilentFrames(networkFrames); + } else { // note: PCM and no codec are identical bool selectedPCM = _selectedCodecName == "pcm" || _selectedCodecName == ""; @@ -160,20 +165,33 @@ int InboundAudioStream::parseData(ReceivedMessage& message) { if (codecInPacket == _selectedCodecName || (packetPCM && selectedPCM)) { auto afterProperties = message.readWithoutCopy(message.getBytesLeftToRead()); parseAudioData(message.getType(), afterProperties); + _mismatchedAudioCodecCount = 0; + } else { - qDebug(audio) << "Codec mismatch: expected" << _selectedCodecName << "got" << codecInPacket << "writing silence"; + _mismatchedAudioCodecCount++; + qDebug(audio) << "Codec mismatch: expected" << _selectedCodecName << "got" << codecInPacket; - // Since the data in the stream is using a codec that we aren't prepared for, - // we need to let the codec know that we don't have data for it, this will - // allow the codec to interpolate missing data and produce a fade to silence. - lostAudioData(1); - - // inform others of the mismatch - auto sendingNode = DependencyManager::get()->nodeWithUUID(message.getSourceID()); - if (sendingNode) { - emit mismatchedAudioCodec(sendingNode, _selectedCodecName, codecInPacket); + if (packetPCM) { + // If there are PCM packets in-flight after the codec is changed, use them. + auto afterProperties = message.readWithoutCopy(message.getBytesLeftToRead()); + _ringBuffer.writeData(afterProperties.data(), afterProperties.size()); + } else { + // Since the data in the stream is using a codec that we aren't prepared for, + // we need to let the codec know that we don't have data for it, this will + // allow the codec to interpolate missing data and produce a fade to silence. + lostAudioData(1); } + if (_mismatchedAudioCodecCount > MAX_MISMATCHED_AUDIO_CODEC_COUNT) { + _mismatchedAudioCodecCount = 0; + + // inform others of the mismatch + auto sendingNode = DependencyManager::get()->nodeWithUUID(message.getSourceID()); + if (sendingNode) { + emit mismatchedAudioCodec(sendingNode, _selectedCodecName, codecInPacket); + qDebug(audio) << "Codec mismatch threshold exceeded, SelectedAudioFormat(" << _selectedCodecName << " ) sent"; + } + } } } break; diff --git a/libraries/audio/src/InboundAudioStream.h b/libraries/audio/src/InboundAudioStream.h index 9494b2f204..ecd1a118f9 100644 --- a/libraries/audio/src/InboundAudioStream.h +++ b/libraries/audio/src/InboundAudioStream.h @@ -186,6 +186,7 @@ protected: CodecPluginPointer _codec; QString _selectedCodecName; Decoder* _decoder { nullptr }; + int _mismatchedAudioCodecCount { 0 }; }; float calculateRepeatedFrameFadeFactor(int indexOfRepeat); diff --git a/libraries/image/src/image/Image.cpp b/libraries/image/src/image/Image.cpp index 59ec4776a6..5959714cd8 100644 --- a/libraries/image/src/image/Image.cpp +++ b/libraries/image/src/image/Image.cpp @@ -983,6 +983,9 @@ public: glm::vec2 dstCoord; glm::ivec2 srcPixel; for (int y = 0; y < faceWidth; ++y) { + QRgb* destScanLineBegin = reinterpret_cast( image.scanLine(y) ); + QRgb* destPixelIterator = destScanLineBegin; + dstCoord.y = 1.0f - (y + 0.5f) * dstInvSize.y; // Fill cube face images from top to bottom for (int x = 0; x < faceWidth; ++x) { dstCoord.x = (x + 0.5f) * dstInvSize.x; @@ -995,13 +998,19 @@ public: srcPixel.y = floor((1.0f - srcCoord.y) * srcFaceHeight); if (((uint32)srcPixel.x < (uint32)source.width()) && ((uint32)srcPixel.y < (uint32)source.height())) { - image.setPixel(x, y, source.pixel(QPoint(srcPixel.x, srcPixel.y))); + // We can't directly use the pixel() method because that launches a pixel color conversion to output + // a correct RGBA8 color. But in our case we may have stored HDR values encoded in a RGB30 format which + // are not convertible by Qt. The same goes with the setPixel method, by the way. + const QRgb* sourcePixelIterator = reinterpret_cast(source.scanLine(srcPixel.y)); + sourcePixelIterator += srcPixel.x; + *destPixelIterator = *sourcePixelIterator; // Keep for debug, this is showing the dir as a color // glm::u8vec4 rgba((xyzDir.x + 1.0)*0.5 * 256, (xyzDir.y + 1.0)*0.5 * 256, (xyzDir.z + 1.0)*0.5 * 256, 256); // unsigned int val = 0xff000000 | (rgba.r) | (rgba.g << 8) | (rgba.b << 16); - // image.setPixel(x, y, val); + // *destPixelIterator = val; } + ++destPixelIterator; } } return image; @@ -1192,6 +1201,10 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& if ((srcImage.width() > 0) && (srcImage.height() > 0)) { QImage image = processSourceImage(srcImage, true); + if (image.format() != QIMAGE_HDR_FORMAT) { + image = convertToHDRFormat(image, HDR_FORMAT); + } + gpu::Element formatMip; gpu::Element formatGPU; if (isCubeTexturesCompressionEnabled()) { @@ -1229,13 +1242,6 @@ gpu::TexturePointer TextureUsage::processCubeTextureColorFromImage(const QImage& faces.push_back(faceImage); } } - - if (image.format() != QIMAGE_HDR_FORMAT) { - for (auto& face : faces) { - face = convertToHDRFormat(face, HDR_FORMAT); - } - } - } else { qCDebug(imagelogging) << "Failed to find a known cube map layout from this image:" << QString(srcImageName.c_str()); return nullptr; diff --git a/libraries/ui/src/ui/OffscreenQmlSurface.cpp b/libraries/ui/src/ui/OffscreenQmlSurface.cpp index 6cf8a927ff..01026ae5ff 100644 --- a/libraries/ui/src/ui/OffscreenQmlSurface.cpp +++ b/libraries/ui/src/ui/OffscreenQmlSurface.cpp @@ -1018,6 +1018,32 @@ void OffscreenQmlSurface::synthesizeKeyPress(QString key, QObject* targetOverrid } } +static void forEachKeyboard(QQuickItem* item, std::function function) { + QObject* itemObject = item; + while (itemObject) { + if (itemObject->parent()) { + itemObject = itemObject->parent(); + } else { + break; + } + } + + auto keyboards = itemObject->findChildren("keyboard"); + + for (auto keyboardObject : keyboards) { + auto keyboard = qobject_cast(keyboardObject); + if (keyboard == nullptr) { + continue; + } + + if (function) { + function(keyboard); + } + } +} + +static const int TEXTINPUT_PASSWORD = 2; + void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool numeric) { #if Q_OS_ANDROID return; @@ -1030,6 +1056,26 @@ void OffscreenQmlSurface::setKeyboardRaised(QObject* object, bool raised, bool n // 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; + } + + auto echoMode = item->property("echoMode"); + bool isPasswordField = echoMode.isValid() && echoMode.toInt() == TEXTINPUT_PASSWORD; + + // we need to somehow pass 'isPasswordField' to visible keyboard so it will change its 'mirror text' to asterixes + // the issue in some cases there might be more than one keyboard in object tree and it is hard to understand which one is being used at the moment + // unfortunately attempts to check for visibility failed becuase visibility is not updated yet. So... I don't see other way than just update properties for all the keyboards + forEachKeyboard(item, [&](QQuickItem* keyboard) { + keyboard->setProperty("mirroredText", QVariant::fromValue(QString(""))); + keyboard->setProperty("password", isPasswordField); + }); + + // 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"; diff --git a/scripts/developer/debugging/debugWindow.js b/scripts/developer/debugging/debugWindow.js index 6dd116089a..b16739b2b8 100644 --- a/scripts/developer/debugging/debugWindow.js +++ b/scripts/developer/debugging/debugWindow.js @@ -53,4 +53,8 @@ ScriptDiscoveryService.clearDebugWindow.connect(function() { window.clearDebugWindow(); }); +Script.scriptEnding.connect(function () { + window.close(); +}) + }());