Merge remote-tracking branch 'upstream/master' into materialMapping
|
@ -80,8 +80,9 @@ endif()
|
||||||
if (ANDROID)
|
if (ANDROID)
|
||||||
set(GLES_OPTION ON)
|
set(GLES_OPTION ON)
|
||||||
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
|
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
|
||||||
|
add_definitions(-DHIFI_ANDROID_APP=\"${HIFI_ANDROID_APP}\")
|
||||||
else ()
|
else ()
|
||||||
set(PLATFORM_QT_COMPONENTS WebEngine)
|
set(PLATFORM_QT_COMPONENTS WebEngine Xml)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
if (USE_GLES AND (NOT ANDROID))
|
if (USE_GLES AND (NOT ANDROID))
|
||||||
|
|
|
@ -3,10 +3,10 @@ apply plugin: 'com.android.application'
|
||||||
android {
|
android {
|
||||||
signingConfigs {
|
signingConfigs {
|
||||||
release {
|
release {
|
||||||
keyAlias 'key0'
|
storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null
|
||||||
keyPassword 'password'
|
storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : ''
|
||||||
storeFile file('C:/android/keystore.jks')
|
keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : ''
|
||||||
storePassword 'password'
|
keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : ''
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,42 +0,0 @@
|
||||||
//
|
|
||||||
// InterfaceActivity.java
|
|
||||||
// gvr-interface/java
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/26/15.
|
|
||||||
// Copyright 2015 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
|
|
||||||
//
|
|
||||||
|
|
||||||
package io.highfidelity.gvrinterface;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.View;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.util.Log;
|
|
||||||
import org.qtproject.qt5.android.bindings.QtActivity;
|
|
||||||
|
|
||||||
public class InterfaceActivity extends QtActivity {
|
|
||||||
|
|
||||||
public static native void handleHifiURL(String hifiURLString);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
||||||
|
|
||||||
// Get the intent that started this activity in case we have a hifi:// URL to parse
|
|
||||||
Intent intent = getIntent();
|
|
||||||
if (intent.getAction() == Intent.ACTION_VIEW) {
|
|
||||||
Uri data = intent.getData();
|
|
||||||
|
|
||||||
if (data.getScheme().equals("hifi")) {
|
|
||||||
handleHifiURL(data.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -31,6 +31,7 @@ import android.view.WindowManager;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.SlidingDrawer;
|
import android.widget.SlidingDrawer;
|
||||||
|
|
||||||
|
import org.qtproject.qt5.android.QtNative;
|
||||||
import org.qtproject.qt5.android.QtLayout;
|
import org.qtproject.qt5.android.QtLayout;
|
||||||
import org.qtproject.qt5.android.QtSurface;
|
import org.qtproject.qt5.android.QtSurface;
|
||||||
import org.qtproject.qt5.android.bindings.QtActivity;
|
import org.qtproject.qt5.android.bindings.QtActivity;
|
||||||
|
@ -166,8 +167,27 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
|
||||||
nativeOnDestroy();
|
nativeOnDestroy();
|
||||||
|
/*
|
||||||
|
cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly
|
||||||
|
After Qt upgrade to 5.11 we had a black screen crash after closing the application with
|
||||||
|
the hardware button "Back" and trying to start the app again. It could only be fixed after
|
||||||
|
totally closing the app swiping it in the list of running apps.
|
||||||
|
This problem did not happen with the previous Qt version.
|
||||||
|
After analysing changes we came up with this case and change:
|
||||||
|
https://codereview.qt-project.org/#/c/218882/
|
||||||
|
In summary they've moved libs loading to the same thread as main() and as a matter of correctness
|
||||||
|
in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();`
|
||||||
|
That exit call is the main reason of this problem.
|
||||||
|
|
||||||
|
In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the
|
||||||
|
entire onDestroy method including that thread exit line for other three lines that purposely
|
||||||
|
terminate qt (borrowed from QtActivityDelegate::onDestroy as well).
|
||||||
|
*/
|
||||||
|
QtNative.terminateQt();
|
||||||
|
QtNative.setActivity(null, null);
|
||||||
|
System.exit(0);
|
||||||
|
super.onDestroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
9
android/apps/questFramePlayer/CMakeLists.txt
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
set(TARGET_NAME questFramePlayer)
|
||||||
|
setup_hifi_library(AndroidExtras)
|
||||||
|
link_hifi_libraries(shared ktx shaders gpu gl oculusMobile ${PLATFORM_GL_BACKEND})
|
||||||
|
target_include_directories(${TARGET_NAME} PRIVATE ${HIFI_ANDROID_PRECOMPILED}/ovr/VrApi/Include)
|
||||||
|
target_link_libraries(${TARGET_NAME} android log m)
|
||||||
|
target_opengl()
|
||||||
|
target_oculus_mobile()
|
||||||
|
|
||||||
|
|
51
android/apps/questFramePlayer/build.gradle
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
|
||||||
|
android {
|
||||||
|
signingConfigs {
|
||||||
|
release {
|
||||||
|
storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null
|
||||||
|
storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : ''
|
||||||
|
keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : ''
|
||||||
|
keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : ''
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
compileSdkVersion 28
|
||||||
|
defaultConfig {
|
||||||
|
applicationId "io.highfidelity.frameplayer"
|
||||||
|
minSdkVersion 25
|
||||||
|
targetSdkVersion 28
|
||||||
|
ndk { abiFilters 'arm64-v8a' }
|
||||||
|
externalNativeBuild {
|
||||||
|
cmake {
|
||||||
|
arguments '-DHIFI_ANDROID=1',
|
||||||
|
'-DHIFI_ANDROID_APP=questFramePlayer',
|
||||||
|
'-DANDROID_TOOLCHAIN=clang',
|
||||||
|
'-DANDROID_STL=c++_shared',
|
||||||
|
|
||||||
|
'-DCMAKE_VERBOSE_MAKEFILE=ON'
|
||||||
|
targets = ['questFramePlayer']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
minifyEnabled false
|
||||||
|
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||||
|
signingConfig signingConfigs.release
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
externalNativeBuild.cmake.path '../../../CMakeLists.txt'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation fileTree(include: ['*.jar'], dir: '../../libraries/qt/libs')
|
||||||
|
implementation project(':oculus')
|
||||||
|
implementation project(':qt')
|
||||||
|
}
|
25
android/apps/questFramePlayer/proguard-rules.pro
vendored
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# By default, the flags in this file are appended to flags specified
|
||||||
|
# in C:\Android\SDK/tools/proguard/proguard-android.txt
|
||||||
|
# You can edit the include path and order by changing the proguardFiles
|
||||||
|
# directive in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# Add any project specific keep options here:
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
55
android/apps/questFramePlayer/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
package="io.highfidelity.frameplayer"
|
||||||
|
android:versionCode="1"
|
||||||
|
android:versionName="1.0"
|
||||||
|
android:installLocation="auto">
|
||||||
|
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true"/>
|
||||||
|
<uses-feature android:name="android.hardware.sensor.gyroscope" android:required="true"/>
|
||||||
|
<uses-feature android:name="android.software.vr.mode" android:required="true"/>
|
||||||
|
<uses-feature android:name="android.hardware.vr.high_performance" android:required="true"/>
|
||||||
|
|
||||||
|
|
||||||
|
<application android:label="Frame Viewer"
|
||||||
|
android:allowBackup="false"
|
||||||
|
android:name="org.qtproject.qt5.android.bindings.QtApplication"
|
||||||
|
tools:ignore="GoogleAppIndexingWarning,MissingApplicationIcon">
|
||||||
|
<meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only"/>
|
||||||
|
<activity
|
||||||
|
android:name=".QuestQtActivity"
|
||||||
|
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
|
||||||
|
android:launchMode="singleTask"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:screenOrientation="landscape"
|
||||||
|
android:excludeFromRecents="false"
|
||||||
|
android:alwaysRetainTaskState="true"
|
||||||
|
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
|
||||||
|
>
|
||||||
|
<!-- JNI nonsense -->
|
||||||
|
<meta-data android:name="android.app.lib_name" android:value="questFramePlayer"/>
|
||||||
|
<!-- Qt nonsense -->
|
||||||
|
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
|
||||||
|
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
|
||||||
|
<meta-data android:name="android.app.bundled_in_assets_resource_id" android:resource="@array/bundled_in_assets"/>
|
||||||
|
<meta-data android:name="android.app.load_local_libs" android:value="plugins/platforms/android/libqtforandroid.so:plugins/bearer/libqandroidbearer.so:lib/libQt5QuickParticles.so"/>
|
||||||
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen"
|
||||||
|
android:configChanges="screenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
|
||||||
|
android:name=".QuestRenderActivity"
|
||||||
|
android:label="Frame Player"
|
||||||
|
android:launchMode="singleInstance"
|
||||||
|
android:screenOrientation="landscape"
|
||||||
|
android:excludeFromRecents="false">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
25
android/apps/questFramePlayer/src/main/cpp/PlayerWindow.cpp
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/10/21
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "PlayerWindow.h"
|
||||||
|
|
||||||
|
#include <QtWidgets/QFileDialog>
|
||||||
|
|
||||||
|
PlayerWindow::PlayerWindow() {
|
||||||
|
installEventFilter(this);
|
||||||
|
setFlags(Qt::MSWindowsOwnDC | Qt::Window | Qt::Dialog | Qt::WindowMinMaxButtonsHint | Qt::WindowTitleHint);
|
||||||
|
setSurfaceType(QSurface::OpenGLSurface);
|
||||||
|
create();
|
||||||
|
showFullScreen();
|
||||||
|
// Ensure the window is visible and the GL context is valid
|
||||||
|
QCoreApplication::processEvents();
|
||||||
|
_renderThread.initialize(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerWindow::~PlayerWindow() {
|
||||||
|
}
|
29
android/apps/questFramePlayer/src/main/cpp/PlayerWindow.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/10/21
|
||||||
|
// Copyright 2014 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
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
#include <QtGui/QWindow>
|
||||||
|
#include <QtCore/QSettings>
|
||||||
|
|
||||||
|
#include <gpu/Forward.h>
|
||||||
|
#include "RenderThread.h"
|
||||||
|
|
||||||
|
// Create a simple OpenGL window that renders text in various ways
|
||||||
|
class PlayerWindow : public QWindow {
|
||||||
|
public:
|
||||||
|
PlayerWindow();
|
||||||
|
virtual ~PlayerWindow();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
//bool eventFilter(QObject* obj, QEvent* event) override;
|
||||||
|
//void keyPressEvent(QKeyEvent* event) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QSettings _settings;
|
||||||
|
RenderThread _renderThread;
|
||||||
|
};
|
240
android/apps/questFramePlayer/src/main/cpp/RenderThread.cpp
Normal file
|
@ -0,0 +1,240 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/10/21
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include "RenderThread.h"
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#include <jni.h>
|
||||||
|
#include <android/log.h>
|
||||||
|
|
||||||
|
#include <QtCore/QFileInfo>
|
||||||
|
#include <QtGui/QWindow>
|
||||||
|
#include <QtGui/QImageReader>
|
||||||
|
|
||||||
|
#include <gl/QOpenGLContextWrapper.h>
|
||||||
|
#include <gpu/FrameIO.h>
|
||||||
|
#include <gpu/Texture.h>
|
||||||
|
|
||||||
|
#include <VrApi_Types.h>
|
||||||
|
#include <VrApi_Helpers.h>
|
||||||
|
#include <ovr/VrHandler.h>
|
||||||
|
#include <ovr/Helpers.h>
|
||||||
|
|
||||||
|
#include <VrApi.h>
|
||||||
|
#include <VrApi_Input.h>
|
||||||
|
|
||||||
|
static JNIEnv* _env { nullptr };
|
||||||
|
static JavaVM* _vm { nullptr };
|
||||||
|
static jobject _activity { nullptr };
|
||||||
|
|
||||||
|
struct HandController{
|
||||||
|
ovrInputTrackedRemoteCapabilities caps {};
|
||||||
|
ovrInputStateTrackedRemote state {};
|
||||||
|
ovrResult stateResult{ ovrSuccess };
|
||||||
|
ovrTracking tracking {};
|
||||||
|
ovrResult trackingResult{ ovrSuccess };
|
||||||
|
|
||||||
|
void update(ovrMobile* session, double time = 0.0) {
|
||||||
|
const auto& deviceId = caps.Header.DeviceID;
|
||||||
|
stateResult = vrapi_GetCurrentInputState(session, deviceId, &state.Header);
|
||||||
|
trackingResult = vrapi_GetInputTrackingState(session, deviceId, 0.0, &tracking);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<HandController> devices;
|
||||||
|
|
||||||
|
extern "C" {
|
||||||
|
|
||||||
|
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *, void *) {
|
||||||
|
__android_log_write(ANDROID_LOG_WARN, "QQQ", __FUNCTION__);
|
||||||
|
return JNI_VERSION_1_6;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
JNIEXPORT void JNICALL Java_io_highfidelity_frameplayer_QuestQtActivity_nativeOnCreate(JNIEnv* env, jobject obj) {
|
||||||
|
env->GetJavaVM(&_vm);
|
||||||
|
_activity = env->NewGlobalRef(obj);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static const char* FRAME_FILE = "assets:/frames/20190121_1220.json";
|
||||||
|
|
||||||
|
static void textureLoader(const std::string& filename, const gpu::TexturePointer& texture, uint16_t layer) {
|
||||||
|
QImage image;
|
||||||
|
QImageReader(filename.c_str()).read(&image);
|
||||||
|
if (layer > 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
texture->assignStoredMip(0, image.byteCount(), image.constBits());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::submitFrame(const gpu::FramePointer& frame) {
|
||||||
|
std::unique_lock<std::mutex> lock(_frameLock);
|
||||||
|
_pendingFrames.push(frame);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::move(const glm::vec3& v) {
|
||||||
|
std::unique_lock<std::mutex> lock(_frameLock);
|
||||||
|
_correction = glm::inverse(glm::translate(mat4(), v)) * _correction;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::initialize(QWindow* window) {
|
||||||
|
std::unique_lock<std::mutex> lock(_frameLock);
|
||||||
|
setObjectName("RenderThread");
|
||||||
|
Parent::initialize();
|
||||||
|
_window = window;
|
||||||
|
_thread->setObjectName("RenderThread");
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::setup() {
|
||||||
|
// Wait until the context has been moved to this thread
|
||||||
|
{ std::unique_lock<std::mutex> lock(_frameLock); }
|
||||||
|
|
||||||
|
|
||||||
|
ovr::VrHandler::initVr();
|
||||||
|
__android_log_write(ANDROID_LOG_WARN, "QQQ", "Launching oculus activity");
|
||||||
|
_vm->AttachCurrentThread(&_env, nullptr);
|
||||||
|
jclass cls = _env->GetObjectClass(_activity);
|
||||||
|
jmethodID mid = _env->GetMethodID(cls, "launchOculusActivity", "()V");
|
||||||
|
_env->CallVoidMethod(_activity, mid);
|
||||||
|
__android_log_write(ANDROID_LOG_WARN, "QQQ", "Launching oculus activity done");
|
||||||
|
ovr::VrHandler::setHandler(this);
|
||||||
|
|
||||||
|
makeCurrent();
|
||||||
|
|
||||||
|
// GPU library init
|
||||||
|
gpu::Context::init<gpu::gl::GLBackend>();
|
||||||
|
_gpuContext = std::make_shared<gpu::Context>();
|
||||||
|
_backend = _gpuContext->getBackend();
|
||||||
|
_gpuContext->beginFrame();
|
||||||
|
_gpuContext->endFrame();
|
||||||
|
|
||||||
|
makeCurrent();
|
||||||
|
glGenTextures(1, &_externalTexture);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, _externalTexture);
|
||||||
|
static const glm::u8vec4 color{ 0,1,0,0 };
|
||||||
|
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &color);
|
||||||
|
|
||||||
|
if (QFileInfo(FRAME_FILE).exists()) {
|
||||||
|
auto frame = gpu::readFrame(FRAME_FILE, _externalTexture, &textureLoader);
|
||||||
|
submitFrame(frame);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::shutdown() {
|
||||||
|
_activeFrame.reset();
|
||||||
|
while (!_pendingFrames.empty()) {
|
||||||
|
_gpuContext->consumeFrameUpdates(_pendingFrames.front());
|
||||||
|
_pendingFrames.pop();
|
||||||
|
}
|
||||||
|
_gpuContext->shutdown();
|
||||||
|
_gpuContext.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::handleInput() {
|
||||||
|
static std::once_flag once;
|
||||||
|
std::call_once(once, [&]{
|
||||||
|
withOvrMobile([&](ovrMobile* session){
|
||||||
|
int deviceIndex = 0;
|
||||||
|
ovrInputCapabilityHeader capsHeader;
|
||||||
|
while (vrapi_EnumerateInputDevices(session, deviceIndex, &capsHeader) >= 0) {
|
||||||
|
if (capsHeader.Type == ovrControllerType_TrackedRemote) {
|
||||||
|
HandController controller = {};
|
||||||
|
controller.caps.Header = capsHeader;
|
||||||
|
controller.state.Header.ControllerType = ovrControllerType_TrackedRemote;
|
||||||
|
vrapi_GetInputDeviceCapabilities( session, &controller.caps.Header);
|
||||||
|
devices.push_back(controller);
|
||||||
|
}
|
||||||
|
++deviceIndex;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
auto readResult = ovr::VrHandler::withOvrMobile([&](ovrMobile *session) {
|
||||||
|
for (auto &controller : devices) {
|
||||||
|
controller.update(session);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (readResult) {
|
||||||
|
for (auto &controller : devices) {
|
||||||
|
const auto &caps = controller.caps;
|
||||||
|
if (controller.stateResult >= 0) {
|
||||||
|
const auto &remote = controller.state;
|
||||||
|
if (remote.Joystick.x != 0.0f || remote.Joystick.y != 0.0f) {
|
||||||
|
glm::vec3 translation;
|
||||||
|
float rotation = 0.0f;
|
||||||
|
if (caps.ControllerCapabilities & ovrControllerCaps_LeftHand) {
|
||||||
|
translation = glm::vec3{0.0f, -remote.Joystick.y, 0.0f};
|
||||||
|
} else {
|
||||||
|
translation = glm::vec3{remote.Joystick.x, 0.0f, -remote.Joystick.y};
|
||||||
|
}
|
||||||
|
float scale = 0.1f + (1.9f * remote.GripTrigger);
|
||||||
|
_correction = glm::translate(glm::mat4(), translation * scale) * _correction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderThread::renderFrame() {
|
||||||
|
GLuint finalTexture = 0;
|
||||||
|
glm::uvec2 finalTextureSize;
|
||||||
|
const auto& tracking = beginFrame();
|
||||||
|
if (_activeFrame) {
|
||||||
|
const auto& frame = _activeFrame;
|
||||||
|
auto& eyeProjections = frame->stereoState._eyeProjections;
|
||||||
|
auto& eyeOffsets = frame->stereoState._eyeViews;
|
||||||
|
// Quest
|
||||||
|
auto frameCorrection = _correction * ovr::toGlm(tracking.HeadPose.Pose);
|
||||||
|
_backend->setCameraCorrection(glm::inverse(frameCorrection), frame->view);
|
||||||
|
ovr::for_each_eye([&](ovrEye eye){
|
||||||
|
const auto& eyeInfo = tracking.Eye[eye];
|
||||||
|
eyeProjections[eye] = ovr::toGlm(eyeInfo.ProjectionMatrix);
|
||||||
|
eyeOffsets[eye] = ovr::toGlm(eyeInfo.ViewMatrix);
|
||||||
|
});
|
||||||
|
_backend->recycle();
|
||||||
|
_backend->syncCache();
|
||||||
|
_gpuContext->enableStereo(true);
|
||||||
|
if (frame && !frame->batches.empty()) {
|
||||||
|
_gpuContext->executeFrame(frame);
|
||||||
|
}
|
||||||
|
auto& glbackend = (gpu::gl::GLBackend&)(*_backend);
|
||||||
|
finalTextureSize = { frame->framebuffer->getWidth(), frame->framebuffer->getHeight() };
|
||||||
|
finalTexture = glbackend.getTextureID(frame->framebuffer->getRenderBuffer(0));
|
||||||
|
}
|
||||||
|
presentFrame(finalTexture, finalTextureSize, tracking);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderThread::process() {
|
||||||
|
pollTask();
|
||||||
|
|
||||||
|
if (!vrActive()) {
|
||||||
|
QThread::msleep(1);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::queue<gpu::FramePointer> pendingFrames;
|
||||||
|
{
|
||||||
|
std::unique_lock<std::mutex> lock(_frameLock);
|
||||||
|
pendingFrames.swap(_pendingFrames);
|
||||||
|
}
|
||||||
|
|
||||||
|
makeCurrent();
|
||||||
|
while (!pendingFrames.empty()) {
|
||||||
|
_activeFrame = pendingFrames.front();
|
||||||
|
pendingFrames.pop();
|
||||||
|
_gpuContext->consumeFrameUpdates(_activeFrame);
|
||||||
|
_activeFrame->stereoState._enable = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
handleInput();
|
||||||
|
renderFrame();
|
||||||
|
return true;
|
||||||
|
}
|
44
android/apps/questFramePlayer/src/main/cpp/RenderThread.h
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/10/21
|
||||||
|
// Copyright 2014 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
|
||||||
|
//
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QtCore/QElapsedTimer>
|
||||||
|
|
||||||
|
#include <GenericThread.h>
|
||||||
|
#include <shared/RateCounter.h>
|
||||||
|
#include <gl/Config.h>
|
||||||
|
#include <gl/Context.h>
|
||||||
|
#include <gpu/gl/GLBackend.h>
|
||||||
|
#include <ovr/VrHandler.h>
|
||||||
|
|
||||||
|
class RenderThread : public GenericThread, ovr::VrHandler {
|
||||||
|
using Parent = GenericThread;
|
||||||
|
public:
|
||||||
|
QWindow* _window{ nullptr };
|
||||||
|
std::mutex _mutex;
|
||||||
|
gpu::ContextPointer _gpuContext; // initialized during window creation
|
||||||
|
std::shared_ptr<gpu::Backend> _backend;
|
||||||
|
std::atomic<size_t> _presentCount{ 0 };
|
||||||
|
std::mutex _frameLock;
|
||||||
|
std::queue<gpu::FramePointer> _pendingFrames;
|
||||||
|
gpu::FramePointer _activeFrame;
|
||||||
|
uint32_t _externalTexture{ 0 };
|
||||||
|
glm::mat4 _correction;
|
||||||
|
|
||||||
|
void move(const glm::vec3& v);
|
||||||
|
void setup() override;
|
||||||
|
bool process() override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
void handleInput();
|
||||||
|
|
||||||
|
void submitFrame(const gpu::FramePointer& frame);
|
||||||
|
void initialize(QWindow* window);
|
||||||
|
void renderFrame();
|
||||||
|
};
|
56
android/apps/questFramePlayer/src/main/cpp/main.cpp
Normal file
|
@ -0,0 +1,56 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/11/22
|
||||||
|
// Copyright 2014 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <android/log.h>
|
||||||
|
|
||||||
|
#include <QtGui/QGuiApplication>
|
||||||
|
#include <QtCore/QTimer>
|
||||||
|
#include <QtCore/QFileInfo>
|
||||||
|
|
||||||
|
#include <Trace.h>
|
||||||
|
|
||||||
|
#include "PlayerWindow.h"
|
||||||
|
|
||||||
|
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||||
|
if (!message.isEmpty()) {
|
||||||
|
const char * local=message.toStdString().c_str();
|
||||||
|
switch (type) {
|
||||||
|
case QtDebugMsg:
|
||||||
|
__android_log_write(ANDROID_LOG_DEBUG,"Interface",local);
|
||||||
|
break;
|
||||||
|
case QtInfoMsg:
|
||||||
|
__android_log_write(ANDROID_LOG_INFO,"Interface",local);
|
||||||
|
break;
|
||||||
|
case QtWarningMsg:
|
||||||
|
__android_log_write(ANDROID_LOG_WARN,"Interface",local);
|
||||||
|
break;
|
||||||
|
case QtCriticalMsg:
|
||||||
|
__android_log_write(ANDROID_LOG_ERROR,"Interface",local);
|
||||||
|
break;
|
||||||
|
case QtFatalMsg:
|
||||||
|
default:
|
||||||
|
__android_log_write(ANDROID_LOG_FATAL,"Interface",local);
|
||||||
|
abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char** argv) {
|
||||||
|
setupHifiApplication("gpuFramePlayer");
|
||||||
|
QGuiApplication app(argc, argv);
|
||||||
|
auto oldMessageHandler = qInstallMessageHandler(messageHandler);
|
||||||
|
DependencyManager::set<tracing::Tracer>();
|
||||||
|
PlayerWindow window;
|
||||||
|
__android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec");
|
||||||
|
app.exec();
|
||||||
|
__android_log_write(ANDROID_LOG_FATAL,"QQQ","Exec done");
|
||||||
|
qInstallMessageHandler(oldMessageHandler);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,53 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/11/20
|
||||||
|
// Copyright 2013-2018 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
|
||||||
|
//
|
||||||
|
package io.highfidelity.frameplayer;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.qtproject.qt5.android.bindings.QtActivity;
|
||||||
|
|
||||||
|
import io.highfidelity.oculus.OculusMobileActivity;
|
||||||
|
|
||||||
|
|
||||||
|
public class QuestQtActivity extends QtActivity {
|
||||||
|
private native void nativeOnCreate();
|
||||||
|
private boolean launchedQuestMode = false;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
Log.w("QQQ_Qt", "QuestQtActivity::onCreate");
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
nativeOnCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDestroy() {
|
||||||
|
Log.w("QQQ_Qt", "QuestQtActivity::onDestroy");
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void launchOculusActivity() {
|
||||||
|
Log.w("QQQ_Qt", "QuestQtActivity::launchOculusActivity");
|
||||||
|
runOnUiThread(()->{
|
||||||
|
keepInterfaceRunning = true;
|
||||||
|
launchedQuestMode = true;
|
||||||
|
moveTaskToBack(true);
|
||||||
|
startActivity(new Intent(this, QuestRenderActivity.class));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResume() {
|
||||||
|
super.onResume();
|
||||||
|
if (launchedQuestMode) {
|
||||||
|
moveTaskToBack(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package io.highfidelity.frameplayer;
|
||||||
|
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
|
||||||
|
import io.highfidelity.oculus.OculusMobileActivity;
|
||||||
|
|
||||||
|
public class QuestRenderActivity extends OculusMobileActivity {
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedState) {
|
||||||
|
super.onCreate(savedState);
|
||||||
|
startActivity(new Intent(this, QuestQtActivity.class));
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--suppress AndroidUnknownAttribute -->
|
||||||
|
<vector xmlns:api24="http://schemas.android.com/apk/res/android" xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:viewportWidth="192"
|
||||||
|
android:viewportHeight="192"
|
||||||
|
android:width="192dp"
|
||||||
|
android:height="192dp">
|
||||||
|
<path
|
||||||
|
android:pathData="M189.5 96.5A93.5 93.5 0 0 1 96 190 93.5 93.5 0 0 1 2.5 96.5 93.5 93.5 0 0 1 96 3 93.5 93.5 0 0 1 189.5 96.5Z"
|
||||||
|
android:fillColor="#333333" />
|
||||||
|
<path
|
||||||
|
android:pathData="M96.2 173.1c-10.3 0 -20.4 -2.1 -29.8 -6 -9.2 -3.8 -17.3 -9.4 -24.3 -16.4 -7 -7 -12.6 -15.2 -16.4 -24.3 -4.1 -9.6 -6.2 -19.6 -6.2 -30 0 -10.3 2.1 -20.4 6 -29.8 3.8 -9.2 9.4 -17.3 16.4 -24.3 7 -7 15.2 -12.6 24.3 -16.4 9.5 -4 19.5 -6 29.8 -6 10.3 0 20.4 2.1 29.8 6 9.2 3.8 17.3 9.4 24.3 16.4 7 7 12.6 15.2 16.4 24.3 4 9.5 6 19.5 6 29.8 0 10.3 -2.1 20.4 -6 29.8 -3.8 9.2 -9.4 17.3 -16.4 24.3 -7 7 -15.2 12.6 -24.3 16.4 -9.2 4.1 -19.3 6.2 -29.6 6.2zm0 -145.3c-37.8 0 -68.6 30.8 -68.6 68.6 0 37.8 30.8 68.6 68.6 68.6 37.8 0 68.6 -30.8 68.6 -68.6 0 -37.8 -30.8 -68.6 -68.6 -68.6z"
|
||||||
|
android:fillColor="#00b4f0" />
|
||||||
|
<path
|
||||||
|
android:pathData="M119.6 129l0 -53.8c3.4 -1.1 5.8 -4.3 5.8 -8 0 -4.6 -3.8 -8.4 -8.4 -8.4 -4.6 0 -8.4 3.8 -8.4 8.4 0 3.6 2.2 6.6 5.4 7.9l0 25L79 83.8 79 64c3.4 -1.1 5.8 -4.3 5.8 -8 0 -4.6 -3.8 -8.4 -8.4 -8.4 -4.6 0 -8.4 3.8 -8.4 8.4 0 3.6 2.2 6.6 5.4 7.9l0 54.1c-3.1 1.2 -5.4 4.3 -5.4 7.9 0 4.6 3.8 8.4 8.4 8.4 4.6 0 8.4 -3.8 8.4 -8.4 0 -3.7 -2.4 -6.9 -5.8 -8l0 -27.3 35 16.3 0 22.2c-3.1 1.2 -5.4 4.3 -5.4 7.9 0 4.6 3.8 8.4 8.4 8.4 4.6 0 8.4 -3.8 8.4 -8.4 0 -3.8 -2.4 -6.9 -5.8 -8z"
|
||||||
|
android:fillColor="#00b4f0" />
|
||||||
|
</vector>
|
|
@ -0,0 +1,3 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name" translatable="false">GPU Frame Player</string>
|
||||||
|
</resources>
|
|
@ -73,13 +73,10 @@ RUN mkdir "$HIFI_BASE" && \
|
||||||
|
|
||||||
RUN git clone https://github.com/jherico/hifi.git && \
|
RUN git clone https://github.com/jherico/hifi.git && \
|
||||||
cd ~/hifi && \
|
cd ~/hifi && \
|
||||||
git checkout feature/quest_move_interface
|
git checkout feature/quest_frame_player
|
||||||
|
|
||||||
WORKDIR /home/jenkins/hifi
|
WORKDIR /home/jenkins/hifi
|
||||||
|
|
||||||
RUN touch .test6 && \
|
|
||||||
git fetch && git reset origin/feature/quest_move_interface --hard
|
|
||||||
|
|
||||||
RUN mkdir build
|
RUN mkdir build
|
||||||
|
|
||||||
# Pre-cache the vcpkg managed dependencies
|
# Pre-cache the vcpkg managed dependencies
|
||||||
|
|
17
android/libraries/oculus/build.gradle
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 28
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
minSdkVersion 24
|
||||||
|
targetSdkVersion 28
|
||||||
|
versionCode 1
|
||||||
|
versionName "1.0"
|
||||||
|
}
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
}
|
2
android/libraries/oculus/src/main/AndroidManifest.xml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.highfidelity.shared.oculus"/>
|
|
@ -0,0 +1,103 @@
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 2018/11/20
|
||||||
|
// Copyright 2013-2018 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
|
||||||
|
//
|
||||||
|
package io.highfidelity.oculus;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Surface;
|
||||||
|
import android.view.SurfaceHolder;
|
||||||
|
import android.view.SurfaceView;
|
||||||
|
import android.view.WindowManager;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Contains a native surface and forwards the activity lifecycle and surface lifecycle
|
||||||
|
* events to the OculusMobileDisplayPlugin
|
||||||
|
*/
|
||||||
|
public class OculusMobileActivity extends Activity implements SurfaceHolder.Callback {
|
||||||
|
private static final String TAG = OculusMobileActivity.class.getSimpleName();
|
||||||
|
static { System.loadLibrary("oculusMobile"); }
|
||||||
|
private native void nativeOnCreate();
|
||||||
|
private native static void nativeOnResume();
|
||||||
|
private native static void nativeOnPause();
|
||||||
|
private native static void nativeOnDestroy();
|
||||||
|
private native static void nativeOnSurfaceChanged(Surface s);
|
||||||
|
|
||||||
|
private SurfaceView mView;
|
||||||
|
private SurfaceHolder mSurfaceHolder;
|
||||||
|
|
||||||
|
|
||||||
|
public static void launch(Activity activity) {
|
||||||
|
if (activity != null) {
|
||||||
|
activity.runOnUiThread(()->{
|
||||||
|
activity.startActivity(new Intent(activity, OculusMobileActivity.class));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
Log.w(TAG, "QQQ onCreate");
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
||||||
|
// Create a native surface for VR rendering (Qt GL surfaces are not suitable
|
||||||
|
// because of the lack of fine control over the surface callbacks)
|
||||||
|
mView = new SurfaceView(this);
|
||||||
|
setContentView(mView);
|
||||||
|
mView.getHolder().addCallback(this);
|
||||||
|
|
||||||
|
// Forward the create message to the JNI code
|
||||||
|
nativeOnCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onDestroy() {
|
||||||
|
Log.w(TAG, "QQQ onDestroy");
|
||||||
|
if (mSurfaceHolder != null) {
|
||||||
|
nativeOnSurfaceChanged(null);
|
||||||
|
}
|
||||||
|
nativeOnDestroy();
|
||||||
|
super.onDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onResume() {
|
||||||
|
Log.w(TAG, "QQQ onResume");
|
||||||
|
super.onResume();
|
||||||
|
nativeOnResume();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onPause() {
|
||||||
|
Log.w(TAG, "QQQ onPause");
|
||||||
|
nativeOnPause();
|
||||||
|
super.onPause();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceCreated(SurfaceHolder holder) {
|
||||||
|
Log.w(TAG, "QQQ surfaceCreated");
|
||||||
|
nativeOnSurfaceChanged(holder.getSurface());
|
||||||
|
mSurfaceHolder = holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
|
||||||
|
Log.w(TAG, "QQQ surfaceChanged");
|
||||||
|
nativeOnSurfaceChanged(holder.getSurface());
|
||||||
|
mSurfaceHolder = holder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||||
|
Log.w(TAG, "QQQ surfaceDestroyed");
|
||||||
|
nativeOnSurfaceChanged(null);
|
||||||
|
mSurfaceHolder = null;
|
||||||
|
}
|
||||||
|
}
|
|
@ -364,25 +364,7 @@ public class QtActivity extends Activity {
|
||||||
@Override
|
@Override
|
||||||
protected void onDestroy() {
|
protected void onDestroy() {
|
||||||
super.onDestroy();
|
super.onDestroy();
|
||||||
/*
|
QtApplication.invokeDelegate();
|
||||||
cduarte https://highfidelity.manuscript.com/f/cases/16712/App-freezes-on-opening-randomly
|
|
||||||
After Qt upgrade to 5.11 we had a black screen crash after closing the application with
|
|
||||||
the hardware button "Back" and trying to start the app again. It could only be fixed after
|
|
||||||
totally closing the app swiping it in the list of running apps.
|
|
||||||
This problem did not happen with the previous Qt version.
|
|
||||||
After analysing changes we came up with this case and change:
|
|
||||||
https://codereview.qt-project.org/#/c/218882/
|
|
||||||
In summary they've moved libs loading to the same thread as main() and as a matter of correctness
|
|
||||||
in the onDestroy method in QtActivityDelegate, they exit that thread with `QtNative.m_qtThread.exit();`
|
|
||||||
That exit call is the main reason of this problem.
|
|
||||||
|
|
||||||
In this fix we just replace the `QtApplication.invokeDelegate();` call that may end using the
|
|
||||||
entire onDestroy method including that thread exit line for other three lines that purposely
|
|
||||||
terminate qt (borrowed from QtActivityDelegate::onDestroy as well).
|
|
||||||
*/
|
|
||||||
QtNative.terminateQt();
|
|
||||||
QtNative.setActivity(null, null);
|
|
||||||
System.exit(0);
|
|
||||||
}
|
}
|
||||||
//---------------------------------------------------------------------------
|
//---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,26 @@
|
||||||
|
//
|
||||||
|
// Libraries
|
||||||
|
//
|
||||||
|
|
||||||
|
include ':oculus'
|
||||||
|
project(':oculus').projectDir = new File(settingsDir, 'libraries/oculus')
|
||||||
|
|
||||||
include ':qt'
|
include ':qt'
|
||||||
project(':qt').projectDir = new File(settingsDir, 'libraries/qt')
|
project(':qt').projectDir = new File(settingsDir, 'libraries/qt')
|
||||||
|
|
||||||
|
//
|
||||||
|
// Applications
|
||||||
|
//
|
||||||
|
|
||||||
include ':interface'
|
include ':interface'
|
||||||
project(':interface').projectDir = new File(settingsDir, 'apps/interface')
|
project(':interface').projectDir = new File(settingsDir, 'apps/interface')
|
||||||
|
|
||||||
//include ':framePlayer'
|
//
|
||||||
//project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer')
|
// Test projects
|
||||||
|
//
|
||||||
|
|
||||||
|
include ':framePlayer'
|
||||||
|
project(':framePlayer').projectDir = new File(settingsDir, 'apps/framePlayer')
|
||||||
|
|
||||||
|
include ':questFramePlayer'
|
||||||
|
project(':questFramePlayer').projectDir = new File(settingsDir, 'apps/questFramePlayer')
|
||||||
|
|
|
@ -38,6 +38,19 @@ const QString AVATAR_MIXER_LOGGING_NAME = "avatar-mixer";
|
||||||
// FIXME - what we'd actually like to do is send to users at ~50% of their present rate down to 30hz. Assume 90 for now.
|
// FIXME - what we'd actually like to do is send to users at ~50% of their present rate down to 30hz. Assume 90 for now.
|
||||||
const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45;
|
const int AVATAR_MIXER_BROADCAST_FRAMES_PER_SECOND = 45;
|
||||||
|
|
||||||
|
const QRegularExpression AvatarMixer::suffixedNamePattern { R"(^\s*(.+)\s*_(\d)+\s*$)" };
|
||||||
|
|
||||||
|
// Lexicographic comparison:
|
||||||
|
bool AvatarMixer::SessionDisplayName::operator<(const SessionDisplayName& rhs) const {
|
||||||
|
if (_baseName < rhs._baseName) {
|
||||||
|
return true;
|
||||||
|
} else if (rhs._baseName < _baseName) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
return _suffix < rhs._suffix;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
AvatarMixer::AvatarMixer(ReceivedMessage& message) :
|
||||||
ThreadedAssignment(message),
|
ThreadedAssignment(message),
|
||||||
_slavePool(&_slaveSharedData)
|
_slavePool(&_slaveSharedData)
|
||||||
|
@ -313,27 +326,40 @@ void AvatarMixer::manageIdentityData(const SharedNodePointer& node) {
|
||||||
bool sendIdentity = false;
|
bool sendIdentity = false;
|
||||||
if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) {
|
if (nodeData && nodeData->getAvatarSessionDisplayNameMustChange()) {
|
||||||
AvatarData& avatar = nodeData->getAvatar();
|
AvatarData& avatar = nodeData->getAvatar();
|
||||||
const QString& existingBaseDisplayName = nodeData->getBaseDisplayName();
|
const QString& existingBaseDisplayName = nodeData->getAvatar().getSessionDisplayName();
|
||||||
if (--_sessionDisplayNames[existingBaseDisplayName].second <= 0) {
|
if (!existingBaseDisplayName.isEmpty()) {
|
||||||
_sessionDisplayNames.remove(existingBaseDisplayName);
|
SessionDisplayName existingDisplayName { existingBaseDisplayName };
|
||||||
|
|
||||||
|
auto suffixMatch = suffixedNamePattern.match(existingBaseDisplayName);
|
||||||
|
if (suffixMatch.hasMatch()) {
|
||||||
|
existingDisplayName._baseName = suffixMatch.captured(1);
|
||||||
|
existingDisplayName._suffix = suffixMatch.captured(2).toInt();
|
||||||
|
}
|
||||||
|
_sessionDisplayNames.erase(existingDisplayName);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString baseName = avatar.getDisplayName().trimmed();
|
QString baseName = avatar.getDisplayName().trimmed();
|
||||||
const QRegularExpression curses { "fuck|shit|damn|cock|cunt" }; // POC. We may eventually want something much more elaborate (subscription?).
|
const QRegularExpression curses { "fuck|shit|damn|cock|cunt" }; // POC. We may eventually want something much more elaborate (subscription?).
|
||||||
baseName = baseName.replace(curses, "*"); // Replace rather than remove, so that people have a clue that the person's a jerk.
|
baseName = baseName.replace(curses, "*"); // Replace rather than remove, so that people have a clue that the person's a jerk.
|
||||||
const QRegularExpression trailingDigits { "\\s*(_\\d+\\s*)?(\\s*\\n[^$]*)?$" }; // trailing whitespace "_123" and any subsequent lines
|
static const QRegularExpression trailingDigits { R"(\s*(_\d+\s*)?(\s*\n[^$]*)?$)" }; // trailing whitespace "_123" and any subsequent lines
|
||||||
baseName = baseName.remove(trailingDigits);
|
baseName = baseName.remove(trailingDigits);
|
||||||
if (baseName.isEmpty()) {
|
if (baseName.isEmpty()) {
|
||||||
baseName = "anonymous";
|
baseName = "anonymous";
|
||||||
}
|
}
|
||||||
|
|
||||||
QPair<int, int>& soFar = _sessionDisplayNames[baseName]; // Inserts and answers 0, 0 if not already present, which is what we want.
|
SessionDisplayName newDisplayName { baseName };
|
||||||
int& highWater = soFar.first;
|
auto nameIter = _sessionDisplayNames.lower_bound(newDisplayName);
|
||||||
nodeData->setBaseDisplayName(baseName);
|
if (nameIter != _sessionDisplayNames.end() && nameIter->_baseName == baseName) {
|
||||||
QString sessionDisplayName = (highWater > 0) ? baseName + "_" + QString::number(highWater) : baseName;
|
// Existing instance(s) of name; find first free suffix
|
||||||
|
while (*nameIter == newDisplayName && ++newDisplayName._suffix && ++nameIter != _sessionDisplayNames.end())
|
||||||
|
;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sessionDisplayNames.insert(newDisplayName);
|
||||||
|
QString sessionDisplayName = (newDisplayName._suffix > 0) ? baseName + "_" + QString::number(newDisplayName._suffix) : baseName;
|
||||||
avatar.setSessionDisplayName(sessionDisplayName);
|
avatar.setSessionDisplayName(sessionDisplayName);
|
||||||
highWater++;
|
nodeData->setBaseDisplayName(baseName);
|
||||||
soFar.second++; // refcount
|
|
||||||
nodeData->flagIdentityChange();
|
nodeData->flagIdentityChange();
|
||||||
nodeData->setAvatarSessionDisplayNameMustChange(false);
|
nodeData->setAvatarSessionDisplayNameMustChange(false);
|
||||||
sendIdentity = true;
|
sendIdentity = true;
|
||||||
|
@ -409,10 +435,19 @@ void AvatarMixer::handleAvatarKilled(SharedNodePointer avatarNode) {
|
||||||
{ // decrement sessionDisplayNames table and possibly remove
|
{ // decrement sessionDisplayNames table and possibly remove
|
||||||
QMutexLocker nodeDataLocker(&avatarNode->getLinkedData()->getMutex());
|
QMutexLocker nodeDataLocker(&avatarNode->getLinkedData()->getMutex());
|
||||||
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
|
AvatarMixerClientData* nodeData = dynamic_cast<AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||||
const QString& baseDisplayName = nodeData->getBaseDisplayName();
|
const QString& displayName = nodeData->getAvatar().getSessionDisplayName();
|
||||||
// No sense guarding against very rare case of a node with no entry, as this will work without the guard and do one less lookup in the common case.
|
SessionDisplayName exitingDisplayName { displayName };
|
||||||
if (--_sessionDisplayNames[baseDisplayName].second <= 0) {
|
|
||||||
_sessionDisplayNames.remove(baseDisplayName);
|
auto suffixMatch = suffixedNamePattern.match(displayName);
|
||||||
|
if (suffixMatch.hasMatch()) {
|
||||||
|
exitingDisplayName._baseName = suffixMatch.captured(1);
|
||||||
|
exitingDisplayName._suffix = suffixMatch.captured(2).toInt();
|
||||||
|
}
|
||||||
|
auto displayNameIter = _sessionDisplayNames.find(exitingDisplayName);
|
||||||
|
if (displayNameIter == _sessionDisplayNames.end()) {
|
||||||
|
qCDebug(avatars) << "Exiting avatar displayname" << displayName << "not found";
|
||||||
|
} else {
|
||||||
|
_sessionDisplayNames.erase(displayNameIter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
#ifndef hifi_AvatarMixer_h
|
#ifndef hifi_AvatarMixer_h
|
||||||
#define hifi_AvatarMixer_h
|
#define hifi_AvatarMixer_h
|
||||||
|
|
||||||
|
#include <set>
|
||||||
#include <shared/RateCounter.h>
|
#include <shared/RateCounter.h>
|
||||||
#include <PortableHighResolutionClock.h>
|
#include <PortableHighResolutionClock.h>
|
||||||
|
|
||||||
|
@ -88,7 +89,24 @@ private:
|
||||||
|
|
||||||
RateCounter<> _broadcastRate;
|
RateCounter<> _broadcastRate;
|
||||||
p_high_resolution_clock::time_point _lastDebugMessage;
|
p_high_resolution_clock::time_point _lastDebugMessage;
|
||||||
QHash<QString, QPair<int, int>> _sessionDisplayNames;
|
|
||||||
|
// Pair of basename + uniquifying integer suffix.
|
||||||
|
struct SessionDisplayName {
|
||||||
|
explicit SessionDisplayName(QString baseName = QString(), int suffix = 0) :
|
||||||
|
_baseName(baseName),
|
||||||
|
_suffix(suffix) { }
|
||||||
|
// Does lexicographic ordering:
|
||||||
|
bool operator<(const SessionDisplayName& rhs) const;
|
||||||
|
bool operator==(const SessionDisplayName& rhs) const {
|
||||||
|
return _baseName == rhs._baseName && _suffix == rhs._suffix;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString _baseName;
|
||||||
|
int _suffix;
|
||||||
|
};
|
||||||
|
static const QRegularExpression suffixedNamePattern;
|
||||||
|
|
||||||
|
std::set<SessionDisplayName> _sessionDisplayNames;
|
||||||
|
|
||||||
quint64 _displayNameManagementElapsedTime { 0 }; // total time spent in broadcastAvatarData/display name management... since last stats window
|
quint64 _displayNameManagementElapsedTime { 0 }; // total time spent in broadcastAvatarData/display name management... since last stats window
|
||||||
quint64 _ignoreCalculationElapsedTime { 0 };
|
quint64 _ignoreCalculationElapsedTime { 0 };
|
||||||
|
|
|
@ -112,7 +112,6 @@ void EntityScriptServer::handleReloadEntityServerScriptPacket(QSharedPointer<Rec
|
||||||
|
|
||||||
if (_entityViewer.getTree() && !_shuttingDown) {
|
if (_entityViewer.getTree() && !_shuttingDown) {
|
||||||
qCDebug(entity_script_server) << "Reloading: " << entityID;
|
qCDebug(entity_script_server) << "Reloading: " << entityID;
|
||||||
_entitiesScriptEngine->unloadEntityScript(entityID);
|
|
||||||
checkAndCallPreload(entityID, true);
|
checkAndCallPreload(entityID, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -184,7 +183,6 @@ void EntityScriptServer::updateEntityPPS() {
|
||||||
pps = std::min(_maxEntityPPS, pps);
|
pps = std::min(_maxEntityPPS, pps);
|
||||||
}
|
}
|
||||||
_entityEditSender.setPacketsPerSecond(pps);
|
_entityEditSender.setPacketsPerSecond(pps);
|
||||||
qDebug() << QString("Updating entity PPS to: %1 @ %2 PPS per script = %3 PPS").arg(numRunningScripts).arg(_entityPPSPerScript).arg(pps);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
void EntityScriptServer::handleEntityServerScriptLogPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||||
|
@ -525,23 +523,25 @@ void EntityScriptServer::deletingEntity(const EntityItemID& entityID) {
|
||||||
|
|
||||||
void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, bool reload) {
|
void EntityScriptServer::entityServerScriptChanging(const EntityItemID& entityID, bool reload) {
|
||||||
if (_entityViewer.getTree() && !_shuttingDown) {
|
if (_entityViewer.getTree() && !_shuttingDown) {
|
||||||
_entitiesScriptEngine->unloadEntityScript(entityID, true);
|
|
||||||
checkAndCallPreload(entityID, reload);
|
checkAndCallPreload(entityID, reload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool reload) {
|
void EntityScriptServer::checkAndCallPreload(const EntityItemID& entityID, bool forceRedownload) {
|
||||||
if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) {
|
if (_entityViewer.getTree() && !_shuttingDown && _entitiesScriptEngine) {
|
||||||
|
|
||||||
EntityItemPointer entity = _entityViewer.getTree()->findEntityByEntityItemID(entityID);
|
EntityItemPointer entity = _entityViewer.getTree()->findEntityByEntityItemID(entityID);
|
||||||
EntityScriptDetails details;
|
EntityScriptDetails details;
|
||||||
bool notRunning = !_entitiesScriptEngine->getEntityScriptDetails(entityID, details);
|
bool isRunning = _entitiesScriptEngine->getEntityScriptDetails(entityID, details);
|
||||||
if (entity && (reload || notRunning || details.scriptText != entity->getServerScripts())) {
|
if (entity && (forceRedownload || !isRunning || details.scriptText != entity->getServerScripts())) {
|
||||||
|
if (isRunning) {
|
||||||
|
_entitiesScriptEngine->unloadEntityScript(entityID, true);
|
||||||
|
}
|
||||||
|
|
||||||
QString scriptUrl = entity->getServerScripts();
|
QString scriptUrl = entity->getServerScripts();
|
||||||
if (!scriptUrl.isEmpty()) {
|
if (!scriptUrl.isEmpty()) {
|
||||||
scriptUrl = DependencyManager::get<ResourceManager>()->normalizeURL(scriptUrl);
|
scriptUrl = DependencyManager::get<ResourceManager>()->normalizeURL(scriptUrl);
|
||||||
qCDebug(entity_script_server) << "Loading entity server script" << scriptUrl << "for" << entityID;
|
_entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, forceRedownload);
|
||||||
_entitiesScriptEngine->loadEntityScript(entityID, scriptUrl, reload);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -67,7 +67,7 @@ private:
|
||||||
void addingEntity(const EntityItemID& entityID);
|
void addingEntity(const EntityItemID& entityID);
|
||||||
void deletingEntity(const EntityItemID& entityID);
|
void deletingEntity(const EntityItemID& entityID);
|
||||||
void entityServerScriptChanging(const EntityItemID& entityID, bool reload);
|
void entityServerScriptChanging(const EntityItemID& entityID, bool reload);
|
||||||
void checkAndCallPreload(const EntityItemID& entityID, bool reload = false);
|
void checkAndCallPreload(const EntityItemID& entityID, bool forceRedownload = false);
|
||||||
|
|
||||||
void cleanupOldKilledListeners();
|
void cleanupOldKilledListeners();
|
||||||
|
|
||||||
|
|
36
cmake/macros/FixupNitpick.cmake
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
#
|
||||||
|
# FixupNitpick.cmake
|
||||||
|
# cmake/macros
|
||||||
|
#
|
||||||
|
# Copyright 2019 High Fidelity, Inc.
|
||||||
|
# Created by Nissim Hadar on January 14th, 2016
|
||||||
|
#
|
||||||
|
# Distributed under the Apache License, Version 2.0.
|
||||||
|
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
#
|
||||||
|
|
||||||
|
macro(fixup_nitpick)
|
||||||
|
if (APPLE)
|
||||||
|
string(REPLACE " " "\\ " ESCAPED_BUNDLE_NAME ${NITPICK_BUNDLE_NAME})
|
||||||
|
string(REPLACE " " "\\ " ESCAPED_INSTALL_PATH ${NITPICK_INSTALL_DIR})
|
||||||
|
set(_NITPICK_INSTALL_PATH "${ESCAPED_INSTALL_PATH}/${ESCAPED_BUNDLE_NAME}.app")
|
||||||
|
|
||||||
|
find_program(MACDEPLOYQT_COMMAND macdeployqt PATHS "${QT_DIR}/bin" NO_DEFAULT_PATH)
|
||||||
|
|
||||||
|
if (NOT MACDEPLOYQT_COMMAND AND (PRODUCTION_BUILD OR PR_BUILD))
|
||||||
|
message(FATAL_ERROR "Could not find macdeployqt at ${QT_DIR}/bin.\
|
||||||
|
It is required to produce a relocatable nitpick application.\
|
||||||
|
Check that the environment variable QT_DIR points to your Qt installation.\
|
||||||
|
")
|
||||||
|
endif ()
|
||||||
|
|
||||||
|
install(CODE "
|
||||||
|
execute_process(COMMAND ${MACDEPLOYQT_COMMAND}\
|
||||||
|
\${CMAKE_INSTALL_PREFIX}/${_NITPICK_INSTALL_PATH}/\
|
||||||
|
-verbose=2 -qmldir=${CMAKE_SOURCE_DIR}/interface/resources/qml/\
|
||||||
|
)"
|
||||||
|
COMPONENT ${CLIENT_COMPONENT}
|
||||||
|
)
|
||||||
|
|
||||||
|
endif ()
|
||||||
|
endmacro()
|
|
@ -10,5 +10,5 @@
|
||||||
#
|
#
|
||||||
|
|
||||||
macro(include_hifi_library_headers LIBRARY)
|
macro(include_hifi_library_headers LIBRARY)
|
||||||
include_directories("${HIFI_LIBRARY_DIR}/${LIBRARY}/src")
|
target_include_directories(${TARGET_NAME} PRIVATE "${HIFI_LIBRARY_DIR}/${LIBRARY}/src")
|
||||||
endmacro(include_hifi_library_headers _library _root_dir)
|
endmacro(include_hifi_library_headers _library _root_dir)
|
|
@ -19,8 +19,8 @@ function(LINK_HIFI_LIBRARIES)
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
foreach(HIFI_LIBRARY ${LIBRARIES_TO_LINK})
|
foreach(HIFI_LIBRARY ${LIBRARIES_TO_LINK})
|
||||||
include_directories("${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src")
|
target_include_directories(${TARGET_NAME} PRIVATE "${HIFI_LIBRARY_DIR}/${HIFI_LIBRARY}/src")
|
||||||
include_directories("${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}")
|
target_include_directories(${TARGET_NAME} PRIVATE "${CMAKE_BINARY_DIR}/libraries/${HIFI_LIBRARY}")
|
||||||
# link the actual library - it is static so don't bubble it up
|
# link the actual library - it is static so don't bubble it up
|
||||||
target_link_libraries(${TARGET_NAME} ${HIFI_LIBRARY})
|
target_link_libraries(${TARGET_NAME} ${HIFI_LIBRARY})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
|
@ -77,6 +77,9 @@ macro(SET_PACKAGING_PARAMETERS)
|
||||||
add_definitions(-DDEV_BUILD)
|
add_definitions(-DDEV_BUILD)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
|
set(NITPICK_BUNDLE_NAME "nitpick")
|
||||||
|
set(NITPICK_ICON_PREFIX "nitpick")
|
||||||
|
|
||||||
string(TIMESTAMP BUILD_TIME "%d/%m/%Y")
|
string(TIMESTAMP BUILD_TIME "%d/%m/%Y")
|
||||||
|
|
||||||
# if STABLE_BUILD is 1, PRODUCTION_BUILD must be 1 and
|
# if STABLE_BUILD is 1, PRODUCTION_BUILD must be 1 and
|
||||||
|
@ -140,8 +143,9 @@ macro(SET_PACKAGING_PARAMETERS)
|
||||||
|
|
||||||
set(DMG_SUBFOLDER_ICON "${HF_CMAKE_DIR}/installer/install-folder.rsrc")
|
set(DMG_SUBFOLDER_ICON "${HF_CMAKE_DIR}/installer/install-folder.rsrc")
|
||||||
|
|
||||||
set(CONSOLE_INSTALL_DIR ${DMG_SUBFOLDER_NAME})
|
set(CONSOLE_INSTALL_DIR ${DMG_SUBFOLDER_NAME})
|
||||||
set(INTERFACE_INSTALL_DIR ${DMG_SUBFOLDER_NAME})
|
set(INTERFACE_INSTALL_DIR ${DMG_SUBFOLDER_NAME})
|
||||||
|
set(NITPICK_INSTALL_DIR ${DMG_SUBFOLDER_NAME})
|
||||||
|
|
||||||
if (CLIENT_ONLY)
|
if (CLIENT_ONLY)
|
||||||
set(CONSOLE_EXEC_NAME "Console.app")
|
set(CONSOLE_EXEC_NAME "Console.app")
|
||||||
|
@ -159,11 +163,14 @@ macro(SET_PACKAGING_PARAMETERS)
|
||||||
|
|
||||||
set(INTERFACE_INSTALL_APP_PATH "${CONSOLE_INSTALL_DIR}/${INTERFACE_BUNDLE_NAME}.app")
|
set(INTERFACE_INSTALL_APP_PATH "${CONSOLE_INSTALL_DIR}/${INTERFACE_BUNDLE_NAME}.app")
|
||||||
set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.icns")
|
set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.icns")
|
||||||
|
set(NITPICK_ICON_FILENAME "${NITPICK_ICON_PREFIX}.icns")
|
||||||
else ()
|
else ()
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(CONSOLE_INSTALL_DIR "server-console")
|
set(CONSOLE_INSTALL_DIR "server-console")
|
||||||
|
set(NITPICK_INSTALL_DIR "nitpick")
|
||||||
else ()
|
else ()
|
||||||
set(CONSOLE_INSTALL_DIR ".")
|
set(CONSOLE_INSTALL_DIR ".")
|
||||||
|
set(NITPICK_INSTALL_DIR ".")
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
set(COMPONENT_INSTALL_DIR ".")
|
set(COMPONENT_INSTALL_DIR ".")
|
||||||
|
@ -173,6 +180,7 @@ macro(SET_PACKAGING_PARAMETERS)
|
||||||
if (WIN32)
|
if (WIN32)
|
||||||
set(INTERFACE_EXEC_PREFIX "interface")
|
set(INTERFACE_EXEC_PREFIX "interface")
|
||||||
set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.ico")
|
set(INTERFACE_ICON_FILENAME "${INTERFACE_ICON_PREFIX}.ico")
|
||||||
|
set(NITPICK_ICON_FILENAME "${NITPICK_ICON_PREFIX}.ico")
|
||||||
|
|
||||||
set(CONSOLE_EXEC_NAME "server-console.exe")
|
set(CONSOLE_EXEC_NAME "server-console.exe")
|
||||||
|
|
||||||
|
|
4
cmake/macros/TargetEGL.cmake
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
macro(target_egl)
|
||||||
|
find_library(EGL EGL)
|
||||||
|
target_link_libraries(${TARGET_NAME} ${EGL})
|
||||||
|
endmacro()
|
20
cmake/macros/TargetOculusMobile.cmake
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
|
||||||
|
macro(target_oculus_mobile)
|
||||||
|
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculus/VrApi)
|
||||||
|
|
||||||
|
# Mobile SDK
|
||||||
|
set(OVR_MOBILE_INCLUDE_DIRS ${INSTALL_DIR}/Include)
|
||||||
|
target_include_directories(${TARGET_NAME} PRIVATE ${OVR_MOBILE_INCLUDE_DIRS})
|
||||||
|
set(OVR_MOBILE_LIBRARY_DIR ${INSTALL_DIR}/Libs/Android/arm64-v8a)
|
||||||
|
set(OVR_MOBILE_LIBRARY_RELEASE ${OVR_MOBILE_LIBRARY_DIR}/Release/libvrapi.so)
|
||||||
|
set(OVR_MOBILE_LIBRARY_DEBUG ${OVR_MOBILE_LIBRARY_DIR}/Debug/libvrapi.so)
|
||||||
|
select_library_configurations(OVR_MOBILE)
|
||||||
|
target_link_libraries(${TARGET_NAME} ${OVR_MOBILE_LIBRARIES})
|
||||||
|
|
||||||
|
# Platform SDK
|
||||||
|
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/oculusPlatform)
|
||||||
|
set(OVR_PLATFORM_INCLUDE_DIRS ${INSTALL_DIR}/Include)
|
||||||
|
target_include_directories(${TARGET_NAME} PRIVATE ${OVR_PLATFORM_INCLUDE_DIRS})
|
||||||
|
set(OVR_PLATFORM_LIBRARIES ${INSTALL_DIR}/Android/libs/arm64-v8a/libovrplatformloader.so)
|
||||||
|
target_link_libraries(${TARGET_NAME} ${OVR_PLATFORM_LIBRARIES})
|
||||||
|
endmacro()
|
|
@ -1,85 +0,0 @@
|
||||||
set(TARGET_NAME gvr-interface)
|
|
||||||
|
|
||||||
if (ANDROID)
|
|
||||||
set(ANDROID_APK_BUILD_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk-build")
|
|
||||||
set(ANDROID_APK_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/apk")
|
|
||||||
|
|
||||||
set(ANDROID_SDK_ROOT $ENV{ANDROID_HOME})
|
|
||||||
set(ANDROID_APP_DISPLAY_NAME Interface)
|
|
||||||
set(ANDROID_API_LEVEL 19)
|
|
||||||
set(ANDROID_APK_PACKAGE io.highfidelity.gvrinterface)
|
|
||||||
set(ANDROID_ACTIVITY_NAME io.highfidelity.gvrinterface.InterfaceActivity)
|
|
||||||
set(ANDROID_APK_VERSION_NAME "0.1")
|
|
||||||
set(ANDROID_APK_VERSION_CODE 1)
|
|
||||||
set(ANDROID_APK_FULLSCREEN TRUE)
|
|
||||||
set(ANDROID_DEPLOY_QT_INSTALL "--install")
|
|
||||||
|
|
||||||
set(BUILD_SHARED_LIBS ON)
|
|
||||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${ANDROID_APK_OUTPUT_DIR}/libs/${ANDROID_ABI}")
|
|
||||||
|
|
||||||
setup_hifi_library(Gui AndroidExtras)
|
|
||||||
else ()
|
|
||||||
setup_hifi_project(Gui)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
include_directories(${Qt5Gui_PRIVATE_INCLUDE_DIRS})
|
|
||||||
|
|
||||||
link_hifi_libraries(shared networking audio-client avatars)
|
|
||||||
|
|
||||||
if (ANDROID)
|
|
||||||
find_package(LibOVR)
|
|
||||||
|
|
||||||
if (LIBOVR_FOUND)
|
|
||||||
add_definitions(-DHAVE_LIBOVR)
|
|
||||||
target_link_libraries(${TARGET_NAME} ${LIBOVR_LIBRARIES} ${LIBOVR_ANDROID_LIBRARIES} ${TURBOJPEG_LIBRARY})
|
|
||||||
include_directories(SYSTEM ${LIBOVR_INCLUDE_DIRS})
|
|
||||||
|
|
||||||
# we need VRLib, so add a project.properties to our apk build folder that says that
|
|
||||||
file(RELATIVE_PATH RELATIVE_VRLIB_PATH ${ANDROID_APK_OUTPUT_DIR} "${LIBOVR_VRLIB_DIR}")
|
|
||||||
file(WRITE "${ANDROID_APK_BUILD_DIR}/project.properties" "android.library.reference.1=${RELATIVE_VRLIB_PATH}")
|
|
||||||
|
|
||||||
list(APPEND IGNORE_COPY_LIBS ${LIBOVR_ANDROID_LIBRARIES})
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
# the presence of a HOCKEY_APP_ID means we are making a beta build
|
|
||||||
if (ANDROID AND HOCKEY_APP_ID)
|
|
||||||
set(HOCKEY_APP_ENABLED true)
|
|
||||||
set(HOCKEY_APP_ACTIVITY "<activity android:name='net.hockeyapp.android.UpdateActivity' />\n")
|
|
||||||
set(ANDROID_ACTIVITY_NAME io.highfidelity.gvrinterface.InterfaceBetaActivity)
|
|
||||||
set(ANDROID_DEPLOY_QT_INSTALL "")
|
|
||||||
set(ANDROID_APK_CUSTOM_NAME "Interface-beta.apk")
|
|
||||||
|
|
||||||
# set the ANDROID_APK_VERSION_CODE to the number of git commits
|
|
||||||
execute_process(
|
|
||||||
COMMAND git rev-list --first-parent --count HEAD
|
|
||||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
|
||||||
OUTPUT_VARIABLE GIT_COMMIT_COUNT
|
|
||||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
|
||||||
)
|
|
||||||
|
|
||||||
set(ANDROID_APK_VERSION_CODE ${GIT_COMMIT_COUNT})
|
|
||||||
|
|
||||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/templates/InterfaceBetaActivity.java.in" "${ANDROID_APK_BUILD_DIR}/src/io/highfidelity/gvrinterface/InterfaceBetaActivity.java")
|
|
||||||
elseif (ANDROID)
|
|
||||||
set(HOCKEY_APP_ENABLED false)
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if (ANDROID)
|
|
||||||
|
|
||||||
set(HIFI_URL_INTENT "<intent-filter>\
|
|
||||||
\n <action android:name='android.intent.action.VIEW' />\
|
|
||||||
\n <category android:name='android.intent.category.DEFAULT' />\
|
|
||||||
\n <category android:name='android.intent.category.BROWSABLE' />\
|
|
||||||
\n <data android:scheme='hifi' />\
|
|
||||||
\n </intent-filter>"
|
|
||||||
)
|
|
||||||
|
|
||||||
set(ANDROID_EXTRA_APPLICATION_XML "${HOCKEY_APP_ACTIVITY}")
|
|
||||||
set(ANDROID_EXTRA_ACTIVITY_XML "${HIFI_URL_INTENT}")
|
|
||||||
|
|
||||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/templates/hockeyapp.xml.in" "${ANDROID_APK_BUILD_DIR}/res/values/hockeyapp.xml")
|
|
||||||
qt_create_apk()
|
|
||||||
|
|
||||||
endif (ANDROID)
|
|
Before Width: | Height: | Size: 9.7 KiB |
|
@ -1,73 +0,0 @@
|
||||||
//
|
|
||||||
// Client.cpp
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/20/15.
|
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "Client.h"
|
|
||||||
|
|
||||||
#include <AccountManager.h>
|
|
||||||
#include <AddressManager.h>
|
|
||||||
#include <HifiSockAddr.h>
|
|
||||||
#include <NodeList.h>
|
|
||||||
#include <PacketHeaders.h>
|
|
||||||
|
|
||||||
Client::Client(QObject* parent) :
|
|
||||||
QObject(parent)
|
|
||||||
{
|
|
||||||
// we need to make sure that required dependencies are created
|
|
||||||
DependencyManager::set<AddressManager>();
|
|
||||||
|
|
||||||
setupNetworking();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Client::setupNetworking() {
|
|
||||||
// once Application order of instantiation is fixed this should be done from AccountManager
|
|
||||||
AccountManager::getInstance().setAuthURL(DEFAULT_NODE_AUTH_URL);
|
|
||||||
|
|
||||||
// setup the NodeList for this client
|
|
||||||
DependencyManager::registerInheritance<LimitedNodeList, NodeList>();
|
|
||||||
auto nodeList = DependencyManager::set<NodeList>(NodeType::Agent, 0);
|
|
||||||
|
|
||||||
// while datagram processing remains simple for targets using Client, we'll handle datagrams
|
|
||||||
connect(&nodeList->getNodeSocket(), &QUdpSocket::readyRead, this, &Client::processDatagrams);
|
|
||||||
|
|
||||||
// every second, ask the NodeList to check in with the domain server
|
|
||||||
QTimer* domainCheckInTimer = new QTimer(this);
|
|
||||||
domainCheckInTimer->setInterval(DOMAIN_SERVER_CHECK_IN_MSECS);
|
|
||||||
connect(domainCheckInTimer, &QTimer::timeout, nodeList.data(), &NodeList::sendDomainServerCheckIn);
|
|
||||||
|
|
||||||
// TODO: once the Client knows its Address on start-up we should be able to immediately send a check in here
|
|
||||||
domainCheckInTimer->start();
|
|
||||||
|
|
||||||
// handle the case where the domain stops talking to us
|
|
||||||
// TODO: can we just have the nodelist do this when it sets up? Is there a user of the NodeList that wouldn't want this?
|
|
||||||
connect(nodeList.data(), &NodeList::limitOfSilentDomainCheckInsReached, nodeList.data(), &NodeList::reset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Client::processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket) {
|
|
||||||
DependencyManager::get<NodeList>()->processNodeData(senderSockAddr, incomingPacket);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Client::processDatagrams() {
|
|
||||||
HifiSockAddr senderSockAddr;
|
|
||||||
|
|
||||||
static QByteArray incomingPacket;
|
|
||||||
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
|
|
||||||
while (DependencyManager::get<NodeList>()->getNodeSocket().hasPendingDatagrams()) {
|
|
||||||
incomingPacket.resize(nodeList->getNodeSocket().pendingDatagramSize());
|
|
||||||
nodeList->getNodeSocket().readDatagram(incomingPacket.data(), incomingPacket.size(),
|
|
||||||
senderSockAddr.getAddressPointer(), senderSockAddr.getPortPointer());
|
|
||||||
|
|
||||||
if (nodeList->packetVersionAndHashMatch(incomingPacket)) {
|
|
||||||
processVerifiedPacket(senderSockAddr, incomingPacket);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,33 +0,0 @@
|
||||||
//
|
|
||||||
// Client.h
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/20/15.
|
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_Client_h
|
|
||||||
#define hifi_Client_h
|
|
||||||
|
|
||||||
#include <QtCore/QObject>
|
|
||||||
|
|
||||||
#include <HifiSockAddr.h>
|
|
||||||
|
|
||||||
class Client : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
Client(QObject* parent = 0);
|
|
||||||
|
|
||||||
virtual void cleanupBeforeQuit() = 0;
|
|
||||||
protected:
|
|
||||||
|
|
||||||
void setupNetworking();
|
|
||||||
virtual void processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket);
|
|
||||||
private slots:
|
|
||||||
void processDatagrams();
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_Client_h
|
|
|
@ -1,191 +0,0 @@
|
||||||
//
|
|
||||||
// GVRInterface.cpp
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 11/18/14.
|
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "GVRInterface.h"
|
|
||||||
|
|
||||||
#ifdef ANDROID
|
|
||||||
|
|
||||||
#include <jni.h>
|
|
||||||
|
|
||||||
#include <qpa/qplatformnativeinterface.h>
|
|
||||||
#include <QtAndroidExtras/QAndroidJniEnvironment>
|
|
||||||
#include <QtAndroidExtras/QAndroidJniObject>
|
|
||||||
|
|
||||||
#ifdef HAVE_LIBOVR
|
|
||||||
|
|
||||||
#include <KeyState.h>
|
|
||||||
#include <VrApi/VrApi.h>
|
|
||||||
|
|
||||||
#endif
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <QtCore/QTimer>
|
|
||||||
#include <QtGui/QKeyEvent>
|
|
||||||
#include <QtWidgets/QMenuBar>
|
|
||||||
|
|
||||||
#include "GVRMainWindow.h"
|
|
||||||
#include "RenderingClient.h"
|
|
||||||
|
|
||||||
static QString launchURLString = QString();
|
|
||||||
|
|
||||||
#ifdef ANDROID
|
|
||||||
|
|
||||||
extern "C" {
|
|
||||||
|
|
||||||
JNIEXPORT void Java_io_highfidelity_gvrinterface_InterfaceActivity_handleHifiURL(JNIEnv *jni, jclass clazz, jstring hifiURLString) {
|
|
||||||
launchURLString = QAndroidJniObject(hifiURLString).toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
GVRInterface::GVRInterface(int argc, char* argv[]) :
|
|
||||||
QApplication(argc, argv),
|
|
||||||
_mainWindow(NULL),
|
|
||||||
_inVRMode(false)
|
|
||||||
{
|
|
||||||
setApplicationName("gvr-interface");
|
|
||||||
setOrganizationName("highfidelity");
|
|
||||||
setOrganizationDomain("io");
|
|
||||||
|
|
||||||
if (!launchURLString.isEmpty()) {
|
|
||||||
// did we get launched with a lookup URL? If so it is time to give that to the AddressManager
|
|
||||||
qDebug() << "We were opened via a hifi URL -" << launchURLString;
|
|
||||||
}
|
|
||||||
|
|
||||||
_client = new RenderingClient(this, launchURLString);
|
|
||||||
|
|
||||||
launchURLString = QString();
|
|
||||||
|
|
||||||
connect(this, &QGuiApplication::applicationStateChanged, this, &GVRInterface::handleApplicationStateChange);
|
|
||||||
|
|
||||||
#if defined(ANDROID) && defined(HAVE_LIBOVR)
|
|
||||||
QAndroidJniEnvironment jniEnv;
|
|
||||||
|
|
||||||
QPlatformNativeInterface* interface = QApplication::platformNativeInterface();
|
|
||||||
jobject activity = (jobject) interface->nativeResourceForIntegration("QtActivity");
|
|
||||||
|
|
||||||
ovr_RegisterHmtReceivers(&*jniEnv, activity);
|
|
||||||
|
|
||||||
// PLATFORMACTIVITY_REMOVAL: Temp workaround for PlatformActivity being
|
|
||||||
// stripped from UnityPlugin. Alternate is to use LOCAL_WHOLE_STATIC_LIBRARIES
|
|
||||||
// but that increases the size of the plugin by ~1MiB
|
|
||||||
OVR::linkerPlatformActivity++;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// call our idle function whenever we can
|
|
||||||
QTimer* idleTimer = new QTimer(this);
|
|
||||||
connect(idleTimer, &QTimer::timeout, this, &GVRInterface::idle);
|
|
||||||
idleTimer->start(0);
|
|
||||||
|
|
||||||
// call our quit handler before we go down
|
|
||||||
connect(this, &QCoreApplication::aboutToQuit, this, &GVRInterface::handleApplicationQuit);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRInterface::handleApplicationQuit() {
|
|
||||||
_client->cleanupBeforeQuit();
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRInterface::idle() {
|
|
||||||
#if defined(ANDROID) && defined(HAVE_LIBOVR)
|
|
||||||
if (!_inVRMode && ovr_IsHeadsetDocked()) {
|
|
||||||
qDebug() << "The headset just got docked - enter VR mode.";
|
|
||||||
enterVRMode();
|
|
||||||
} else if (_inVRMode) {
|
|
||||||
|
|
||||||
if (ovr_IsHeadsetDocked()) {
|
|
||||||
static int counter = 0;
|
|
||||||
|
|
||||||
// Get the latest head tracking state, predicted ahead to the midpoint of the time
|
|
||||||
// it will be displayed. It will always be corrected to the real values by
|
|
||||||
// time warp, but the closer we get, the less black will be pulled in at the edges.
|
|
||||||
const double now = ovr_GetTimeInSeconds();
|
|
||||||
static double prev;
|
|
||||||
const double rawDelta = now - prev;
|
|
||||||
prev = now;
|
|
||||||
const double clampedPrediction = std::min( 0.1, rawDelta * 2);
|
|
||||||
ovrSensorState sensor = ovrHmd_GetSensorState(OvrHmd, now + clampedPrediction, true );
|
|
||||||
|
|
||||||
auto ovrOrientation = sensor.Predicted.Pose.Orientation;
|
|
||||||
glm::quat newOrientation(ovrOrientation.w, ovrOrientation.x, ovrOrientation.y, ovrOrientation.z);
|
|
||||||
_client->setOrientation(newOrientation);
|
|
||||||
|
|
||||||
if (counter++ % 100000 == 0) {
|
|
||||||
qDebug() << "GetSensorState in frame" << counter << "-"
|
|
||||||
<< ovrOrientation.x << ovrOrientation.y << ovrOrientation.z << ovrOrientation.w;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
qDebug() << "The headset was undocked - leaving VR mode.";
|
|
||||||
|
|
||||||
leaveVRMode();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OVR::KeyState& backKeyState = _mainWindow->getBackKeyState();
|
|
||||||
auto backEvent = backKeyState.Update(ovr_GetTimeInSeconds());
|
|
||||||
|
|
||||||
if (backEvent == OVR::KeyState::KEY_EVENT_LONG_PRESS) {
|
|
||||||
qDebug() << "Attemping to start the Platform UI Activity.";
|
|
||||||
ovr_StartPackageActivity(_ovr, PUI_CLASS_NAME, PUI_GLOBAL_MENU);
|
|
||||||
} else if (backEvent == OVR::KeyState::KEY_EVENT_DOUBLE_TAP || backEvent == OVR::KeyState::KEY_EVENT_SHORT_PRESS) {
|
|
||||||
qDebug() << "Got an event we should cancel for!";
|
|
||||||
} else if (backEvent == OVR::KeyState::KEY_EVENT_DOUBLE_TAP) {
|
|
||||||
qDebug() << "The button is down!";
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRInterface::handleApplicationStateChange(Qt::ApplicationState state) {
|
|
||||||
switch(state) {
|
|
||||||
case Qt::ApplicationActive:
|
|
||||||
qDebug() << "The application is active.";
|
|
||||||
break;
|
|
||||||
case Qt::ApplicationSuspended:
|
|
||||||
qDebug() << "The application is being suspended.";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRInterface::enterVRMode() {
|
|
||||||
#if defined(ANDROID) && defined(HAVE_LIBOVR)
|
|
||||||
// Default vrModeParms
|
|
||||||
ovrModeParms vrModeParms;
|
|
||||||
vrModeParms.AsynchronousTimeWarp = true;
|
|
||||||
vrModeParms.AllowPowerSave = true;
|
|
||||||
vrModeParms.DistortionFileName = NULL;
|
|
||||||
vrModeParms.EnableImageServer = false;
|
|
||||||
vrModeParms.CpuLevel = 2;
|
|
||||||
vrModeParms.GpuLevel = 2;
|
|
||||||
vrModeParms.GameThreadTid = 0;
|
|
||||||
|
|
||||||
QAndroidJniEnvironment jniEnv;
|
|
||||||
|
|
||||||
QPlatformNativeInterface* interface = QApplication::platformNativeInterface();
|
|
||||||
jobject activity = (jobject) interface->nativeResourceForIntegration("QtActivity");
|
|
||||||
|
|
||||||
vrModeParms.ActivityObject = activity;
|
|
||||||
|
|
||||||
ovrHmdInfo hmdInfo;
|
|
||||||
_ovr = ovr_EnterVrMode(vrModeParms, &hmdInfo);
|
|
||||||
|
|
||||||
_inVRMode = true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRInterface::leaveVRMode() {
|
|
||||||
#if defined(ANDROID) && defined(HAVE_LIBOVR)
|
|
||||||
ovr_LeaveVrMode(_ovr);
|
|
||||||
_inVRMode = false;
|
|
||||||
#endif
|
|
||||||
}
|
|
|
@ -1,72 +0,0 @@
|
||||||
//
|
|
||||||
// GVRInterface.h
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 11/18/14.
|
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_GVRInterface_h
|
|
||||||
#define hifi_GVRInterface_h
|
|
||||||
|
|
||||||
#include <QtWidgets/QApplication>
|
|
||||||
|
|
||||||
#if defined(ANDROID) && defined(HAVE_LIBOVR)
|
|
||||||
class ovrMobile;
|
|
||||||
class ovrHmdInfo;
|
|
||||||
|
|
||||||
// This is set by JNI_OnLoad() when the .so is initially loaded.
|
|
||||||
// Must use to attach each thread that will use JNI:
|
|
||||||
namespace OVR {
|
|
||||||
// PLATFORMACTIVITY_REMOVAL: Temp workaround for PlatformActivity being
|
|
||||||
// stripped from UnityPlugin. Alternate is to use LOCAL_WHOLE_STATIC_LIBRARIES
|
|
||||||
// but that increases the size of the plugin by ~1MiB
|
|
||||||
extern int linkerPlatformActivity;
|
|
||||||
}
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class GVRMainWindow;
|
|
||||||
class RenderingClient;
|
|
||||||
class QKeyEvent;
|
|
||||||
|
|
||||||
#if defined(qApp)
|
|
||||||
#undef qApp
|
|
||||||
#endif
|
|
||||||
#define qApp (static_cast<GVRInterface*>(QApplication::instance()))
|
|
||||||
|
|
||||||
class GVRInterface : public QApplication {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
GVRInterface(int argc, char* argv[]);
|
|
||||||
RenderingClient* getClient() { return _client; }
|
|
||||||
|
|
||||||
void setMainWindow(GVRMainWindow* mainWindow) { _mainWindow = mainWindow; }
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void keyPressEvent(QKeyEvent* event);
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void handleApplicationStateChange(Qt::ApplicationState state);
|
|
||||||
void idle();
|
|
||||||
private:
|
|
||||||
void handleApplicationQuit();
|
|
||||||
|
|
||||||
void enterVRMode();
|
|
||||||
void leaveVRMode();
|
|
||||||
|
|
||||||
#if defined(ANDROID) && defined(HAVE_LIBOVR)
|
|
||||||
ovrMobile* _ovr;
|
|
||||||
ovrHmdInfo* _hmdInfo;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
GVRMainWindow* _mainWindow;
|
|
||||||
|
|
||||||
RenderingClient* _client;
|
|
||||||
bool _inVRMode;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_GVRInterface_h
|
|
|
@ -1,176 +0,0 @@
|
||||||
//
|
|
||||||
// GVRMainWindow.cpp
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/20/14.
|
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "GVRMainWindow.h"
|
|
||||||
|
|
||||||
#include <QtGui/QKeyEvent>
|
|
||||||
#include <QtWidgets/QApplication>
|
|
||||||
#include <QtWidgets/QInputDialog>
|
|
||||||
#include <QtWidgets/QLabel>
|
|
||||||
#include <QtWidgets/QLineEdit>
|
|
||||||
#include <QtWidgets/QMenuBar>
|
|
||||||
#include <QtWidgets/QMessageBox>
|
|
||||||
#include <QtWidgets/QVBoxLayout>
|
|
||||||
|
|
||||||
#ifndef ANDROID
|
|
||||||
|
|
||||||
#include <QtWidgets/QDesktopWidget>
|
|
||||||
|
|
||||||
#elif defined(HAVE_LIBOVR)
|
|
||||||
|
|
||||||
#include <OVR_CAPI.h>
|
|
||||||
|
|
||||||
const float LIBOVR_DOUBLE_TAP_DURATION = 0.25f;
|
|
||||||
const float LIBOVR_LONG_PRESS_DURATION = 0.75f;
|
|
||||||
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#include <AddressManager.h>
|
|
||||||
|
|
||||||
#include "InterfaceView.h"
|
|
||||||
#include "LoginDialog.h"
|
|
||||||
#include "RenderingClient.h"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
GVRMainWindow::GVRMainWindow(QWidget* parent) :
|
|
||||||
QMainWindow(parent),
|
|
||||||
#if defined(ANDROID) && defined(HAVE_LIBOVR)
|
|
||||||
_backKeyState(LIBOVR_DOUBLE_TAP_DURATION, LIBOVR_LONG_PRESS_DURATION),
|
|
||||||
_wasBackKeyDown(false),
|
|
||||||
#endif
|
|
||||||
_mainLayout(NULL),
|
|
||||||
_menuBar(NULL),
|
|
||||||
_loginAction(NULL)
|
|
||||||
{
|
|
||||||
|
|
||||||
#ifndef ANDROID
|
|
||||||
const int NOTE_4_WIDTH = 2560;
|
|
||||||
const int NOTE_4_HEIGHT = 1440;
|
|
||||||
setFixedSize(NOTE_4_WIDTH / 2, NOTE_4_HEIGHT / 2);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
setupMenuBar();
|
|
||||||
|
|
||||||
QWidget* baseWidget = new QWidget(this);
|
|
||||||
|
|
||||||
// setup a layout so we can vertically align to top
|
|
||||||
_mainLayout = new QVBoxLayout(baseWidget);
|
|
||||||
_mainLayout->setAlignment(Qt::AlignTop);
|
|
||||||
|
|
||||||
// set the layout on the base widget
|
|
||||||
baseWidget->setLayout(_mainLayout);
|
|
||||||
|
|
||||||
setCentralWidget(baseWidget);
|
|
||||||
|
|
||||||
// add the interface view
|
|
||||||
new InterfaceView(baseWidget);
|
|
||||||
}
|
|
||||||
|
|
||||||
GVRMainWindow::~GVRMainWindow() {
|
|
||||||
delete _menuBar;
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRMainWindow::keyPressEvent(QKeyEvent* event) {
|
|
||||||
#ifdef ANDROID
|
|
||||||
if (event->key() == Qt::Key_Back) {
|
|
||||||
// got the Android back key, hand off to OVR KeyState
|
|
||||||
_backKeyState.HandleEvent(ovr_GetTimeInSeconds(), true, (_wasBackKeyDown ? 1 : 0));
|
|
||||||
_wasBackKeyDown = true;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
QWidget::keyPressEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRMainWindow::keyReleaseEvent(QKeyEvent* event) {
|
|
||||||
#ifdef ANDROID
|
|
||||||
if (event->key() == Qt::Key_Back) {
|
|
||||||
// release on the Android back key, hand off to OVR KeyState
|
|
||||||
_backKeyState.HandleEvent(ovr_GetTimeInSeconds(), false, 0);
|
|
||||||
_wasBackKeyDown = false;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
QWidget::keyReleaseEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRMainWindow::setupMenuBar() {
|
|
||||||
QMenu* fileMenu = new QMenu("File");
|
|
||||||
QMenu* helpMenu = new QMenu("Help");
|
|
||||||
|
|
||||||
_menuBar = new QMenuBar(0);
|
|
||||||
|
|
||||||
_menuBar->addMenu(fileMenu);
|
|
||||||
_menuBar->addMenu(helpMenu);
|
|
||||||
|
|
||||||
QAction* goToAddress = new QAction("Go to Address", fileMenu);
|
|
||||||
connect(goToAddress, &QAction::triggered, this, &GVRMainWindow::showAddressBar);
|
|
||||||
fileMenu->addAction(goToAddress);
|
|
||||||
|
|
||||||
_loginAction = new QAction("Login", fileMenu);
|
|
||||||
fileMenu->addAction(_loginAction);
|
|
||||||
|
|
||||||
// change the login action depending on our logged in/out state
|
|
||||||
AccountManager& accountManager = AccountManager::getInstance();
|
|
||||||
connect(&accountManager, &AccountManager::loginComplete, this, &GVRMainWindow::refreshLoginAction);
|
|
||||||
connect(&accountManager, &AccountManager::logoutComplete, this, &GVRMainWindow::refreshLoginAction);
|
|
||||||
|
|
||||||
// refresh the state now
|
|
||||||
refreshLoginAction();
|
|
||||||
|
|
||||||
QAction* aboutQt = new QAction("About Qt", helpMenu);
|
|
||||||
connect(aboutQt, &QAction::triggered, qApp, &QApplication::aboutQt);
|
|
||||||
helpMenu->addAction(aboutQt);
|
|
||||||
|
|
||||||
setMenuBar(_menuBar);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRMainWindow::showAddressBar() {
|
|
||||||
// setup the address QInputDialog
|
|
||||||
QInputDialog* addressDialog = new QInputDialog(this);
|
|
||||||
addressDialog->setLabelText("Address");
|
|
||||||
|
|
||||||
// add the address dialog to the main layout
|
|
||||||
_mainLayout->addWidget(addressDialog);
|
|
||||||
|
|
||||||
connect(addressDialog, &QInputDialog::textValueSelected,
|
|
||||||
DependencyManager::get<AddressManager>().data(), &AddressManager::handleLookupString);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRMainWindow::showLoginDialog() {
|
|
||||||
LoginDialog* loginDialog = new LoginDialog(this);
|
|
||||||
|
|
||||||
// have the acccount manager handle credentials from LoginDialog
|
|
||||||
AccountManager& accountManager = AccountManager::getInstance();
|
|
||||||
connect(loginDialog, &LoginDialog::credentialsEntered, &accountManager, &AccountManager::requestAccessToken);
|
|
||||||
connect(&accountManager, &AccountManager::loginFailed, this, &GVRMainWindow::showLoginFailure);
|
|
||||||
|
|
||||||
_mainLayout->addWidget(loginDialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRMainWindow::showLoginFailure() {
|
|
||||||
QMessageBox::warning(this, "Login Failed",
|
|
||||||
"Could not log in with that username and password. Please try again!");
|
|
||||||
}
|
|
||||||
|
|
||||||
void GVRMainWindow::refreshLoginAction() {
|
|
||||||
AccountManager& accountManager = AccountManager::getInstance();
|
|
||||||
disconnect(_loginAction, &QAction::triggered, &accountManager, 0);
|
|
||||||
|
|
||||||
if (accountManager.isLoggedIn()) {
|
|
||||||
_loginAction->setText("Logout");
|
|
||||||
connect(_loginAction, &QAction::triggered, &accountManager, &AccountManager::logout);
|
|
||||||
} else {
|
|
||||||
_loginAction->setText("Login");
|
|
||||||
connect(_loginAction, &QAction::triggered, this, &GVRMainWindow::showLoginDialog);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
//
|
|
||||||
// GVRMainWindow.h
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/20/14.
|
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_GVRMainWindow_h
|
|
||||||
#define hifi_GVRMainWindow_h
|
|
||||||
|
|
||||||
#include <QtWidgets/QMainWindow>
|
|
||||||
|
|
||||||
#if defined(ANDROID) && defined(HAVE_LIBOVR)
|
|
||||||
#include <KeyState.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
class QKeyEvent;
|
|
||||||
class QMenuBar;
|
|
||||||
class QVBoxLayout;
|
|
||||||
|
|
||||||
class GVRMainWindow : public QMainWindow {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
GVRMainWindow(QWidget* parent = 0);
|
|
||||||
~GVRMainWindow();
|
|
||||||
public slots:
|
|
||||||
void showAddressBar();
|
|
||||||
void showLoginDialog();
|
|
||||||
|
|
||||||
void showLoginFailure();
|
|
||||||
|
|
||||||
#if defined(ANDROID) && defined(HAVE_LIBOVR)
|
|
||||||
OVR::KeyState& getBackKeyState() { return _backKeyState; }
|
|
||||||
#endif
|
|
||||||
|
|
||||||
protected:
|
|
||||||
void keyPressEvent(QKeyEvent* event);
|
|
||||||
void keyReleaseEvent(QKeyEvent* event);
|
|
||||||
private slots:
|
|
||||||
void refreshLoginAction();
|
|
||||||
private:
|
|
||||||
void setupMenuBar();
|
|
||||||
|
|
||||||
#if defined(ANDROID) && defined(HAVE_LIBOVR)
|
|
||||||
OVR::KeyState _backKeyState;
|
|
||||||
bool _wasBackKeyDown;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QVBoxLayout* _mainLayout;
|
|
||||||
QMenuBar* _menuBar;
|
|
||||||
QAction* _loginAction;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_GVRMainWindow_h
|
|
|
@ -1,18 +0,0 @@
|
||||||
//
|
|
||||||
// InterfaceView.cpp
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/28/14.
|
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "InterfaceView.h"
|
|
||||||
|
|
||||||
InterfaceView::InterfaceView(QWidget* parent, Qt::WindowFlags flags) :
|
|
||||||
QOpenGLWidget(parent, flags)
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,23 +0,0 @@
|
||||||
//
|
|
||||||
// InterfaceView.h
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/28/14.
|
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_InterfaceView_h
|
|
||||||
#define hifi_InterfaceView_h
|
|
||||||
|
|
||||||
#include <QtWidgets/QOpenGLWidget>
|
|
||||||
|
|
||||||
class InterfaceView : public QOpenGLWidget {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
InterfaceView(QWidget* parent = 0, Qt::WindowFlags flags = 0);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_InterfaceView_h
|
|
|
@ -1,69 +0,0 @@
|
||||||
//
|
|
||||||
// LoginDialog.cpp
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 2015-02-03.
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "LoginDialog.h"
|
|
||||||
|
|
||||||
#include <QtWidgets/QDialogButtonBox>
|
|
||||||
#include <QtWidgets/QGridLayout>
|
|
||||||
#include <QtWidgets/QLabel>
|
|
||||||
#include <QtWidgets/QLineEdit>
|
|
||||||
#include <QtWidgets/QPushButton>
|
|
||||||
|
|
||||||
LoginDialog::LoginDialog(QWidget* parent) :
|
|
||||||
QDialog(parent)
|
|
||||||
{
|
|
||||||
setupGUI();
|
|
||||||
setWindowTitle("Login");
|
|
||||||
setModal(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoginDialog::setupGUI() {
|
|
||||||
// setup a grid layout
|
|
||||||
QGridLayout* formGridLayout = new QGridLayout(this);
|
|
||||||
|
|
||||||
_usernameLineEdit = new QLineEdit(this);
|
|
||||||
|
|
||||||
QLabel* usernameLabel = new QLabel(this);
|
|
||||||
usernameLabel->setText("Username");
|
|
||||||
usernameLabel->setBuddy(_usernameLineEdit);
|
|
||||||
|
|
||||||
formGridLayout->addWidget(usernameLabel, 0, 0);
|
|
||||||
formGridLayout->addWidget(_usernameLineEdit, 1, 0);
|
|
||||||
|
|
||||||
_passwordLineEdit = new QLineEdit(this);
|
|
||||||
_passwordLineEdit->setEchoMode(QLineEdit::Password);
|
|
||||||
|
|
||||||
QLabel* passwordLabel = new QLabel(this);
|
|
||||||
passwordLabel->setText("Password");
|
|
||||||
passwordLabel->setBuddy(_passwordLineEdit);
|
|
||||||
|
|
||||||
formGridLayout->addWidget(passwordLabel, 2, 0);
|
|
||||||
formGridLayout->addWidget(_passwordLineEdit, 3, 0);
|
|
||||||
|
|
||||||
QDialogButtonBox* buttons = new QDialogButtonBox(this);
|
|
||||||
|
|
||||||
QPushButton* okButton = buttons->addButton(QDialogButtonBox::Ok);
|
|
||||||
QPushButton* cancelButton = buttons->addButton(QDialogButtonBox::Cancel);
|
|
||||||
|
|
||||||
okButton->setText("Login");
|
|
||||||
|
|
||||||
connect(cancelButton, &QPushButton::clicked, this, &QDialog::close);
|
|
||||||
connect(okButton, &QPushButton::clicked, this, &LoginDialog::loginButtonClicked);
|
|
||||||
|
|
||||||
formGridLayout->addWidget(buttons, 4, 0, 1, 2);
|
|
||||||
|
|
||||||
setLayout(formGridLayout);
|
|
||||||
}
|
|
||||||
|
|
||||||
void LoginDialog::loginButtonClicked() {
|
|
||||||
emit credentialsEntered(_usernameLineEdit->text(), _passwordLineEdit->text());
|
|
||||||
close();
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
//
|
|
||||||
// LoginDialog.h
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 2015-02-03.
|
|
||||||
// Copyright 2015 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef hifi_LoginDialog_h
|
|
||||||
#define hifi_LoginDialog_h
|
|
||||||
|
|
||||||
#include <QtWidgets/QDialog>
|
|
||||||
|
|
||||||
class QLineEdit;
|
|
||||||
|
|
||||||
class LoginDialog : public QDialog {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
LoginDialog(QWidget* parent = 0);
|
|
||||||
signals:
|
|
||||||
void credentialsEntered(const QString& username, const QString& password);
|
|
||||||
private slots:
|
|
||||||
void loginButtonClicked();
|
|
||||||
private:
|
|
||||||
void setupGUI();
|
|
||||||
|
|
||||||
QLineEdit* _usernameLineEdit;
|
|
||||||
QLineEdit* _passwordLineEdit;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_LoginDialog_h
|
|
|
@ -1,156 +0,0 @@
|
||||||
//
|
|
||||||
// RenderingClient.cpp
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/20/15.
|
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "RenderingClient.h"
|
|
||||||
|
|
||||||
#include <QtCore/QThread>
|
|
||||||
#include <QtWidgets/QInputDialog>
|
|
||||||
|
|
||||||
#include <AddressManager.h>
|
|
||||||
#include <AudioClient.h>
|
|
||||||
#include <AvatarHashMap.h>
|
|
||||||
#include <NodeList.h>
|
|
||||||
|
|
||||||
RenderingClient* RenderingClient::_instance = NULL;
|
|
||||||
|
|
||||||
RenderingClient::RenderingClient(QObject *parent, const QString& launchURLString) :
|
|
||||||
Client(parent)
|
|
||||||
{
|
|
||||||
_instance = this;
|
|
||||||
|
|
||||||
// connect to AddressManager and pass it the launch URL, if we have one
|
|
||||||
auto addressManager = DependencyManager::get<AddressManager>();
|
|
||||||
connect(addressManager.data(), &AddressManager::locationChangeRequired, this, &RenderingClient::goToLocation);
|
|
||||||
addressManager->loadSettings(launchURLString);
|
|
||||||
|
|
||||||
// tell the NodeList which node types all rendering clients will want to know about
|
|
||||||
DependencyManager::get<NodeList>()->addSetOfNodeTypesToNodeInterestSet(NodeSet() << NodeType::AudioMixer << NodeType::AvatarMixer);
|
|
||||||
|
|
||||||
DependencyManager::set<AvatarHashMap>();
|
|
||||||
|
|
||||||
// get our audio client setup on its own thread
|
|
||||||
auto audioClient = DependencyManager::set<AudioClient>();
|
|
||||||
audioClient->setPositionGetter(getPositionForAudio);
|
|
||||||
audioClient->setOrientationGetter(getOrientationForAudio);
|
|
||||||
audioClient->startThread();
|
|
||||||
|
|
||||||
|
|
||||||
connect(&_avatarTimer, &QTimer::timeout, this, &RenderingClient::sendAvatarPacket);
|
|
||||||
_avatarTimer.setInterval(16); // 60 FPS
|
|
||||||
_avatarTimer.start();
|
|
||||||
_fakeAvatar.setDisplayName("GearVR");
|
|
||||||
_fakeAvatar.setFaceModelURL(QUrl(DEFAULT_HEAD_MODEL_URL));
|
|
||||||
_fakeAvatar.setSkeletonModelURL(QUrl(DEFAULT_BODY_MODEL_URL));
|
|
||||||
_fakeAvatar.toByteArray(); // Creates HeadData
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderingClient::sendAvatarPacket() {
|
|
||||||
_fakeAvatar.setPosition(_position);
|
|
||||||
_fakeAvatar.setHeadOrientation(_orientation);
|
|
||||||
|
|
||||||
QByteArray packet = byteArrayWithPopulatedHeader(PacketTypeAvatarData);
|
|
||||||
packet.append(_fakeAvatar.toByteArray());
|
|
||||||
DependencyManager::get<NodeList>()->broadcastToNodes(packet, NodeSet() << NodeType::AvatarMixer);
|
|
||||||
_fakeAvatar.sendIdentityPacket();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderingClient::cleanupBeforeQuit() {
|
|
||||||
DependencyManager::get<AudioClient>()->cleanupBeforeQuit();
|
|
||||||
// destroy the AudioClient so it and its thread will safely go down
|
|
||||||
DependencyManager::destroy<AudioClient>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderingClient::processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket) {
|
|
||||||
auto nodeList = DependencyManager::get<NodeList>();
|
|
||||||
PacketType incomingType = packetTypeForPacket(incomingPacket);
|
|
||||||
|
|
||||||
switch (incomingType) {
|
|
||||||
case PacketTypeAudioEnvironment:
|
|
||||||
case PacketTypeAudioStreamStats:
|
|
||||||
case PacketTypeMixedAudio:
|
|
||||||
case PacketTypeSilentAudioFrame: {
|
|
||||||
|
|
||||||
if (incomingType == PacketTypeAudioStreamStats) {
|
|
||||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "parseAudioStreamStatsPacket",
|
|
||||||
Qt::QueuedConnection,
|
|
||||||
Q_ARG(QByteArray, incomingPacket));
|
|
||||||
} else if (incomingType == PacketTypeAudioEnvironment) {
|
|
||||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "parseAudioEnvironmentData",
|
|
||||||
Qt::QueuedConnection,
|
|
||||||
Q_ARG(QByteArray, incomingPacket));
|
|
||||||
} else {
|
|
||||||
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(), "addReceivedAudioToStream",
|
|
||||||
Qt::QueuedConnection,
|
|
||||||
Q_ARG(QByteArray, incomingPacket));
|
|
||||||
}
|
|
||||||
|
|
||||||
// update having heard from the audio-mixer and record the bytes received
|
|
||||||
SharedNodePointer audioMixer = nodeList->sendingNodeForPacket(incomingPacket);
|
|
||||||
|
|
||||||
if (audioMixer) {
|
|
||||||
audioMixer->setLastHeardMicrostamp(usecTimestampNow());
|
|
||||||
}
|
|
||||||
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case PacketTypeBulkAvatarData:
|
|
||||||
case PacketTypeKillAvatar:
|
|
||||||
case PacketTypeAvatarIdentity:
|
|
||||||
case PacketTypeAvatarBillboard: {
|
|
||||||
// update having heard from the avatar-mixer and record the bytes received
|
|
||||||
SharedNodePointer avatarMixer = nodeList->sendingNodeForPacket(incomingPacket);
|
|
||||||
|
|
||||||
if (avatarMixer) {
|
|
||||||
avatarMixer->setLastHeardMicrostamp(usecTimestampNow());
|
|
||||||
|
|
||||||
QMetaObject::invokeMethod(DependencyManager::get<AvatarHashMap>().data(),
|
|
||||||
"processAvatarMixerDatagram",
|
|
||||||
Q_ARG(const QByteArray&, incomingPacket),
|
|
||||||
Q_ARG(const QWeakPointer<Node>&, avatarMixer));
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
Client::processVerifiedPacket(senderSockAddr, incomingPacket);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RenderingClient::goToLocation(const glm::vec3& newPosition,
|
|
||||||
bool hasOrientationChange, const glm::quat& newOrientation,
|
|
||||||
bool shouldFaceLocation) {
|
|
||||||
qDebug().nospace() << "RenderingClient goToLocation - moving to " << newPosition.x << ", "
|
|
||||||
<< newPosition.y << ", " << newPosition.z;
|
|
||||||
|
|
||||||
glm::vec3 shiftedPosition = newPosition;
|
|
||||||
|
|
||||||
if (hasOrientationChange) {
|
|
||||||
qDebug().nospace() << "RenderingClient goToLocation - new orientation is "
|
|
||||||
<< newOrientation.x << ", " << newOrientation.y << ", " << newOrientation.z << ", " << newOrientation.w;
|
|
||||||
|
|
||||||
// orient the user to face the target
|
|
||||||
glm::quat quatOrientation = newOrientation;
|
|
||||||
|
|
||||||
if (shouldFaceLocation) {
|
|
||||||
|
|
||||||
quatOrientation = newOrientation * glm::angleAxis(PI, glm::vec3(0.0f, 1.0f, 0.0f));
|
|
||||||
|
|
||||||
// move the user a couple units away
|
|
||||||
const float DISTANCE_TO_USER = 2.0f;
|
|
||||||
shiftedPosition = newPosition - quatOrientation * glm::vec3( 0.0f, 0.0f,-1.0f) * DISTANCE_TO_USER;
|
|
||||||
}
|
|
||||||
|
|
||||||
_orientation = quatOrientation;
|
|
||||||
}
|
|
||||||
|
|
||||||
_position = shiftedPosition;
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
//
|
|
||||||
// RenderingClient.h
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/20/15.
|
|
||||||
// Copyright 2013 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
|
|
||||||
#ifndef hifi_RenderingClient_h
|
|
||||||
#define hifi_RenderingClient_h
|
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
#include <glm/gtc/quaternion.hpp>
|
|
||||||
|
|
||||||
#include <QTimer>
|
|
||||||
|
|
||||||
#include <AvatarData.h>
|
|
||||||
|
|
||||||
#include "Client.h"
|
|
||||||
|
|
||||||
class RenderingClient : public Client {
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
RenderingClient(QObject* parent = 0, const QString& launchURLString = QString());
|
|
||||||
|
|
||||||
const glm::vec3& getPosition() const { return _position; }
|
|
||||||
const glm::quat& getOrientation() const { return _orientation; }
|
|
||||||
void setOrientation(const glm::quat& orientation) { _orientation = orientation; }
|
|
||||||
|
|
||||||
static glm::vec3 getPositionForAudio() { return _instance->getPosition(); }
|
|
||||||
static glm::quat getOrientationForAudio() { return _instance->getOrientation(); }
|
|
||||||
|
|
||||||
virtual void cleanupBeforeQuit();
|
|
||||||
|
|
||||||
private slots:
|
|
||||||
void goToLocation(const glm::vec3& newPosition,
|
|
||||||
bool hasOrientationChange, const glm::quat& newOrientation,
|
|
||||||
bool shouldFaceLocation);
|
|
||||||
void sendAvatarPacket();
|
|
||||||
|
|
||||||
private:
|
|
||||||
virtual void processVerifiedPacket(const HifiSockAddr& senderSockAddr, const QByteArray& incomingPacket);
|
|
||||||
|
|
||||||
static RenderingClient* _instance;
|
|
||||||
|
|
||||||
glm::vec3 _position;
|
|
||||||
glm::quat _orientation;
|
|
||||||
|
|
||||||
QTimer _avatarTimer;
|
|
||||||
AvatarData _fakeAvatar;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // hifi_RenderingClient_h
|
|
|
@ -1,41 +0,0 @@
|
||||||
//
|
|
||||||
// InterfaceActivity.java
|
|
||||||
// gvr-interface/java
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/26/15.
|
|
||||||
// Copyright 2015 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
|
|
||||||
//
|
|
||||||
|
|
||||||
package io.highfidelity.gvrinterface;
|
|
||||||
|
|
||||||
import android.content.Intent;
|
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
|
||||||
import android.view.WindowManager;
|
|
||||||
import android.util.Log;
|
|
||||||
import org.qtproject.qt5.android.bindings.QtActivity;
|
|
||||||
|
|
||||||
public class InterfaceActivity extends QtActivity {
|
|
||||||
|
|
||||||
public static native void handleHifiURL(String hifiURLString);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
||||||
|
|
||||||
// Get the intent that started this activity in case we have a hifi:// URL to parse
|
|
||||||
Intent intent = getIntent();
|
|
||||||
if (intent.getAction() == Intent.ACTION_VIEW) {
|
|
||||||
Uri data = intent.getData();
|
|
||||||
|
|
||||||
if (data.getScheme().equals("hifi")) {
|
|
||||||
handleHifiURL(data.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,28 +0,0 @@
|
||||||
//
|
|
||||||
// main.cpp
|
|
||||||
// gvr-interface/src
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 11/17/14.
|
|
||||||
// Copyright 2014 High Fidelity, Inc.
|
|
||||||
//
|
|
||||||
// Distributed under the Apache License, Version 2.0.
|
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "GVRMainWindow.h"
|
|
||||||
#include "GVRInterface.h"
|
|
||||||
|
|
||||||
int main(int argc, char* argv[]) {
|
|
||||||
GVRInterface app(argc, argv);
|
|
||||||
|
|
||||||
GVRMainWindow mainWindow;
|
|
||||||
#ifdef ANDROID
|
|
||||||
mainWindow.showFullScreen();
|
|
||||||
#else
|
|
||||||
mainWindow.showMaximized();
|
|
||||||
#endif
|
|
||||||
|
|
||||||
app.setMainWindow(&mainWindow);
|
|
||||||
|
|
||||||
return app.exec();
|
|
||||||
}
|
|
|
@ -1,51 +0,0 @@
|
||||||
//
|
|
||||||
// InterfaceBetaActivity.java
|
|
||||||
// gvr-interface/java
|
|
||||||
//
|
|
||||||
// Created by Stephen Birarda on 1/27/15.
|
|
||||||
// Copyright 2015 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
|
|
||||||
//
|
|
||||||
|
|
||||||
package io.highfidelity.gvrinterface;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import net.hockeyapp.android.CrashManager;
|
|
||||||
import net.hockeyapp.android.UpdateManager;
|
|
||||||
|
|
||||||
public class InterfaceBetaActivity extends InterfaceActivity {
|
|
||||||
|
|
||||||
public String _hockeyAppID;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
|
||||||
super.onCreate(savedInstanceState);
|
|
||||||
|
|
||||||
_hockeyAppID = getString(R.string.HockeyAppID);
|
|
||||||
|
|
||||||
checkForUpdates();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onPause() {
|
|
||||||
super.onPause();
|
|
||||||
UpdateManager.unregister();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void onResume() {
|
|
||||||
super.onResume();
|
|
||||||
checkForCrashes();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkForCrashes() {
|
|
||||||
CrashManager.register(this, _hockeyAppID);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkForUpdates() {
|
|
||||||
// Remove this for store / production builds!
|
|
||||||
UpdateManager.register(this, _hockeyAppID);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
<?xml version='1.0' encoding='utf-8'?>
|
|
||||||
<resources>
|
|
||||||
<string name="HockeyAppID">${HOCKEY_APP_ID}</string>
|
|
||||||
<bool name="HockeyAppEnabled">${HOCKEY_APP_ENABLED}</bool>
|
|
||||||
</resources>
|
|
|
@ -52,6 +52,13 @@ ANDROID_PACKAGES = {
|
||||||
'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release',
|
'sharedLibFolder': 'VrApi/Libs/Android/arm64-v8a/Release',
|
||||||
'includeLibs': ['libvrapi.so']
|
'includeLibs': ['libvrapi.so']
|
||||||
},
|
},
|
||||||
|
'oculusPlatform': {
|
||||||
|
'file': 'OVRPlatformSDK_v1.32.0.zip',
|
||||||
|
'versionId': 'jG9DB16zOGxSrmtZy4jcQnwO0TJUuaeL',
|
||||||
|
'checksum': 'ab5b203b3a39a56ab148d68fff769e05',
|
||||||
|
'sharedLibFolder': 'Android/libs/arm64-v8a',
|
||||||
|
'includeLibs': ['libovrplatformloader.so']
|
||||||
|
},
|
||||||
'openssl': {
|
'openssl': {
|
||||||
'file': 'openssl-1.1.0g_armv8.tgz',
|
'file': 'openssl-1.1.0g_armv8.tgz',
|
||||||
'versionId': 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW',
|
'versionId': 'AiiPjmgUZTgNj7YV1EEx2lL47aDvvvAW',
|
||||||
|
|
|
@ -265,7 +265,7 @@ foreach(EXTERNAL ${OPTIONAL_EXTERNALS})
|
||||||
endforeach()
|
endforeach()
|
||||||
|
|
||||||
# include headers for interface and InterfaceConfig.
|
# include headers for interface and InterfaceConfig.
|
||||||
include_directories("${PROJECT_SOURCE_DIR}/src")
|
target_include_directories(${TARGET_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/src")
|
||||||
|
|
||||||
if (ANDROID)
|
if (ANDROID)
|
||||||
find_library(ANDROID_LOG_LIB log)
|
find_library(ANDROID_LOG_LIB log)
|
||||||
|
|
0
interface/resources/icons/+android/backward.svg → interface/resources/icons/+android_interface/backward.svg
Executable file → Normal file
Before Width: | Height: | Size: 735 B After Width: | Height: | Size: 735 B |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 3.5 KiB |
0
interface/resources/icons/+android/go-a.svg → interface/resources/icons/+android_interface/go-a.svg
Executable file → Normal file
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.7 KiB After Width: | Height: | Size: 3.7 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 94 KiB |
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
0
interface/resources/icons/+android/mic-unmute-a.svg → interface/resources/icons/+android_interface/mic-unmute-a.svg
Executable file → Normal file
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 1.3 KiB |
0
interface/resources/icons/+android/myview-a.svg → interface/resources/icons/+android_interface/myview-a.svg
Executable file → Normal file
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
0
interface/resources/icons/+android/myview-hover.svg → interface/resources/icons/+android_interface/myview-hover.svg
Executable file → Normal file
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.6 KiB |
0
interface/resources/icons/+android/myview-i.svg → interface/resources/icons/+android_interface/myview-i.svg
Executable file → Normal file
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 655 B After Width: | Height: | Size: 655 B |
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 95 KiB After Width: | Height: | Size: 95 KiB |
BIN
interface/resources/images/unsupportedImage.png
Normal file
After Width: | Height: | Size: 24 KiB |
275
interface/resources/qml/+webengine/Browser.qml
Normal file
|
@ -0,0 +1,275 @@
|
||||||
|
import QtQuick 2.5
|
||||||
|
import QtWebChannel 1.0
|
||||||
|
import QtWebEngine 1.5
|
||||||
|
|
||||||
|
import controlsUit 1.0
|
||||||
|
import stylesUit 1.0
|
||||||
|
import "qrc:////qml//windows"
|
||||||
|
|
||||||
|
ScrollingWindow {
|
||||||
|
id: root
|
||||||
|
HifiConstants { id: hifi }
|
||||||
|
//HifiStyles.HifiConstants { id: hifistyles }
|
||||||
|
title: "Browser"
|
||||||
|
resizable: true
|
||||||
|
destroyOnHidden: true
|
||||||
|
width: 800
|
||||||
|
height: 600
|
||||||
|
property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
|
||||||
|
property alias url: webview.url
|
||||||
|
property alias webView: webview
|
||||||
|
|
||||||
|
signal loadingChanged(int status)
|
||||||
|
|
||||||
|
x: 100
|
||||||
|
y: 100
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
focus = true
|
||||||
|
shown = true
|
||||||
|
addressBar.text = webview.url
|
||||||
|
}
|
||||||
|
|
||||||
|
function setProfile(profile) {
|
||||||
|
webview.profile = profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
function showPermissionsBar(){
|
||||||
|
permissionsContainer.visible=true;
|
||||||
|
}
|
||||||
|
|
||||||
|
function hidePermissionsBar(){
|
||||||
|
permissionsContainer.visible=false;
|
||||||
|
}
|
||||||
|
|
||||||
|
function allowPermissions(){
|
||||||
|
webview.grantFeaturePermission(permissionsBar.securityOrigin, permissionsBar.feature, true);
|
||||||
|
hidePermissionsBar();
|
||||||
|
}
|
||||||
|
|
||||||
|
function setAutoAdd(auto) {
|
||||||
|
desktop.setAutoAdd(auto);
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id:item
|
||||||
|
width: pane.contentWidth
|
||||||
|
implicitHeight: pane.scrollHeight
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: buttons
|
||||||
|
spacing: 4
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 8
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 8
|
||||||
|
HiFiGlyphs {
|
||||||
|
id: back;
|
||||||
|
enabled: webview.canGoBack;
|
||||||
|
text: hifi.glyphs.backward
|
||||||
|
color: enabled ? hifi.colors.text : hifi.colors.disabledText
|
||||||
|
size: 48
|
||||||
|
MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
|
||||||
|
}
|
||||||
|
|
||||||
|
HiFiGlyphs {
|
||||||
|
id: forward;
|
||||||
|
enabled: webview.canGoForward;
|
||||||
|
text: hifi.glyphs.forward
|
||||||
|
color: enabled ? hifi.colors.text : hifi.colors.disabledText
|
||||||
|
size: 48
|
||||||
|
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
|
||||||
|
}
|
||||||
|
|
||||||
|
HiFiGlyphs {
|
||||||
|
id: reload;
|
||||||
|
enabled: webview.canGoForward;
|
||||||
|
text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload
|
||||||
|
color: enabled ? hifi.colors.text : hifi.colors.disabledText
|
||||||
|
size: 48
|
||||||
|
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: border
|
||||||
|
height: 48
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 8
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
anchors.left: buttons.right
|
||||||
|
anchors.leftMargin: 8
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: barIcon
|
||||||
|
width: parent.height
|
||||||
|
height: parent.height
|
||||||
|
Image {
|
||||||
|
source: webview.icon;
|
||||||
|
x: (parent.height - height) / 2
|
||||||
|
y: (parent.width - width) / 2
|
||||||
|
sourceSize: Qt.size(width, height);
|
||||||
|
verticalAlignment: Image.AlignVCenter;
|
||||||
|
horizontalAlignment: Image.AlignHCenter
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
TextField {
|
||||||
|
id: addressBar
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 8
|
||||||
|
anchors.left: barIcon.right
|
||||||
|
anchors.leftMargin: 0
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
focus: true
|
||||||
|
colorScheme: hifi.colorSchemes.dark
|
||||||
|
placeholderText: "Enter URL"
|
||||||
|
Component.onCompleted: ScriptDiscoveryService.scriptsModelFilter.filterRegExp = new RegExp("^.*$", "i")
|
||||||
|
Keys.onPressed: {
|
||||||
|
switch(event.key) {
|
||||||
|
case Qt.Key_Enter:
|
||||||
|
case Qt.Key_Return:
|
||||||
|
event.accepted = true
|
||||||
|
if (text.indexOf("http") != 0) {
|
||||||
|
text = "http://" + text;
|
||||||
|
}
|
||||||
|
root.hidePermissionsBar();
|
||||||
|
root.keyboardRaised = false;
|
||||||
|
webview.url = text;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id:permissionsContainer
|
||||||
|
visible:false
|
||||||
|
color: "#000000"
|
||||||
|
width: parent.width
|
||||||
|
anchors.top: buttons.bottom
|
||||||
|
height:40
|
||||||
|
z:100
|
||||||
|
gradient: Gradient {
|
||||||
|
GradientStop { position: 0.0; color: "black" }
|
||||||
|
GradientStop { position: 1.0; color: "grey" }
|
||||||
|
}
|
||||||
|
|
||||||
|
RalewayLight {
|
||||||
|
id: permissionsInfo
|
||||||
|
anchors.right:permissionsRow.left
|
||||||
|
anchors.rightMargin: 32
|
||||||
|
anchors.topMargin:8
|
||||||
|
anchors.top:parent.top
|
||||||
|
text: "This site wants to use your microphone/camera"
|
||||||
|
size: 18
|
||||||
|
color: hifi.colors.white
|
||||||
|
}
|
||||||
|
|
||||||
|
Row {
|
||||||
|
id: permissionsRow
|
||||||
|
spacing: 4
|
||||||
|
anchors.top:parent.top
|
||||||
|
anchors.topMargin: 8
|
||||||
|
anchors.right: parent.right
|
||||||
|
visible: true
|
||||||
|
z:101
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id:allow
|
||||||
|
text: "Allow"
|
||||||
|
color: hifi.buttons.blue
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
width: 120
|
||||||
|
enabled: true
|
||||||
|
onClicked: root.allowPermissions();
|
||||||
|
z:101
|
||||||
|
}
|
||||||
|
|
||||||
|
Button {
|
||||||
|
id:block
|
||||||
|
text: "Block"
|
||||||
|
color: hifi.buttons.red
|
||||||
|
colorScheme: root.colorScheme
|
||||||
|
width: 120
|
||||||
|
enabled: true
|
||||||
|
onClicked: root.hidePermissionsBar();
|
||||||
|
z:101
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebView {
|
||||||
|
id: webview
|
||||||
|
url: "https://highfidelity.com/"
|
||||||
|
profile: FileTypeProfile;
|
||||||
|
|
||||||
|
// Create a global EventBridge object for raiseAndLowerKeyboard.
|
||||||
|
WebEngineScript {
|
||||||
|
id: createGlobalEventBridge
|
||||||
|
sourceCode: eventBridgeJavaScriptToInject
|
||||||
|
injectionPoint: WebEngineScript.Deferred
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect when may want to raise and lower keyboard.
|
||||||
|
WebEngineScript {
|
||||||
|
id: raiseAndLowerKeyboard
|
||||||
|
injectionPoint: WebEngineScript.Deferred
|
||||||
|
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
|
||||||
|
|
||||||
|
anchors.top: buttons.bottom
|
||||||
|
anchors.topMargin: 8
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
onFeaturePermissionRequested: {
|
||||||
|
if (feature == 2) { // QWebEnginePage::MediaAudioCapture
|
||||||
|
grantFeaturePermission(securityOrigin, feature, true);
|
||||||
|
} else {
|
||||||
|
permissionsBar.securityOrigin = securityOrigin;
|
||||||
|
permissionsBar.feature = feature;
|
||||||
|
root.showPermissionsBar();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoadingChanged: {
|
||||||
|
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
|
||||||
|
addressBar.text = loadRequest.url
|
||||||
|
}
|
||||||
|
root.loadingChanged(loadRequest.status);
|
||||||
|
}
|
||||||
|
|
||||||
|
onWindowCloseRequested: {
|
||||||
|
root.destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
webChannel.registerObject("eventBridge", eventBridge);
|
||||||
|
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
||||||
|
desktop.initWebviewProfileHandlers(webview.profile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // item
|
||||||
|
|
||||||
|
|
||||||
|
Keys.onPressed: {
|
||||||
|
switch(event.key) {
|
||||||
|
case Qt.Key_L:
|
||||||
|
if (event.modifiers == Qt.ControlModifier) {
|
||||||
|
event.accepted = true
|
||||||
|
addressBar.selectAll()
|
||||||
|
addressBar.forceActiveFocus()
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} // dialog
|
50
interface/resources/qml/+webengine/InfoView.qml
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
//
|
||||||
|
// InfoView.qml
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 27 Apr 2015
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.5
|
||||||
|
import Hifi 1.0 as Hifi
|
||||||
|
|
||||||
|
import controlsUit 1.0
|
||||||
|
import "qrc:////qml//windows" as Windows
|
||||||
|
|
||||||
|
Windows.ScrollingWindow {
|
||||||
|
id: root
|
||||||
|
width: 800
|
||||||
|
height: 800
|
||||||
|
resizable: true
|
||||||
|
|
||||||
|
Hifi.InfoView {
|
||||||
|
id: infoView
|
||||||
|
width: pane.contentWidth
|
||||||
|
implicitHeight: pane.scrollHeight
|
||||||
|
|
||||||
|
WebView {
|
||||||
|
id: webview
|
||||||
|
objectName: "WebView"
|
||||||
|
anchors.fill: parent
|
||||||
|
url: infoView.url
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
centerWindow(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (visible) {
|
||||||
|
centerWindow(root);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function centerWindow() {
|
||||||
|
desktop.centerOnVisible(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
108
interface/resources/qml/+webengine/QmlWebWindow.qml
Normal file
|
@ -0,0 +1,108 @@
|
||||||
|
//
|
||||||
|
// QmlWebWindow.qml
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 17 Dec 2015
|
||||||
|
// Copyright 2015 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.5
|
||||||
|
import QtWebEngine 1.1
|
||||||
|
import QtWebChannel 1.0
|
||||||
|
|
||||||
|
import "qrc:////qml//windows" as Windows
|
||||||
|
import controlsUit 1.0 as Controls
|
||||||
|
import stylesUit 1.0
|
||||||
|
|
||||||
|
Windows.ScrollingWindow {
|
||||||
|
id: root
|
||||||
|
HifiConstants { id: hifi }
|
||||||
|
title: "WebWindow"
|
||||||
|
resizable: true
|
||||||
|
shown: false
|
||||||
|
// Don't destroy on close... otherwise the JS/C++ will have a dangling pointer
|
||||||
|
destroyOnCloseButton: false
|
||||||
|
property alias source: webview.url
|
||||||
|
property alias scriptUrl: webview.userScriptUrl
|
||||||
|
|
||||||
|
// This is for JS/QML communication, which is unused in a WebWindow,
|
||||||
|
// but not having this here results in spurious warnings about a
|
||||||
|
// missing signal
|
||||||
|
signal sendToScript(var message);
|
||||||
|
|
||||||
|
signal moved(vector2d position);
|
||||||
|
signal resized(size size);
|
||||||
|
|
||||||
|
function notifyMoved() {
|
||||||
|
moved(Qt.vector2d(x, y));
|
||||||
|
}
|
||||||
|
|
||||||
|
function notifyResized() {
|
||||||
|
resized(Qt.size(width, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
onXChanged: notifyMoved();
|
||||||
|
onYChanged: notifyMoved();
|
||||||
|
|
||||||
|
onWidthChanged: notifyResized();
|
||||||
|
onHeightChanged: notifyResized();
|
||||||
|
|
||||||
|
onShownChanged: {
|
||||||
|
keyboardEnabled = HMD.active;
|
||||||
|
}
|
||||||
|
|
||||||
|
Item {
|
||||||
|
width: pane.contentWidth
|
||||||
|
implicitHeight: pane.scrollHeight
|
||||||
|
|
||||||
|
Controls.WebView {
|
||||||
|
id: webview
|
||||||
|
url: "about:blank"
|
||||||
|
anchors.fill: parent
|
||||||
|
focus: true
|
||||||
|
profile: HFWebEngineProfile;
|
||||||
|
|
||||||
|
property string userScriptUrl: ""
|
||||||
|
|
||||||
|
// Create a global EventBridge object for raiseAndLowerKeyboard.
|
||||||
|
WebEngineScript {
|
||||||
|
id: createGlobalEventBridge
|
||||||
|
sourceCode: eventBridgeJavaScriptToInject
|
||||||
|
injectionPoint: WebEngineScript.DocumentCreation
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect when may want to raise and lower keyboard.
|
||||||
|
WebEngineScript {
|
||||||
|
id: raiseAndLowerKeyboard
|
||||||
|
injectionPoint: WebEngineScript.Deferred
|
||||||
|
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
// User script.
|
||||||
|
WebEngineScript {
|
||||||
|
id: userScript
|
||||||
|
sourceUrl: webview.userScriptUrl
|
||||||
|
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
||||||
|
|
||||||
|
function onWebEventReceived(event) {
|
||||||
|
if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
|
||||||
|
ApplicationInterface.addAssetToWorldFromURL(event.slice(18));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
webChannel.registerObject("eventBridge", eventBridge);
|
||||||
|
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
||||||
|
eventBridge.webEventReceived.connect(onWebEventReceived);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
125
interface/resources/qml/+webengine/TabletBrowser.qml
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
import QtQuick 2.5
|
||||||
|
import QtWebChannel 1.0
|
||||||
|
import QtWebEngine 1.5
|
||||||
|
|
||||||
|
import "controls"
|
||||||
|
import controlsUit 1.0 as HifiControls
|
||||||
|
import "styles" as HifiStyles
|
||||||
|
import stylesUit 1.0
|
||||||
|
import "windows"
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
HifiConstants { id: hifi }
|
||||||
|
HifiStyles.HifiConstants { id: hifistyles }
|
||||||
|
|
||||||
|
height: 600
|
||||||
|
property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
|
||||||
|
property alias url: webview.url
|
||||||
|
|
||||||
|
property bool canGoBack: webview.canGoBack
|
||||||
|
property bool canGoForward: webview.canGoForward
|
||||||
|
|
||||||
|
|
||||||
|
signal loadingChanged(int status)
|
||||||
|
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
|
||||||
|
function setProfile(profile) {
|
||||||
|
webview.profile = profile;
|
||||||
|
}
|
||||||
|
|
||||||
|
WebEngineView {
|
||||||
|
id: webview
|
||||||
|
objectName: "webEngineView"
|
||||||
|
x: 0
|
||||||
|
y: 0
|
||||||
|
width: parent.width
|
||||||
|
height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
|
||||||
|
|
||||||
|
profile: HFWebEngineProfile;
|
||||||
|
|
||||||
|
property string userScriptUrl: ""
|
||||||
|
|
||||||
|
// creates a global EventBridge object.
|
||||||
|
WebEngineScript {
|
||||||
|
id: createGlobalEventBridge
|
||||||
|
sourceCode: eventBridgeJavaScriptToInject
|
||||||
|
injectionPoint: WebEngineScript.DocumentCreation
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
// detects when to raise and lower virtual keyboard
|
||||||
|
WebEngineScript {
|
||||||
|
id: raiseAndLowerKeyboard
|
||||||
|
injectionPoint: WebEngineScript.Deferred
|
||||||
|
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
// User script.
|
||||||
|
WebEngineScript {
|
||||||
|
id: userScript
|
||||||
|
sourceUrl: webview.userScriptUrl
|
||||||
|
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
||||||
|
|
||||||
|
property string newUrl: ""
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
webChannel.registerObject("eventBridge", eventBridge);
|
||||||
|
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
||||||
|
|
||||||
|
// Ensure the JS from the web-engine makes it to our logging
|
||||||
|
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
|
||||||
|
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
|
||||||
|
});
|
||||||
|
|
||||||
|
webview.profile.httpUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36";
|
||||||
|
web.address = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
onFeaturePermissionRequested: {
|
||||||
|
grantFeaturePermission(securityOrigin, feature, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoadingChanged: {
|
||||||
|
keyboardRaised = false;
|
||||||
|
punctuationMode = false;
|
||||||
|
keyboard.resetShiftMode(false);
|
||||||
|
|
||||||
|
// Required to support clicking on "hifi://" links
|
||||||
|
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
|
||||||
|
urlAppend(loadRequest.url.toString())
|
||||||
|
var url = loadRequest.url.toString();
|
||||||
|
if (urlHandler.canHandleUrl(url)) {
|
||||||
|
if (urlHandler.handleUrl(url)) {
|
||||||
|
root.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewViewRequested: {
|
||||||
|
request.openIn(webView);
|
||||||
|
}
|
||||||
|
|
||||||
|
HifiControls.WebSpinner { }
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: {
|
||||||
|
switch(event.key) {
|
||||||
|
case Qt.Key_L:
|
||||||
|
if (event.modifiers == Qt.ControlModifier) {
|
||||||
|
event.accepted = true
|
||||||
|
addressBar.selectAll()
|
||||||
|
addressBar.forceActiveFocus()
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,16 +1,14 @@
|
||||||
import QtQuick 2.5
|
import QtQuick 2.5
|
||||||
import QtWebChannel 1.0
|
|
||||||
import QtWebEngine 1.5
|
|
||||||
|
|
||||||
import controlsUit 1.0
|
import controlsUit 1.0
|
||||||
import "styles" as HifiStyles
|
|
||||||
import stylesUit 1.0
|
import stylesUit 1.0
|
||||||
|
|
||||||
import "windows"
|
import "windows"
|
||||||
|
|
||||||
ScrollingWindow {
|
ScrollingWindow {
|
||||||
id: root
|
id: root
|
||||||
HifiConstants { id: hifi }
|
HifiConstants { id: hifi }
|
||||||
HifiStyles.HifiConstants { id: hifistyles }
|
//HifiStyles.HifiConstants { id: hifistyles }
|
||||||
title: "Browser"
|
title: "Browser"
|
||||||
resizable: true
|
resizable: true
|
||||||
destroyOnHidden: true
|
destroyOnHidden: true
|
||||||
|
@ -32,7 +30,6 @@ ScrollingWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
function setProfile(profile) {
|
function setProfile(profile) {
|
||||||
webview.profile = profile;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function showPermissionsBar(){
|
function showPermissionsBar(){
|
||||||
|
@ -44,7 +41,6 @@ ScrollingWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
function allowPermissions(){
|
function allowPermissions(){
|
||||||
webview.grantFeaturePermission(permissionsBar.securityOrigin, permissionsBar.feature, true);
|
|
||||||
hidePermissionsBar();
|
hidePermissionsBar();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +64,7 @@ ScrollingWindow {
|
||||||
id: back;
|
id: back;
|
||||||
enabled: webview.canGoBack;
|
enabled: webview.canGoBack;
|
||||||
text: hifi.glyphs.backward
|
text: hifi.glyphs.backward
|
||||||
color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText
|
color: enabled ? hifi.colors.text : hifi.colors.disabledText
|
||||||
size: 48
|
size: 48
|
||||||
MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
|
MouseArea { anchors.fill: parent; onClicked: webview.goBack() }
|
||||||
}
|
}
|
||||||
|
@ -77,7 +73,7 @@ ScrollingWindow {
|
||||||
id: forward;
|
id: forward;
|
||||||
enabled: webview.canGoForward;
|
enabled: webview.canGoForward;
|
||||||
text: hifi.glyphs.forward
|
text: hifi.glyphs.forward
|
||||||
color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText
|
color: enabled ? hifi.colors.text : hifi.colors.disabledText
|
||||||
size: 48
|
size: 48
|
||||||
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
|
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
|
||||||
}
|
}
|
||||||
|
@ -86,7 +82,7 @@ ScrollingWindow {
|
||||||
id: reload;
|
id: reload;
|
||||||
enabled: webview.canGoForward;
|
enabled: webview.canGoForward;
|
||||||
text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload
|
text: webview.loading ? hifi.glyphs.close : hifi.glyphs.reload
|
||||||
color: enabled ? hifistyles.colors.text : hifistyles.colors.disabledText
|
color: enabled ? hifi.colors.text : hifi.colors.disabledText
|
||||||
size: 48
|
size: 48
|
||||||
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
|
MouseArea { anchors.fill: parent; onClicked: webview.goForward() }
|
||||||
}
|
}
|
||||||
|
@ -202,61 +198,10 @@ ScrollingWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
WebView {
|
ProxyWebView {
|
||||||
id: webview
|
id: webview
|
||||||
|
anchors.centerIn: parent
|
||||||
url: "https://highfidelity.com/"
|
url: "https://highfidelity.com/"
|
||||||
profile: FileTypeProfile;
|
|
||||||
|
|
||||||
// Create a global EventBridge object for raiseAndLowerKeyboard.
|
|
||||||
WebEngineScript {
|
|
||||||
id: createGlobalEventBridge
|
|
||||||
sourceCode: eventBridgeJavaScriptToInject
|
|
||||||
injectionPoint: WebEngineScript.Deferred
|
|
||||||
worldId: WebEngineScript.MainWorld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect when may want to raise and lower keyboard.
|
|
||||||
WebEngineScript {
|
|
||||||
id: raiseAndLowerKeyboard
|
|
||||||
injectionPoint: WebEngineScript.Deferred
|
|
||||||
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
|
||||||
worldId: WebEngineScript.MainWorld
|
|
||||||
}
|
|
||||||
|
|
||||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard ]
|
|
||||||
|
|
||||||
anchors.top: buttons.bottom
|
|
||||||
anchors.topMargin: 8
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
anchors.left: parent.left
|
|
||||||
anchors.right: parent.right
|
|
||||||
|
|
||||||
onFeaturePermissionRequested: {
|
|
||||||
if (feature == 2) { // QWebEnginePage::MediaAudioCapture
|
|
||||||
grantFeaturePermission(securityOrigin, feature, true);
|
|
||||||
} else {
|
|
||||||
permissionsBar.securityOrigin = securityOrigin;
|
|
||||||
permissionsBar.feature = feature;
|
|
||||||
root.showPermissionsBar();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoadingChanged: {
|
|
||||||
if (loadRequest.status === WebEngineView.LoadSucceededStatus) {
|
|
||||||
addressBar.text = loadRequest.url
|
|
||||||
}
|
|
||||||
root.loadingChanged(loadRequest.status);
|
|
||||||
}
|
|
||||||
|
|
||||||
onWindowCloseRequested: {
|
|
||||||
root.destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
webChannel.registerObject("eventBridge", eventBridge);
|
|
||||||
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
|
||||||
desktop.initWebviewProfileHandlers(webview.profile);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // item
|
} // item
|
||||||
|
|
|
@ -19,13 +19,12 @@ Windows.ScrollingWindow {
|
||||||
width: 800
|
width: 800
|
||||||
height: 800
|
height: 800
|
||||||
resizable: true
|
resizable: true
|
||||||
|
|
||||||
Hifi.InfoView {
|
Hifi.InfoView {
|
||||||
id: infoView
|
id: infoView
|
||||||
width: pane.contentWidth
|
width: pane.contentWidth
|
||||||
implicitHeight: pane.scrollHeight
|
implicitHeight: pane.scrollHeight
|
||||||
|
|
||||||
WebView {
|
ProxyWebView {
|
||||||
id: webview
|
id: webview
|
||||||
objectName: "WebView"
|
objectName: "WebView"
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
|
|
@ -9,8 +9,6 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import QtQuick 2.5
|
import QtQuick 2.5
|
||||||
import QtWebEngine 1.1
|
|
||||||
import QtWebChannel 1.0
|
|
||||||
|
|
||||||
import "windows" as Windows
|
import "windows" as Windows
|
||||||
import controlsUit 1.0 as Controls
|
import controlsUit 1.0 as Controls
|
||||||
|
@ -60,49 +58,9 @@ Windows.ScrollingWindow {
|
||||||
Controls.WebView {
|
Controls.WebView {
|
||||||
id: webview
|
id: webview
|
||||||
url: "about:blank"
|
url: "about:blank"
|
||||||
|
property string userScriptUrl: ""
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
focus: true
|
focus: true
|
||||||
profile: HFWebEngineProfile;
|
|
||||||
|
|
||||||
property string userScriptUrl: ""
|
|
||||||
|
|
||||||
// Create a global EventBridge object for raiseAndLowerKeyboard.
|
|
||||||
WebEngineScript {
|
|
||||||
id: createGlobalEventBridge
|
|
||||||
sourceCode: eventBridgeJavaScriptToInject
|
|
||||||
injectionPoint: WebEngineScript.DocumentCreation
|
|
||||||
worldId: WebEngineScript.MainWorld
|
|
||||||
}
|
|
||||||
|
|
||||||
// Detect when may want to raise and lower keyboard.
|
|
||||||
WebEngineScript {
|
|
||||||
id: raiseAndLowerKeyboard
|
|
||||||
injectionPoint: WebEngineScript.Deferred
|
|
||||||
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
|
||||||
worldId: WebEngineScript.MainWorld
|
|
||||||
}
|
|
||||||
|
|
||||||
// User script.
|
|
||||||
WebEngineScript {
|
|
||||||
id: userScript
|
|
||||||
sourceUrl: webview.userScriptUrl
|
|
||||||
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
|
||||||
worldId: WebEngineScript.MainWorld
|
|
||||||
}
|
|
||||||
|
|
||||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
|
||||||
|
|
||||||
function onWebEventReceived(event) {
|
|
||||||
if (event.slice(0, 17) === "CLARA.IO DOWNLOAD") {
|
|
||||||
ApplicationInterface.addAssetToWorldFromURL(event.slice(18));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
webChannel.registerObject("eventBridge", eventBridge);
|
|
||||||
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
|
||||||
eventBridge.webEventReceived.connect(onWebEventReceived);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,11 @@
|
||||||
import QtQuick 2.5
|
import QtQuick 2.5
|
||||||
import QtWebChannel 1.0
|
|
||||||
import QtWebEngine 1.5
|
|
||||||
|
|
||||||
import "controls"
|
|
||||||
import controlsUit 1.0 as HifiControls
|
import controlsUit 1.0 as HifiControls
|
||||||
import "styles" as HifiStyles
|
|
||||||
import stylesUit 1.0
|
import stylesUit 1.0
|
||||||
import "windows"
|
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: root
|
id: root
|
||||||
HifiConstants { id: hifi }
|
HifiConstants { id: hifi }
|
||||||
HifiStyles.HifiConstants { id: hifistyles }
|
|
||||||
|
|
||||||
height: 600
|
height: 600
|
||||||
property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
|
property variant permissionsBar: {'securityOrigin':'none','feature':'none'}
|
||||||
|
@ -30,96 +24,9 @@ Item {
|
||||||
webview.profile = profile;
|
webview.profile = profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
WebEngineView {
|
HifiControls.ProxyWebView {
|
||||||
id: webview
|
id: webview
|
||||||
objectName: "webEngineView"
|
|
||||||
x: 0
|
|
||||||
y: 0
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: keyboardEnabled && keyboardRaised ? parent.height - keyboard.height : parent.height
|
height: parent.height
|
||||||
|
|
||||||
profile: HFWebEngineProfile;
|
|
||||||
|
|
||||||
property string userScriptUrl: ""
|
|
||||||
|
|
||||||
// creates a global EventBridge object.
|
|
||||||
WebEngineScript {
|
|
||||||
id: createGlobalEventBridge
|
|
||||||
sourceCode: eventBridgeJavaScriptToInject
|
|
||||||
injectionPoint: WebEngineScript.DocumentCreation
|
|
||||||
worldId: WebEngineScript.MainWorld
|
|
||||||
}
|
|
||||||
|
|
||||||
// detects when to raise and lower virtual keyboard
|
|
||||||
WebEngineScript {
|
|
||||||
id: raiseAndLowerKeyboard
|
|
||||||
injectionPoint: WebEngineScript.Deferred
|
|
||||||
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
|
||||||
worldId: WebEngineScript.MainWorld
|
|
||||||
}
|
|
||||||
|
|
||||||
// User script.
|
|
||||||
WebEngineScript {
|
|
||||||
id: userScript
|
|
||||||
sourceUrl: webview.userScriptUrl
|
|
||||||
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
|
||||||
worldId: WebEngineScript.MainWorld
|
|
||||||
}
|
|
||||||
|
|
||||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
|
||||||
|
|
||||||
property string newUrl: ""
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
webChannel.registerObject("eventBridge", eventBridge);
|
|
||||||
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
|
||||||
|
|
||||||
// Ensure the JS from the web-engine makes it to our logging
|
|
||||||
webview.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
|
|
||||||
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
|
|
||||||
});
|
|
||||||
|
|
||||||
webview.profile.httpUserAgent = "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Mobile Safari/537.36";
|
|
||||||
web.address = url;
|
|
||||||
}
|
|
||||||
|
|
||||||
onFeaturePermissionRequested: {
|
|
||||||
grantFeaturePermission(securityOrigin, feature, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoadingChanged: {
|
|
||||||
keyboardRaised = false;
|
|
||||||
punctuationMode = false;
|
|
||||||
keyboard.resetShiftMode(false);
|
|
||||||
|
|
||||||
// Required to support clicking on "hifi://" links
|
|
||||||
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
|
|
||||||
urlAppend(loadRequest.url.toString())
|
|
||||||
var url = loadRequest.url.toString();
|
|
||||||
if (urlHandler.canHandleUrl(url)) {
|
|
||||||
if (urlHandler.handleUrl(url)) {
|
|
||||||
root.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
onNewViewRequested: {
|
|
||||||
request.openIn(webView);
|
|
||||||
}
|
|
||||||
|
|
||||||
HifiControls.WebSpinner { }
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: {
|
|
||||||
switch(event.key) {
|
|
||||||
case Qt.Key_L:
|
|
||||||
if (event.modifiers == Qt.ControlModifier) {
|
|
||||||
event.accepted = true
|
|
||||||
addressBar.selectAll()
|
|
||||||
addressBar.forceActiveFocus()
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,189 @@
|
||||||
|
import QtQuick 2.7
|
||||||
|
import QtWebEngine 1.5
|
||||||
|
import QtWebChannel 1.0
|
||||||
|
|
||||||
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
|
import stylesUit 1.0 as StylesUIt
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: flick
|
||||||
|
|
||||||
|
property alias url: webViewCore.url
|
||||||
|
property alias canGoBack: webViewCore.canGoBack
|
||||||
|
property alias webViewCore: webViewCore
|
||||||
|
property alias webViewCoreProfile: webViewCore.profile
|
||||||
|
property string webViewCoreUserAgent
|
||||||
|
|
||||||
|
property string userScriptUrl: ""
|
||||||
|
property string urlTag: "noDownload=false";
|
||||||
|
|
||||||
|
signal newViewRequestedCallback(var request)
|
||||||
|
signal loadingChangedCallback(var loadRequest)
|
||||||
|
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
|
||||||
|
property bool interactive: false
|
||||||
|
|
||||||
|
property bool blurOnCtrlShift: true
|
||||||
|
|
||||||
|
StylesUIt.HifiConstants {
|
||||||
|
id: hifi
|
||||||
|
}
|
||||||
|
|
||||||
|
function stop() {
|
||||||
|
webViewCore.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
Timer {
|
||||||
|
id: delayedUnfocuser
|
||||||
|
repeat: false
|
||||||
|
interval: 200
|
||||||
|
onTriggered: {
|
||||||
|
|
||||||
|
// The idea behind this is to delay unfocusing, so that fast lower/raise will not result actual unfocusing.
|
||||||
|
// Fast lower/raise happens every time keyboard is being re-raised (see the code below in OffscreenQmlSurface::setKeyboardRaised)
|
||||||
|
//
|
||||||
|
// if (raised) {
|
||||||
|
// item->setProperty("keyboardRaised", QVariant(!raised));
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// item->setProperty("keyboardRaised", QVariant(raised));
|
||||||
|
//
|
||||||
|
|
||||||
|
webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) {
|
||||||
|
console.log('unfocus completed: ', result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function unfocus() {
|
||||||
|
delayedUnfocuser.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopUnfocus() {
|
||||||
|
delayedUnfocuser.stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
function onLoadingChanged(loadRequest) {
|
||||||
|
if (WebEngineView.LoadStartedStatus === loadRequest.status) {
|
||||||
|
|
||||||
|
// Required to support clicking on "hifi://" links
|
||||||
|
var url = loadRequest.url.toString();
|
||||||
|
url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag;
|
||||||
|
if (urlHandler.canHandleUrl(url)) {
|
||||||
|
if (urlHandler.handleUrl(url)) {
|
||||||
|
webViewCore.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WebEngineView.LoadFailedStatus === loadRequest.status) {
|
||||||
|
console.log("Tablet WebEngineView failed to load url: " + loadRequest.url.toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (WebEngineView.LoadSucceededStatus === loadRequest.status) {
|
||||||
|
//disable Chromium's scroll bars
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebEngineView {
|
||||||
|
id: webViewCore
|
||||||
|
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
|
||||||
|
profile: HFWebEngineProfile;
|
||||||
|
settings.pluginsEnabled: true
|
||||||
|
settings.touchIconsEnabled: true
|
||||||
|
settings.allowRunningInsecureContent: true
|
||||||
|
|
||||||
|
// creates a global EventBridge object.
|
||||||
|
WebEngineScript {
|
||||||
|
id: createGlobalEventBridge
|
||||||
|
sourceCode: eventBridgeJavaScriptToInject
|
||||||
|
injectionPoint: WebEngineScript.DocumentCreation
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
// detects when to raise and lower virtual keyboard
|
||||||
|
WebEngineScript {
|
||||||
|
id: raiseAndLowerKeyboard
|
||||||
|
injectionPoint: WebEngineScript.Deferred
|
||||||
|
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
// User script.
|
||||||
|
WebEngineScript {
|
||||||
|
id: userScript
|
||||||
|
sourceUrl: flick.userScriptUrl
|
||||||
|
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
||||||
|
worldId: WebEngineScript.MainWorld
|
||||||
|
}
|
||||||
|
|
||||||
|
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
webChannel.registerObject("eventBridge", eventBridge);
|
||||||
|
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
||||||
|
|
||||||
|
if (webViewCoreUserAgent !== undefined) {
|
||||||
|
webViewCore.profile.httpUserAgent = webViewCoreUserAgent
|
||||||
|
} else {
|
||||||
|
webViewCore.profile.httpUserAgent += " (HighFidelityInterface)";
|
||||||
|
}
|
||||||
|
// Ensure the JS from the web-engine makes it to our logging
|
||||||
|
webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
|
||||||
|
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onFeaturePermissionRequested: {
|
||||||
|
grantFeaturePermission(securityOrigin, feature, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
//disable popup
|
||||||
|
onContextMenuRequested: {
|
||||||
|
request.accepted = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
onNewViewRequested: {
|
||||||
|
newViewRequestedCallback(request)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prior to 5.10, the WebEngineView loading property is true during initial page loading and then stays false
|
||||||
|
// as in-page javascript adds more html content. However, in 5.10 there is a bug such that adding html turns
|
||||||
|
// loading true, and never turns it false again. safeLoading provides a workaround, but it should be removed
|
||||||
|
// when QT fixes this.
|
||||||
|
property bool safeLoading: false
|
||||||
|
property bool loadingLatched: false
|
||||||
|
property var loadingRequest: null
|
||||||
|
onLoadingChanged: {
|
||||||
|
webViewCore.loadingRequest = loadRequest;
|
||||||
|
webViewCore.safeLoading = webViewCore.loading && !loadingLatched;
|
||||||
|
webViewCore.loadingLatched |= webViewCore.loading;
|
||||||
|
}
|
||||||
|
onSafeLoadingChanged: {
|
||||||
|
flick.onLoadingChanged(webViewCore.loadingRequest)
|
||||||
|
loadingChangedCallback(webViewCore.loadingRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedImage {
|
||||||
|
//anchoring doesnt works here when changing content size
|
||||||
|
x: flick.width/2 - width/2
|
||||||
|
y: flick.height/2 - height/2
|
||||||
|
source: "../../icons/loader-snake-64-w.gif"
|
||||||
|
visible: webViewCore.safeLoading && /^(http.*|)$/i.test(webViewCore.url.toString())
|
||||||
|
playing: visible
|
||||||
|
z: 10000
|
||||||
|
}
|
||||||
|
|
||||||
|
Keys.onPressed: {
|
||||||
|
if (blurOnCtrlShift && (event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) {
|
||||||
|
webViewCore.focus = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,9 @@
|
||||||
import QtQuick 2.7
|
import QtQuick 2.7
|
||||||
import QtWebEngine 1.5
|
|
||||||
import QtWebChannel 1.0
|
|
||||||
|
|
||||||
import QtQuick.Controls 2.2
|
import QtQuick.Controls 2.2
|
||||||
|
|
||||||
import stylesUit 1.0 as StylesUIt
|
import stylesUit 1.0 as StylesUIt
|
||||||
|
import controlsUit 1.0 as ControlsUit
|
||||||
|
|
||||||
Item {
|
Item {
|
||||||
id: flick
|
id: flick
|
||||||
|
@ -33,157 +32,21 @@ Item {
|
||||||
}
|
}
|
||||||
|
|
||||||
function stop() {
|
function stop() {
|
||||||
webViewCore.stop();
|
|
||||||
}
|
|
||||||
|
|
||||||
Timer {
|
|
||||||
id: delayedUnfocuser
|
|
||||||
repeat: false
|
|
||||||
interval: 200
|
|
||||||
onTriggered: {
|
|
||||||
|
|
||||||
// The idea behind this is to delay unfocusing, so that fast lower/raise will not result actual unfocusing.
|
|
||||||
// Fast lower/raise happens every time keyboard is being re-raised (see the code below in OffscreenQmlSurface::setKeyboardRaised)
|
|
||||||
//
|
|
||||||
// if (raised) {
|
|
||||||
// item->setProperty("keyboardRaised", QVariant(!raised));
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// item->setProperty("keyboardRaised", QVariant(raised));
|
|
||||||
//
|
|
||||||
|
|
||||||
webViewCore.runJavaScript("if (document.activeElement) document.activeElement.blur();", function(result) {
|
|
||||||
console.log('unfocus completed: ', result);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function unfocus() {
|
function unfocus() {
|
||||||
delayedUnfocuser.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function stopUnfocus() {
|
function stopUnfocus() {
|
||||||
delayedUnfocuser.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function onLoadingChanged(loadRequest) {
|
function onLoadingChanged(loadRequest) {
|
||||||
if (WebEngineView.LoadStartedStatus === loadRequest.status) {
|
|
||||||
|
|
||||||
// Required to support clicking on "hifi://" links
|
|
||||||
var url = loadRequest.url.toString();
|
|
||||||
url = (url.indexOf("?") >= 0) ? url + urlTag : url + "?" + urlTag;
|
|
||||||
if (urlHandler.canHandleUrl(url)) {
|
|
||||||
if (urlHandler.handleUrl(url)) {
|
|
||||||
webViewCore.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WebEngineView.LoadFailedStatus === loadRequest.status) {
|
|
||||||
console.log("Tablet WebEngineView failed to load url: " + loadRequest.url.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (WebEngineView.LoadSucceededStatus === loadRequest.status) {
|
|
||||||
//disable Chromium's scroll bars
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WebEngineView {
|
ControlsUit.ProxyWebView {
|
||||||
id: webViewCore
|
id: webViewCore
|
||||||
|
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: parent.height
|
height: parent.height
|
||||||
|
|
||||||
profile: HFWebEngineProfile;
|
|
||||||
settings.pluginsEnabled: true
|
|
||||||
settings.touchIconsEnabled: true
|
|
||||||
settings.allowRunningInsecureContent: true
|
|
||||||
|
|
||||||
// creates a global EventBridge object.
|
|
||||||
WebEngineScript {
|
|
||||||
id: createGlobalEventBridge
|
|
||||||
sourceCode: eventBridgeJavaScriptToInject
|
|
||||||
injectionPoint: WebEngineScript.DocumentCreation
|
|
||||||
worldId: WebEngineScript.MainWorld
|
|
||||||
}
|
|
||||||
|
|
||||||
// detects when to raise and lower virtual keyboard
|
|
||||||
WebEngineScript {
|
|
||||||
id: raiseAndLowerKeyboard
|
|
||||||
injectionPoint: WebEngineScript.Deferred
|
|
||||||
sourceUrl: resourceDirectoryUrl + "/html/raiseAndLowerKeyboard.js"
|
|
||||||
worldId: WebEngineScript.MainWorld
|
|
||||||
}
|
|
||||||
|
|
||||||
// User script.
|
|
||||||
WebEngineScript {
|
|
||||||
id: userScript
|
|
||||||
sourceUrl: flick.userScriptUrl
|
|
||||||
injectionPoint: WebEngineScript.DocumentReady // DOM ready but page load may not be finished.
|
|
||||||
worldId: WebEngineScript.MainWorld
|
|
||||||
}
|
|
||||||
|
|
||||||
userScripts: [ createGlobalEventBridge, raiseAndLowerKeyboard, userScript ]
|
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
webChannel.registerObject("eventBridge", eventBridge);
|
|
||||||
webChannel.registerObject("eventBridgeWrapper", eventBridgeWrapper);
|
|
||||||
|
|
||||||
if (webViewCoreUserAgent !== undefined) {
|
|
||||||
webViewCore.profile.httpUserAgent = webViewCoreUserAgent
|
|
||||||
} else {
|
|
||||||
webViewCore.profile.httpUserAgent += " (HighFidelityInterface)";
|
|
||||||
}
|
|
||||||
// Ensure the JS from the web-engine makes it to our logging
|
|
||||||
webViewCore.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
|
|
||||||
console.log("Web Entity JS message: " + sourceID + " " + lineNumber + " " + message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onFeaturePermissionRequested: {
|
|
||||||
grantFeaturePermission(securityOrigin, feature, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
//disable popup
|
|
||||||
onContextMenuRequested: {
|
|
||||||
request.accepted = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
onNewViewRequested: {
|
|
||||||
newViewRequestedCallback(request)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prior to 5.10, the WebEngineView loading property is true during initial page loading and then stays false
|
|
||||||
// as in-page javascript adds more html content. However, in 5.10 there is a bug such that adding html turns
|
|
||||||
// loading true, and never turns it false again. safeLoading provides a workaround, but it should be removed
|
|
||||||
// when QT fixes this.
|
|
||||||
property bool safeLoading: false
|
|
||||||
property bool loadingLatched: false
|
|
||||||
property var loadingRequest: null
|
|
||||||
onLoadingChanged: {
|
|
||||||
webViewCore.loadingRequest = loadRequest;
|
|
||||||
webViewCore.safeLoading = webViewCore.loading && !loadingLatched;
|
|
||||||
webViewCore.loadingLatched |= webViewCore.loading;
|
|
||||||
}
|
|
||||||
onSafeLoadingChanged: {
|
|
||||||
flick.onLoadingChanged(webViewCore.loadingRequest)
|
|
||||||
loadingChangedCallback(webViewCore.loadingRequest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedImage {
|
|
||||||
//anchoring doesnt works here when changing content size
|
|
||||||
x: flick.width/2 - width/2
|
|
||||||
y: flick.height/2 - height/2
|
|
||||||
source: "../../icons/loader-snake-64-w.gif"
|
|
||||||
visible: webViewCore.safeLoading && /^(http.*|)$/i.test(webViewCore.url.toString())
|
|
||||||
playing: visible
|
|
||||||
z: 10000
|
|
||||||
}
|
|
||||||
|
|
||||||
Keys.onPressed: {
|
|
||||||
if (blurOnCtrlShift && (event.modifiers & Qt.ShiftModifier) && (event.modifiers & Qt.ControlModifier)) {
|
|
||||||
webViewCore.focus = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import QtQuick 2.7
|
import QtQuick 2.7
|
||||||
import QtWebEngine 1.5
|
|
||||||
import controlsUit 1.0 as HiFiControls
|
import controlsUit 1.0 as HiFiControls
|
||||||
import "../styles" as HifiStyles
|
import "../styles" as HifiStyles
|
||||||
import stylesUit 1.0
|
import stylesUit 1.0
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
//
|
||||||
|
// WebView.qml
|
||||||
|
//
|
||||||
|
// Created by Bradley Austin Davis on 12 Jan 2016
|
||||||
|
// Copyright 2016 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.7
|
||||||
|
import QtWebEngine 1.5
|
||||||
|
|
||||||
|
WebEngineView {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
console.log("Connecting JS messaging to Hifi Logging")
|
||||||
|
// Ensure the JS from the web-engine makes it to our logging
|
||||||
|
root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
|
||||||
|
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onLoadingChanged: {
|
||||||
|
// Required to support clicking on "hifi://" links
|
||||||
|
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
|
||||||
|
var url = loadRequest.url.toString();
|
||||||
|
if (urlHandler.canHandleUrl(url)) {
|
||||||
|
if (urlHandler.handleUrl(url)) {
|
||||||
|
root.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
WebSpinner { }
|
||||||
|
}
|
|
@ -0,0 +1,24 @@
|
||||||
|
//
|
||||||
|
// WebSpinner.qml
|
||||||
|
//
|
||||||
|
// Created by David Rowe on 23 May 2017
|
||||||
|
// Copyright 2017 High Fidelity, Inc.
|
||||||
|
//
|
||||||
|
// Distributed under the Apache License, Version 2.0.
|
||||||
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
|
//
|
||||||
|
|
||||||
|
import QtQuick 2.5
|
||||||
|
import QtWebEngine 1.5
|
||||||
|
|
||||||
|
AnimatedImage {
|
||||||
|
property WebEngineView webview: parent
|
||||||
|
source: "qrc:////icons//loader-snake-64-w.gif"
|
||||||
|
visible: webview.loading && /^(http.*|)$/i.test(webview.url.toString())
|
||||||
|
playing: visible
|
||||||
|
z: 10000
|
||||||
|
anchors {
|
||||||
|
horizontalCenter: parent.horizontalCenter
|
||||||
|
verticalCenter: parent.verticalCenter
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,31 +8,8 @@
|
||||||
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||||
//
|
//
|
||||||
|
|
||||||
import QtQuick 2.7
|
import "."
|
||||||
import QtWebEngine 1.5
|
|
||||||
|
|
||||||
WebEngineView {
|
ProxyWebView {
|
||||||
id: root
|
id: root
|
||||||
|
|
||||||
Component.onCompleted: {
|
|
||||||
console.log("Connecting JS messaging to Hifi Logging")
|
|
||||||
// Ensure the JS from the web-engine makes it to our logging
|
|
||||||
root.javaScriptConsoleMessage.connect(function(level, message, lineNumber, sourceID) {
|
|
||||||
console.log("Web Window JS message: " + sourceID + " " + lineNumber + " " + message);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onLoadingChanged: {
|
|
||||||
// Required to support clicking on "hifi://" links
|
|
||||||
if (WebEngineView.LoadStartedStatus == loadRequest.status) {
|
|
||||||
var url = loadRequest.url.toString();
|
|
||||||
if (urlHandler.canHandleUrl(url)) {
|
|
||||||
if (urlHandler.handleUrl(url)) {
|
|
||||||
root.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
WebSpinner { }
|
|
||||||
}
|
}
|
||||||
|
|
30
interface/resources/qml/controlsUit/ProxyWebView.qml
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
import QtQuick 2.7
|
||||||
|
import stylesUit 1.0
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
HifiConstants {
|
||||||
|
id: hifi
|
||||||
|
}
|
||||||
|
|
||||||
|
color: hifi.colors.darkGray
|
||||||
|
|
||||||
|
signal onNewViewRequested();
|
||||||
|
|
||||||
|
property string url: "";
|
||||||
|
property bool canGoBack: false
|
||||||
|
property bool canGoForward: false
|
||||||
|
property string icon: ""
|
||||||
|
property var profile: {}
|
||||||
|
|
||||||
|
property bool safeLoading: false
|
||||||
|
property bool loadingLatched: false
|
||||||
|
property var loadingRequest: null
|
||||||
|
|
||||||
|
|
||||||
|
Text {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: "This feature is not supported"
|
||||||
|
font.pixelSize: 32
|
||||||
|
color: hifi.colors.white
|
||||||
|
}
|
||||||
|
}
|
|
@ -9,11 +9,15 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import QtQuick 2.5
|
import QtQuick 2.5
|
||||||
import QtWebEngine 1.5
|
|
||||||
|
|
||||||
AnimatedImage {
|
Image {
|
||||||
property WebEngineView webview: parent
|
Item {
|
||||||
source: "../../icons/loader-snake-64-w.gif"
|
id: webView
|
||||||
|
property bool loading: false
|
||||||
|
property string url: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
source: "qrc:////images//unsupportedImage.png"
|
||||||
visible: webview.loading && /^(http.*|)$/i.test(webview.url.toString())
|
visible: webview.loading && /^(http.*|)$/i.test(webview.url.toString())
|
||||||
playing: visible
|
playing: visible
|
||||||
z: 10000
|
z: 10000
|
||||||
|
|
|
@ -29,6 +29,7 @@ TextEdit 1.0 TextEdit.qml
|
||||||
TextField 1.0 TextField.qml
|
TextField 1.0 TextField.qml
|
||||||
ToolTip 1.0 ToolTip.qml
|
ToolTip 1.0 ToolTip.qml
|
||||||
Tree 1.0 Tree.qml
|
Tree 1.0 Tree.qml
|
||||||
|
ProxyWebView 1.0 ProxyWebView.qml
|
||||||
VerticalSpacer 1.0 VerticalSpacer.qml
|
VerticalSpacer 1.0 VerticalSpacer.qml
|
||||||
WebGlyphButton 1.0 WebGlyphButton.qml
|
WebGlyphButton 1.0 WebGlyphButton.qml
|
||||||
WebSpinner 1.0 WebSpinner.qml
|
WebSpinner 1.0 WebSpinner.qml
|
||||||
|
|