Merged with master
20
.gitignore
vendored
|
@ -18,9 +18,12 @@ local.properties
|
|||
android/gradle*
|
||||
android/.gradle
|
||||
android/app/src/main/jniLibs
|
||||
android/app/libs
|
||||
android/app/src/main/res/values/libs.xml
|
||||
android/app/src/main/assets/bundled
|
||||
|
||||
# VSCode
|
||||
# List taken from Github Global Ignores master@435c4d92
|
||||
# List taken from Github Global Ignores master@435c4d92
|
||||
# https://github.com/github/gitignore/commits/master/Global/VisualStudioCode.gitignore
|
||||
.vscode/*
|
||||
!.vscode/settings.json
|
||||
|
@ -66,7 +69,7 @@ gvr-interface/libs/*
|
|||
# ignore files for various dev environments
|
||||
TAGS
|
||||
*.sw[po]
|
||||
*.qmlc
|
||||
*.jsc
|
||||
|
||||
# ignore QML compilation output
|
||||
*.qmlc
|
||||
|
@ -75,5 +78,14 @@ TAGS
|
|||
node_modules
|
||||
npm-debug.log
|
||||
|
||||
# ignore qmlc files generated from qml as cache
|
||||
*.qmlc
|
||||
# Android studio files
|
||||
*___jb_old___
|
||||
|
||||
# Generated assets for Android
|
||||
android/app/src/main/assets
|
||||
|
||||
# Resource binary file
|
||||
interface/compiledResources
|
||||
|
||||
# GPUCache
|
||||
interface/resources/GPUCache/*
|
||||
|
|
|
@ -2,9 +2,9 @@ Please read the [general build guide](BUILD.md) for information on building othe
|
|||
|
||||
# Dependencies
|
||||
|
||||
*Currently Android building is only supported on 64 bit Linux host environments*
|
||||
Building is currently supported on OSX, Windows and Linux platforms, but developers intending to do work on the library dependencies are strongly urged to use 64 bit Linux as a build platform
|
||||
|
||||
You will need the following tools to build our Android targets.
|
||||
You will need the following tools to build Android targets.
|
||||
|
||||
* [Gradle](https://gradle.org/install/)
|
||||
* [Android Studio](https://developer.android.com/studio/index.html)
|
||||
|
|
36
BUILD_LINUX_CHEATSHEET.md
Normal file
|
@ -0,0 +1,36 @@
|
|||
## This guide is specific to Ubuntu 16.04.
|
||||
Deb packages of High Fidelity domain server and assignment client are stored on debian.highfidelity.com
|
||||
|
||||
```
|
||||
sudo su -
|
||||
apt-get -y update
|
||||
apt-get install -y software-properties-common
|
||||
apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 15FF1AAE
|
||||
add-apt-repository "deb http://debian.highfidelity.com stable main"
|
||||
apt-get -y update
|
||||
apt-get install -y hifi-domain-server
|
||||
apt-get install -y hifi-assignment-client
|
||||
```
|
||||
|
||||
When installing master/dev builds, the packages are slightly different and you just need to change the last 2 steps to:
|
||||
```
|
||||
apt-get install -y hifi-dev-domain-server
|
||||
apt-get install -y hifi-dev-assignment-client
|
||||
```
|
||||
|
||||
Domain server and assignment clients should already be running. The processes are controlled via:
|
||||
```
|
||||
systemctl start hifi-domain-server
|
||||
systemctl stop hifi-domain-server
|
||||
```
|
||||
|
||||
Once the machine is setup and processes are running, you should ensure that your firewall exposes port 40100 on TCP and all UDP ports. This will get your domain up and running and you could connect to it (for now) by using High Fidelity Interface and typing in the IP for the place name. (Further customizations can be done via http://IPAddress:40100).
|
||||
|
||||
The server always depends on both hifi-domain-server and hifi-assignment-client running at the same time.
|
||||
As an additional step, you should ensure that your packages are automatically updated when a new version goes out. You could, for example, set the automatic update checks to happen every hour (though this could potentially result in the domain being unreachable for a whole hour by new clients when they are released - adjust the update checks accordingly).
|
||||
To do this you can modify /etc/crontab by adding the following lines
|
||||
```
|
||||
0 */1 * * * root apt-get update
|
||||
1 */1 * * * root apt-get install --only-upgrade -y hifi-domain-server
|
||||
2 */1 * * * root apt-get install --only-upgrade -y hifi-assignment-client
|
||||
```
|
|
@ -13,6 +13,11 @@ include("cmake/init.cmake")
|
|||
|
||||
include("cmake/compiler.cmake")
|
||||
|
||||
if (BUILD_SCRIBE_ONLY)
|
||||
add_subdirectory(tools/scribe)
|
||||
return()
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED SERVER_ONLY)
|
||||
set(SERVER_ONLY 0)
|
||||
endif()
|
||||
|
@ -26,9 +31,11 @@ endif()
|
|||
if (ANDROID OR UWP)
|
||||
option(BUILD_SERVER "Build server components" OFF)
|
||||
option(BUILD_TOOLS "Build tools" OFF)
|
||||
option(BUILD_INSTALLER "Build installer" OFF)
|
||||
else()
|
||||
option(BUILD_SERVER "Build server components" ON)
|
||||
option(BUILD_TOOLS "Build tools" ON)
|
||||
option(BUILD_INSTALLER "Build installer" ON)
|
||||
endif()
|
||||
|
||||
if (SERVER_ONLY)
|
||||
|
@ -39,13 +46,54 @@ else()
|
|||
option(BUILD_TESTS "Build tests" ON)
|
||||
endif()
|
||||
|
||||
option(BUILD_INSTALLER "Build installer" ON)
|
||||
if (ANDROID)
|
||||
option(USE_GLES "Use OpenGL ES" ON)
|
||||
set(PLATFORM_QT_COMPONENTS AndroidExtras WebView)
|
||||
else ()
|
||||
option(USE_GLES "Use OpenGL ES" OFF)
|
||||
set(PLATFORM_QT_COMPONENTS WebEngine WebEngineWidgets)
|
||||
endif ()
|
||||
|
||||
if (USE_GLES AND (NOT ANDROID))
|
||||
option(DISABLE_QML "Disable QML" ON)
|
||||
else()
|
||||
option(DISABLE_QML "Disable QML" OFF)
|
||||
endif()
|
||||
option(DISABLE_KTX_CACHE "Disable KTX Cache" OFF)
|
||||
|
||||
|
||||
|
||||
set(PLATFORM_QT_GL OpenGL)
|
||||
|
||||
if (USE_GLES)
|
||||
add_definitions(-DUSE_GLES)
|
||||
set(PLATFORM_GL_BACKEND gpu-gles)
|
||||
else()
|
||||
set(PLATFORM_GL_BACKEND gpu-gl)
|
||||
endif()
|
||||
|
||||
|
||||
foreach(PLATFORM_QT_COMPONENT ${PLATFORM_QT_COMPONENTS})
|
||||
list(APPEND PLATFORM_QT_LIBRARIES "Qt5::${PLATFORM_QT_COMPONENT}")
|
||||
endforeach()
|
||||
|
||||
|
||||
MESSAGE(STATUS "Build server: " ${BUILD_SERVER})
|
||||
MESSAGE(STATUS "Build client: " ${BUILD_CLIENT})
|
||||
MESSAGE(STATUS "Build tests: " ${BUILD_TESTS})
|
||||
MESSAGE(STATUS "Build tools: " ${BUILD_TOOLS})
|
||||
MESSAGE(STATUS "Build installer: " ${BUILD_INSTALLER})
|
||||
MESSAGE(STATUS "GL ES: " ${USE_GLES})
|
||||
|
||||
if (DISABLE_QML)
|
||||
MESSAGE(STATUS "QML disabled!")
|
||||
add_definitions(-DDISABLE_QML)
|
||||
endif()
|
||||
|
||||
if (DISABLE_KTX_CACHE)
|
||||
MESSAGE(STATUS "KTX cache disabled!")
|
||||
add_definitions(-DDISABLE_KTX_CACHE)
|
||||
endif()
|
||||
|
||||
if (UNIX AND DEFINED ENV{HIFI_MEMORY_DEBUGGING})
|
||||
MESSAGE(STATUS "Memory debugging is enabled")
|
||||
|
|
|
@ -1,10 +1,28 @@
|
|||
set(TARGET_NAME native-lib)
|
||||
setup_hifi_library()
|
||||
link_hifi_libraries(shared networking gl gpu gpu-gles image fbx render-utils physics)
|
||||
|
||||
target_link_libraries(native-lib android log m)
|
||||
|
||||
link_hifi_libraries(shared task networking gl gpu qml image fbx render-utils physics entities octree ${PLATFORM_GL_BACKEND})
|
||||
target_opengl()
|
||||
target_googlevr()
|
||||
target_bullet()
|
||||
|
||||
set(INTERFACE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../interface")
|
||||
add_subdirectory("${INTERFACE_DIR}" "libraries/interface")
|
||||
include_directories("${INTERFACE_DIR}/src")
|
||||
|
||||
target_link_libraries(native-lib android log m interface)
|
||||
|
||||
set(GVR_ROOT "${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/")
|
||||
target_include_directories(native-lib PRIVATE "${GVR_ROOT}/libraries/headers" "libraries/ui/src")
|
||||
target_link_libraries(native-lib "${GVR_ROOT}/libraries/libgvr.so" ui)
|
||||
|
||||
# finished libraries
|
||||
# core -> qt
|
||||
# networking -> openssl, tbb
|
||||
# fbx -> draco
|
||||
# physics -> bullet
|
||||
# entities-renderer -> polyvox
|
||||
|
||||
# unfinished libraries
|
||||
# image -> nvtt (doesn't look good, but can be made optional)
|
||||
# script-engine -> quazip (probably not required for the android client)
|
||||
|
||||
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
ext.RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0'
|
||||
ext.RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
|
||||
ext.BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : ''
|
||||
|
||||
android {
|
||||
compileSdkVersion 26
|
||||
//buildToolsVersion '27.0.3'
|
||||
|
||||
defaultConfig {
|
||||
applicationId "org.saintandreas.testapp"
|
||||
applicationId "io.highfidelity.hifiinterface"
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 26
|
||||
versionCode 1
|
||||
|
@ -20,28 +18,23 @@ android {
|
|||
'-DANDROID_TOOLCHAIN=clang',
|
||||
'-DANDROID_STL=c++_shared',
|
||||
'-DQT_CMAKE_PREFIX_PATH=' + HIFI_ANDROID_PRECOMPILED + '/qt/lib/cmake',
|
||||
'-DNATIVE_SCRIBE=' + HIFI_ANDROID_PRECOMPILED + '/scribe',
|
||||
'-DNATIVE_SCRIBE=' + HIFI_ANDROID_PRECOMPILED + '/scribe' + EXEC_SUFFIX,
|
||||
'-DHIFI_ANDROID_PRECOMPILED=' + HIFI_ANDROID_PRECOMPILED,
|
||||
'-DRELEASE_NUMBER=' + RELEASE_NUMBER,
|
||||
'-DRELEASE_TYPE=' + RELEASE_TYPE,
|
||||
'-DBUILD_BRANCH=' + BUILD_BRANCH
|
||||
'-DBUILD_BRANCH=' + BUILD_BRANCH,
|
||||
'-DDISABLE_QML=ON',
|
||||
'-DDISABLE_KTX_CACHE=ON'
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.all {
|
||||
if (RELEASE_NUMBER != '0') {
|
||||
outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
|
@ -53,11 +46,58 @@ android {
|
|||
path '../../CMakeLists.txt'
|
||||
}
|
||||
}
|
||||
|
||||
applicationVariants.all { variant ->
|
||||
// Our asset contents depend on items produced in the CMake build
|
||||
// so our merge has to depend on the external native build
|
||||
variant.externalNativeBuildTasks.each { task ->
|
||||
variant.mergeResources.dependsOn(task)
|
||||
}
|
||||
|
||||
variant.mergeAssets.doLast {
|
||||
def assetList = new LinkedList<String>()
|
||||
def youngestLastModified = 0
|
||||
|
||||
// Copy the compiled resources generated by the external native build
|
||||
copy {
|
||||
from new File(projectDir, "../../interface/compiledResources")
|
||||
into outputDir
|
||||
duplicatesStrategy DuplicatesStrategy.INCLUDE
|
||||
eachFile { details ->
|
||||
youngestLastModified = Math.max(youngestLastModified, details.lastModified)
|
||||
assetList.add(details.path)
|
||||
}
|
||||
}
|
||||
|
||||
// Copy the scripts directory
|
||||
copy {
|
||||
from new File(projectDir, "../../scripts")
|
||||
into new File(outputDir, "scripts")
|
||||
duplicatesStrategy DuplicatesStrategy.INCLUDE
|
||||
eachFile { details->
|
||||
youngestLastModified = Math.max(youngestLastModified, details.lastModified)
|
||||
assetList.add("scripts/" + details.path)
|
||||
}
|
||||
}
|
||||
|
||||
// Write a list of files to be unpacked to the cache folder
|
||||
new File(outputDir, 'cache_assets.txt').withWriter { out ->
|
||||
out.println(Long.toString(youngestLastModified))
|
||||
assetList.each { file -> out.println(file) }
|
||||
}
|
||||
}
|
||||
|
||||
variant.outputs.all {
|
||||
if (RELEASE_NUMBER != '0') {
|
||||
outputFileName = "app_" + RELEASE_NUMBER + "_" + RELEASE_TYPE + ".apk"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile fileTree(dir: "${project.rootDir}/libraries/jar", include: 'QtAndroid-bundled.jar')
|
||||
compile fileTree(dir: 'libs', include: ['*.jar'])
|
||||
compile 'com.google.vr:sdk-audio:1.80.0'
|
||||
compile 'com.google.vr:sdk-base:1.80.0'
|
||||
implementation 'com.google.vr:sdk-audio:1.80.0'
|
||||
implementation 'com.google.vr:sdk-base:1.80.0'
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
}
|
||||
|
||||
|
|
|
@ -1,28 +1,69 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.saintandreas.testapp">
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="io.highfidelity.hifiinterface">
|
||||
|
||||
<uses-sdk android:minSdkVersion="24" android:targetSdkVersion="26" />
|
||||
<uses-feature android:glEsVersion="0x00030001" android:required="true" />
|
||||
<uses-feature android:glEsVersion="0x00030002" android:required="true" />
|
||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-feature android:name="android.hardware.sensor.accelerometer" android:required="true"/>
|
||||
<uses-feature android:name="android.hardware.sensor.gyroscope" android:required="true"/>
|
||||
|
||||
<application
|
||||
android:name="org.qtproject.qt5.android.bindings.QtApplication"
|
||||
android:label="@string/app_name"
|
||||
android:hardwareAccelerated="true"
|
||||
android:allowBackup="true"
|
||||
android:screenOrientation="unspecified"
|
||||
android:theme="@style/NoSystemUI"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:launchMode="singleTop"
|
||||
android:roundIcon="@mipmap/ic_launcher_round">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="landscape"
|
||||
android:configChanges="orientation|keyboardHidden|screenSize"
|
||||
android:resizeableActivity="false">
|
||||
<activity android:name="io.highfidelity.hifiinterface.PermissionChecker">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="io.highfidelity.hifiinterface.WebViewActivity"
|
||||
android:theme="@android:style/Theme.Material.Light.NoActionBar"/>
|
||||
<!-- We don't want to show this on Daydream yet (we need to fix the turn-around problem on this screen)
|
||||
<activity android:name="io.highfidelity.hifiinterface.GvrLoaderActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="com.google.intent.category.DAYDREAM"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
-->
|
||||
<activity
|
||||
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation"
|
||||
android:name=".InterfaceActivity"
|
||||
android:label="@string/app_name"
|
||||
android:screenOrientation="landscape"
|
||||
android:launchMode="singleTop"
|
||||
android:enableVrMode="com.google.vr.vrcore/com.google.vr.vrcore.common.VrCoreListenerService"
|
||||
>
|
||||
|
||||
<intent-filter>
|
||||
<category android:name="com.google.intent.category.DAYDREAM"/>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
</intent-filter>
|
||||
|
||||
<meta-data android:name="android.app.lib_name" android:value="native-lib"/>
|
||||
<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"/>
|
||||
<meta-data android:name="android.app.background_running" android:value="false"/>
|
||||
<meta-data android:name="android.app.auto_screen_scale_factor" android:value="false"/>
|
||||
<meta-data android:name="android.app.extract_android_style" android:value="full"/>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
||||
|
||||
<uses-feature android:name="android.software.vr.mode" android:required="true"/>
|
||||
<uses-feature android:name="android.hardware.vr.high_performance" android:required="true"/>
|
||||
|
||||
</manifest>
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <vr/gvr/capi/include/gvr.h>
|
||||
|
||||
namespace googlevr {
|
||||
|
||||
// Convert a GVR matrix to GLM matrix
|
||||
glm::mat4 toGlm(const gvr::Mat4f &matrix) {
|
||||
glm::mat4 result;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
for (int j = 0; j < 4; ++j) {
|
||||
result[j][i] = matrix.m[i][j];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// Given a field of view in degrees, compute the corresponding projection
|
||||
// matrix.
|
||||
glm::mat4 perspectiveMatrixFromView(const gvr::Rectf& fov, float z_near, float z_far) {
|
||||
const float x_left = -std::tan(fov.left * M_PI / 180.0f) * z_near;
|
||||
const float x_right = std::tan(fov.right * M_PI / 180.0f) * z_near;
|
||||
const float y_bottom = -std::tan(fov.bottom * M_PI / 180.0f) * z_near;
|
||||
const float y_top = std::tan(fov.top * M_PI / 180.0f) * z_near;
|
||||
const float Y = (2 * z_near) / (y_top - y_bottom);
|
||||
const float A = (x_right + x_left) / (x_right - x_left);
|
||||
const float B = (y_top + y_bottom) / (y_top - y_bottom);
|
||||
const float C = (z_near + z_far) / (z_near - z_far);
|
||||
const float D = (2 * z_near * z_far) / (z_near - z_far);
|
||||
|
||||
glm::mat4 result { 0 };
|
||||
result[2][0] = A;
|
||||
result[1][1] = Y;
|
||||
result[2][1] = B;
|
||||
result[2][2] = C;
|
||||
result[3][2] = D;
|
||||
result[2][3] = -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::quat toGlm(const gvr::ControllerQuat& q) {
|
||||
glm::quat result;
|
||||
result.w = q.qw;
|
||||
result.x = q.qx;
|
||||
result.y = q.qy;
|
||||
result.z = q.qz;
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,73 +0,0 @@
|
|||
#include <jni.h>
|
||||
|
||||
#include <android/log.h>
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include "renderer.h"
|
||||
|
||||
int QtMsgTypeToAndroidPriority(QtMsgType type) {
|
||||
int priority = ANDROID_LOG_UNKNOWN;
|
||||
switch (type) {
|
||||
case QtDebugMsg: priority = ANDROID_LOG_DEBUG; break;
|
||||
case QtWarningMsg: priority = ANDROID_LOG_WARN; break;
|
||||
case QtCriticalMsg: priority = ANDROID_LOG_ERROR; break;
|
||||
case QtFatalMsg: priority = ANDROID_LOG_FATAL; break;
|
||||
case QtInfoMsg: priority = ANDROID_LOG_INFO; break;
|
||||
default: break;
|
||||
}
|
||||
return priority;
|
||||
}
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||
__android_log_write(QtMsgTypeToAndroidPriority(type), "Interface", message.toStdString().c_str());
|
||||
}
|
||||
|
||||
static jlong toJni(NativeRenderer *renderer) {
|
||||
return reinterpret_cast<intptr_t>(renderer);
|
||||
}
|
||||
|
||||
static NativeRenderer *fromJni(jlong renderer) {
|
||||
return reinterpret_cast<NativeRenderer*>(renderer);
|
||||
}
|
||||
|
||||
#define JNI_METHOD(r, name) JNIEXPORT r JNICALL Java_org_saintandreas_testapp_MainActivity_##name
|
||||
|
||||
extern "C" {
|
||||
|
||||
JNI_METHOD(jlong, nativeCreateRenderer)
|
||||
(JNIEnv *env, jclass clazz, jobject class_loader, jobject android_context, jlong native_gvr_api) {
|
||||
qInstallMessageHandler(messageHandler);
|
||||
return toJni(new NativeRenderer());
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeDestroyRenderer)
|
||||
(JNIEnv *env, jclass clazz, jlong renderer) {
|
||||
delete fromJni(renderer);
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeInitializeGl)
|
||||
(JNIEnv *env, jobject obj, jlong renderer) {
|
||||
fromJni(renderer)->InitializeGl();
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeDrawFrame)
|
||||
(JNIEnv *env, jobject obj, jlong renderer) {
|
||||
fromJni(renderer)->DrawFrame();
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeOnTriggerEvent)
|
||||
(JNIEnv *env, jobject obj, jlong renderer) {
|
||||
fromJni(renderer)->OnTriggerEvent();
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeOnPause)
|
||||
(JNIEnv *env, jobject obj, jlong renderer) {
|
||||
fromJni(renderer)->OnPause();
|
||||
}
|
||||
|
||||
JNI_METHOD(void, nativeOnResume)
|
||||
(JNIEnv *env, jobject obj, jlong renderer) {
|
||||
fromJni(renderer)->OnResume();
|
||||
}
|
||||
|
||||
} // extern "C"
|
160
android/app/src/main/cpp/native.cpp
Normal file
|
@ -0,0 +1,160 @@
|
|||
#include <functional>
|
||||
|
||||
|
||||
#include <QtCore/QBuffer>
|
||||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QDir>
|
||||
#include <QtCore/QFile>
|
||||
#include <QtCore/QThread>
|
||||
#include <QtCore/QStringList>
|
||||
#include <QtCore/QStandardPaths>
|
||||
#include <QtCore/QTextStream>
|
||||
|
||||
#include <QtAndroidExtras/QAndroidJniObject>
|
||||
|
||||
#include <android/log.h>
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/asset_manager_jni.h>
|
||||
|
||||
#include <shared/Storage.h>
|
||||
|
||||
void tempMessageHandler(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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AAssetManager* g_assetManager = nullptr;
|
||||
|
||||
void withAssetData(const char* filename, const std::function<void(off64_t, const void*)>& callback) {
|
||||
auto asset = AAssetManager_open(g_assetManager, filename, AASSET_MODE_BUFFER);
|
||||
if (!asset) {
|
||||
throw std::runtime_error("Failed to open file");
|
||||
}
|
||||
auto buffer = AAsset_getBuffer(asset);
|
||||
off64_t size = AAsset_getLength64(asset);
|
||||
callback(size, buffer);
|
||||
AAsset_close(asset);
|
||||
}
|
||||
|
||||
QStringList readAssetLines(const char* filename) {
|
||||
QStringList result;
|
||||
withAssetData(filename, [&](off64_t size, const void* data){
|
||||
QByteArray buffer = QByteArray::fromRawData((const char*)data, size);
|
||||
{
|
||||
QTextStream in(&buffer);
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine();
|
||||
result << line;
|
||||
}
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
void copyAsset(const char* sourceAsset, const QString& destFilename) {
|
||||
withAssetData(sourceAsset, [&](off64_t size, const void* data){
|
||||
QFile file(destFilename);
|
||||
if (!file.open(QFile::ReadWrite | QIODevice::Truncate)) {
|
||||
throw std::runtime_error("Unable to open output file for writing");
|
||||
}
|
||||
if (!file.resize(size)) {
|
||||
throw std::runtime_error("Unable to resize output file");
|
||||
}
|
||||
if (size != 0) {
|
||||
auto mapped = file.map(0, size);
|
||||
if (!mapped) {
|
||||
throw std::runtime_error("Unable to map output file");
|
||||
}
|
||||
memcpy(mapped, data, size);
|
||||
}
|
||||
file.close();
|
||||
});
|
||||
}
|
||||
|
||||
void unpackAndroidAssets() {
|
||||
const QString DEST_PREFIX = QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/";
|
||||
|
||||
QStringList filesToCopy = readAssetLines("cache_assets.txt");
|
||||
QString dateStamp = filesToCopy.takeFirst();
|
||||
QString dateStampFilename = DEST_PREFIX + dateStamp;
|
||||
qDebug() << "Checking date stamp" << dateStamp;
|
||||
if (QFileInfo(dateStampFilename).exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto rootDir = QDir::root();
|
||||
for (const auto& fileToCopy : filesToCopy) {
|
||||
auto destFileName = DEST_PREFIX + fileToCopy;
|
||||
auto destFolder = QFileInfo(destFileName).absoluteDir();
|
||||
if (!destFolder.exists()) {
|
||||
qDebug() << "Creating folder" << destFolder.absolutePath();
|
||||
if (!rootDir.mkpath(destFolder.absolutePath())) {
|
||||
throw std::runtime_error("Error creating output path");
|
||||
}
|
||||
}
|
||||
if (QFile::exists(destFileName)) {
|
||||
qDebug() << "Removing old file" << destFileName;
|
||||
if (!QFile::remove(destFileName)) {
|
||||
throw std::runtime_error("Failed to remove existing file");
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Copying asset " << fileToCopy << "to" << destFileName;
|
||||
copyAsset(fileToCopy.toStdString().c_str(), destFileName);
|
||||
}
|
||||
|
||||
{
|
||||
qDebug() << "Writing date stamp" << dateStamp;
|
||||
QFile file(dateStampFilename);
|
||||
if (!file.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
|
||||
throw std::runtime_error("Can't write date stamp");
|
||||
}
|
||||
QTextStream(&file) << "touch" << endl;
|
||||
file.close();
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" {
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) {
|
||||
qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
|
||||
g_assetManager = AAssetManager_fromJava(env, asset_mgr);
|
||||
auto oldMessageHandler = qInstallMessageHandler(tempMessageHandler);
|
||||
unpackAndroidAssets();
|
||||
qInstallMessageHandler(oldMessageHandler);
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) {
|
||||
qDebug() << "nativeOnPause";
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnResume(JNIEnv* env, jobject obj) {
|
||||
qDebug() << "nativeOnResume";
|
||||
}
|
||||
|
||||
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnExitVr(JNIEnv* env, jobject obj) {
|
||||
qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,120 +0,0 @@
|
|||
#include "renderer.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
|
||||
#include <QtCore/QDebug>
|
||||
|
||||
#include <gl/Config.h>
|
||||
#include <gl/GLShaders.h>
|
||||
|
||||
static const char *kSimepleVertexShader = R"glsl(#version 300 es
|
||||
#extension GL_OVR_multiview2 : enable
|
||||
|
||||
layout(num_views=2) in;
|
||||
|
||||
layout(location = 0) in vec4 a_Position;
|
||||
|
||||
out vec4 v_Color;
|
||||
|
||||
void main() {
|
||||
v_Color = vec4(a_Position.xyz, 1.0);
|
||||
gl_Position = vec4(a_Position.xyz, 1.0);
|
||||
}
|
||||
)glsl";
|
||||
|
||||
static const char *kPassthroughFragmentShader = R"glsl(#version 300 es
|
||||
precision mediump float;
|
||||
in vec4 v_Color;
|
||||
out vec4 FragColor;
|
||||
|
||||
void main() { FragColor = v_Color; }
|
||||
)glsl";
|
||||
|
||||
|
||||
int LoadGLShader(int type, const char *shadercode) {
|
||||
GLuint result = 0;
|
||||
std::string shaderError;
|
||||
static const std::string SHADER_DEFINES;
|
||||
if (!gl::compileShader(type, shadercode, SHADER_DEFINES, result, shaderError)) {
|
||||
qWarning() << "QQQ" << __FUNCTION__ << "Shader compile failure" << shaderError.c_str();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
static void CheckGLError(const char* label) {
|
||||
int gl_error = glGetError();
|
||||
if (gl_error != GL_NO_ERROR) {
|
||||
qWarning("GL error @ %s: %d", label, gl_error);
|
||||
// Crash immediately to make OpenGL errors obvious.
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
// Contains vertex, normal and other data.
|
||||
namespace triangle {
|
||||
static std::array<float, 9> TRIANGLE_VERTS {{
|
||||
-0.5f, -0.5f, 0.0f,
|
||||
0.5f, -0.5f, 0.0f,
|
||||
0.0f, 0.5f, 0.0f
|
||||
}};
|
||||
}
|
||||
|
||||
|
||||
void NativeRenderer::InitializeGl() {
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(GL_CULL_FACE);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
glDisable(GL_BLEND);
|
||||
|
||||
const uint32_t vertShader = LoadGLShader(GL_VERTEX_SHADER, kSimepleVertexShader);
|
||||
const uint32_t fragShader = LoadGLShader(GL_FRAGMENT_SHADER, kPassthroughFragmentShader);
|
||||
std::string error;
|
||||
_program = gl::compileProgram({ vertShader, fragShader }, error);
|
||||
CheckGLError("build program");
|
||||
|
||||
glGenBuffers(1, &_geometryBuffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _geometryBuffer);
|
||||
glBufferData(GL_ARRAY_BUFFER, sizeof(float) * 9, triangle::TRIANGLE_VERTS.data(), GL_STATIC_DRAW);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
CheckGLError("upload vertices");
|
||||
|
||||
glGenVertexArrays(1, &_vao);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, _geometryBuffer);
|
||||
glBindVertexArray(_vao);
|
||||
|
||||
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
|
||||
glEnableVertexAttribArray(0);
|
||||
glBindVertexArray(0);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
CheckGLError("build vao ");
|
||||
}
|
||||
|
||||
|
||||
void NativeRenderer::DrawFrame() {
|
||||
auto now = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||
std::chrono::system_clock::now() - start);
|
||||
glm::vec3 v;
|
||||
v.r = (float) (now.count() % 1000) / 1000.0f;
|
||||
v.g = 1.0f - v.r;
|
||||
v.b = 1.0f;
|
||||
|
||||
glClearColor(v.r, v.g, v.b, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glUseProgram(_program);
|
||||
glBindVertexArray(_vao);
|
||||
glDrawArrays(GL_TRIANGLES, 0, 3);
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
|
||||
void NativeRenderer::OnTriggerEvent() {
|
||||
qDebug() << "QQQ" << __FUNCTION__;
|
||||
}
|
||||
|
||||
void NativeRenderer::OnPause() {
|
||||
qDebug() << "QQQ" << __FUNCTION__;
|
||||
}
|
||||
|
||||
void NativeRenderer::OnResume() {
|
||||
qDebug() << "QQQ" << __FUNCTION__;
|
||||
}
|
|
@ -1,21 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <array>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
class NativeRenderer {
|
||||
public:
|
||||
void InitializeGl();
|
||||
void DrawFrame();
|
||||
void OnTriggerEvent();
|
||||
void OnPause();
|
||||
void OnResume();
|
||||
|
||||
private:
|
||||
std::chrono::time_point<std::chrono::system_clock> start { std::chrono::system_clock::now() };
|
||||
|
||||
uint32_t _geometryBuffer { 0 };
|
||||
uint32_t _vao { 0 };
|
||||
uint32_t _program { 0 };
|
||||
};
|
|
@ -0,0 +1,42 @@
|
|||
//
|
||||
// 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());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,178 @@
|
|||
//
|
||||
// InterfaceActivity.java
|
||||
// android/app/src/main/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.hifiinterface;
|
||||
|
||||
import android.content.Intent;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Bundle;
|
||||
import android.view.WindowManager;
|
||||
import android.util.Log;
|
||||
import org.qtproject.qt5.android.bindings.QtActivity;
|
||||
|
||||
/*import com.google.vr.cardboard.DisplaySynchronizer;
|
||||
import com.google.vr.cardboard.DisplayUtils;
|
||||
import com.google.vr.ndk.base.GvrApi;*/
|
||||
import android.graphics.Point;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.view.View;
|
||||
|
||||
public class InterfaceActivity extends QtActivity {
|
||||
|
||||
//public static native void handleHifiURL(String hifiURLString);
|
||||
private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
|
||||
//private native void nativeOnPause();
|
||||
//private native void nativeOnResume();
|
||||
//private native void nativeOnStop();
|
||||
//private native void nativeOnStart();
|
||||
//private native void saveRealScreenSize(int width, int height);
|
||||
//private native void setAppVersion(String version);
|
||||
private native long nativeOnExitVr();
|
||||
|
||||
private AssetManager assetManager;
|
||||
|
||||
private static boolean inVrMode;
|
||||
// private GvrApi gvrApi;
|
||||
// Opaque native pointer to the Application C++ object.
|
||||
// This object is owned by the InterfaceActivity instance and passed to the native methods.
|
||||
//private long nativeGvrApi;
|
||||
|
||||
public void enterVr() {
|
||||
//Log.d("[VR]", "Entering Vr mode (java)");
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
|
||||
inVrMode = true;
|
||||
}
|
||||
|
||||
@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());
|
||||
// }
|
||||
}
|
||||
|
||||
/* DisplaySynchronizer displaySynchronizer = new DisplaySynchronizer(this, DisplayUtils.getDefaultDisplay(this));
|
||||
gvrApi = new GvrApi(this, displaySynchronizer);
|
||||
*/
|
||||
// Log.d("GVR", "gvrApi.toString(): " + gvrApi.toString());
|
||||
|
||||
assetManager = getResources().getAssets();
|
||||
|
||||
//nativeGvrApi =
|
||||
nativeOnCreate(this, assetManager /*, gvrApi.getNativeGvrContext()*/);
|
||||
|
||||
Point size = new Point();
|
||||
getWindowManager().getDefaultDisplay().getRealSize(size);
|
||||
// saveRealScreenSize(size.x, size.y);
|
||||
|
||||
try {
|
||||
PackageInfo pInfo = this.getPackageManager().getPackageInfo(getPackageName(), 0);
|
||||
String version = pInfo.versionName;
|
||||
// setAppVersion(version);
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Log.e("GVR", "Error getting application version", e);
|
||||
}
|
||||
|
||||
final View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
|
||||
|
||||
// This is a workaround to hide the menu bar when the virtual keyboard is shown from Qt
|
||||
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener() {
|
||||
@Override
|
||||
public void onGlobalLayout() {
|
||||
int heightDiff = rootView.getRootView().getHeight() - rootView.getHeight();
|
||||
if (getActionBar().isShowing()) {
|
||||
getActionBar().hide();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
//nativeOnPause();
|
||||
//gvrApi.pauseTracking();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
// nativeOnStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
Log.d("[Background]","Calling nativeOnStop from InterfaceActivity");
|
||||
// nativeOnStop();
|
||||
super.onStop();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
//nativeOnResume();
|
||||
//gvrApi.resumeTracking();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
// Checks the orientation of the screen
|
||||
if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){
|
||||
// Log.d("[VR]", "Portrait, forcing landscape");
|
||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
||||
if (inVrMode) {
|
||||
inVrMode = false;
|
||||
// Log.d("[VR]", "Starting VR exit");
|
||||
nativeOnExitVr();
|
||||
} else {
|
||||
Log.w("[VR]", "Portrait detected but not in VR mode. Should not happen");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void openUrlInAndroidWebView(String urlString) {
|
||||
Log.d("openUrl", "Received in open " + urlString);
|
||||
Intent openUrlIntent = new Intent(this, WebViewActivity.class);
|
||||
openUrlIntent.putExtra(WebViewActivity.WEB_VIEW_ACTIVITY_EXTRA_URL, urlString);
|
||||
startActivity(openUrlIntent);
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when view focus is changed
|
||||
*/
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus) {
|
||||
final int uiOptions = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
|
||||
| View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
|
||||
getWindow().getDecorView().setSystemUiVisibility(uiOptions);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,130 @@
|
|||
package io.highfidelity.hifiinterface;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Bundle;
|
||||
import android.app.Activity;
|
||||
|
||||
import android.content.DialogInterface;
|
||||
import android.app.AlertDialog;
|
||||
|
||||
import org.json.JSONException;
|
||||
import org.json.JSONObject;
|
||||
import android.util.Log;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
import java.io.Writer;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class PermissionChecker extends Activity {
|
||||
private static final int REQUEST_PERMISSIONS = 20;
|
||||
|
||||
private static final boolean CHOOSE_AVATAR_ON_STARTUP = false;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
if (CHOOSE_AVATAR_ON_STARTUP) {
|
||||
showMenu();
|
||||
}
|
||||
this.requestAppPermissions(new
|
||||
String[]{
|
||||
Manifest.permission.READ_EXTERNAL_STORAGE,
|
||||
Manifest.permission.WRITE_EXTERNAL_STORAGE,
|
||||
Manifest.permission.RECORD_AUDIO,
|
||||
Manifest.permission.CAMERA}
|
||||
,2,REQUEST_PERMISSIONS);
|
||||
|
||||
}
|
||||
|
||||
public void requestAppPermissions(final String[] requestedPermissions,
|
||||
final int stringId, final int requestCode) {
|
||||
int permissionCheck = PackageManager.PERMISSION_GRANTED;
|
||||
boolean shouldShowRequestPermissionRationale = false;
|
||||
for (String permission : requestedPermissions) {
|
||||
permissionCheck = permissionCheck + checkSelfPermission(permission);
|
||||
shouldShowRequestPermissionRationale = shouldShowRequestPermissionRationale || shouldShowRequestPermissionRationale(permission);
|
||||
}
|
||||
if (permissionCheck != PackageManager.PERMISSION_GRANTED) {
|
||||
System.out.println("Permission was not granted. Ask for permissions");
|
||||
if (shouldShowRequestPermissionRationale) {
|
||||
requestPermissions(requestedPermissions, requestCode);
|
||||
} else {
|
||||
requestPermissions(requestedPermissions, requestCode);
|
||||
}
|
||||
} else {
|
||||
System.out.println("Launching the other activity..");
|
||||
launchActivityWithPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
private void launchActivityWithPermissions(){
|
||||
finish();
|
||||
Intent i = new Intent(this, InterfaceActivity.class);
|
||||
startActivity(i);
|
||||
finish();
|
||||
}
|
||||
|
||||
private void showMenu(){
|
||||
final List<String> avatarOptions = Arrays.asList("\uD83D\uDC66\uD83C\uDFFB Cody","\uD83D\uDC66\uD83C\uDFFF Will","\uD83D\uDC68\uD83C\uDFFD Albert", "\uD83D\uDC7D Being of Light");
|
||||
final String[] avatarPaths = {
|
||||
"http://mpassets.highfidelity.com/8c859fca-4cbd-4e82-aad1-5f4cb0ca5d53-v1/cody.fst",
|
||||
"http://mpassets.highfidelity.com/d029ae8d-2905-4eb7-ba46-4bd1b8cb9d73-v1/4618d52e711fbb34df442b414da767bb.fst",
|
||||
"http://mpassets.highfidelity.com/1e57c395-612e-4acd-9561-e79dbda0bc49-v1/albert.fst" };
|
||||
|
||||
final String pathForJson = "/data/data/io.highfidelity.hifiinterface/files/.config/High Fidelity - dev/";
|
||||
new AlertDialog.Builder(this)
|
||||
.setTitle("Pick an avatar")
|
||||
.setItems(avatarOptions.toArray(new CharSequence[avatarOptions.size()]),new DialogInterface.OnClickListener(){
|
||||
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
if(which < avatarPaths.length ) {
|
||||
JSONObject obj = new JSONObject();
|
||||
try {
|
||||
obj.put("firstRun",false);
|
||||
obj.put("Avatar/fullAvatarURL", avatarPaths[which]);
|
||||
File directory = new File(pathForJson);
|
||||
|
||||
if(!directory.exists()) directory.mkdirs();
|
||||
|
||||
File file = new File(pathForJson + "Interface.json");
|
||||
Writer output = new BufferedWriter(new FileWriter(file));
|
||||
output.write(obj.toString().replace("\\",""));
|
||||
output.close();
|
||||
System.out.println("I Could write config file expect to see the selected avatar"+obj.toString().replace("\\",""));
|
||||
|
||||
} catch (JSONException e) {
|
||||
System.out.println("JSONException something weired went wrong");
|
||||
} catch (IOException e) {
|
||||
System.out.println("Could not write file :(");
|
||||
}
|
||||
} else {
|
||||
System.out.println("Default avatar selected...");
|
||||
}
|
||||
launchActivityWithPermissions();
|
||||
}
|
||||
}).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
int permissionCheck = PackageManager.PERMISSION_GRANTED;
|
||||
for (int permission : grantResults) {
|
||||
permissionCheck = permissionCheck + permission;
|
||||
}
|
||||
if ((grantResults.length > 0) && permissionCheck == PackageManager.PERMISSION_GRANTED) {
|
||||
launchActivityWithPermissions();
|
||||
} else if (grantResults.length > 0) {
|
||||
System.out.println("User has deliberately denied Permissions. Launching anyways");
|
||||
launchActivityWithPermissions();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,242 @@
|
|||
//
|
||||
// WebViewActivity.java
|
||||
// interface/java
|
||||
//
|
||||
// Created by Cristian Duarte and Gabriel Calero on 8/17/17.
|
||||
// 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
|
||||
//
|
||||
|
||||
//package hifiinterface.highfidelity.io.mybrowserapplication;
|
||||
package io.highfidelity.hifiinterface;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.app.Activity;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.net.Uri;
|
||||
import android.net.http.SslError;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.webkit.SslErrorHandler;
|
||||
import android.webkit.WebChromeClient;
|
||||
import android.webkit.WebResourceError;
|
||||
import android.webkit.WebResourceRequest;
|
||||
import android.webkit.WebResourceResponse;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.ProgressBar;
|
||||
import android.widget.Toast;
|
||||
import android.widget.Toolbar;
|
||||
import android.os.Looper;
|
||||
import java.lang.Thread;
|
||||
import java.lang.Runnable;
|
||||
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
|
||||
public class WebViewActivity extends Activity {
|
||||
|
||||
public static final String WEB_VIEW_ACTIVITY_EXTRA_URL = "url";
|
||||
|
||||
private native void nativeProcessURL(String url);
|
||||
|
||||
private WebView myWebView;
|
||||
private ProgressBar mProgressBar;
|
||||
private ActionBar mActionBar;
|
||||
private String mUrl;
|
||||
|
||||
enum SafenessLevel {
|
||||
NOT_ANALYZED_YET(""),
|
||||
NOT_SECURE(""),
|
||||
SECURE("\uD83D\uDD12 "),
|
||||
BAD_SECURE("\uD83D\uDD13 ");
|
||||
|
||||
String icon;
|
||||
SafenessLevel(String icon) {
|
||||
this.icon = icon;
|
||||
}
|
||||
}
|
||||
|
||||
private SafenessLevel safenessLevel = SafenessLevel.NOT_ANALYZED_YET;
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setContentView(R.layout.activity_web_view);
|
||||
|
||||
setActionBar((Toolbar) findViewById(R.id.toolbar_actionbar));
|
||||
mActionBar = getActionBar();
|
||||
mActionBar.setDisplayHomeAsUpEnabled(true);
|
||||
|
||||
mProgressBar = (ProgressBar) findViewById(R.id.toolbarProgressBar);
|
||||
|
||||
mUrl = getIntent().getStringExtra(WEB_VIEW_ACTIVITY_EXTRA_URL);
|
||||
myWebView = (WebView) findViewById(R.id.web_view);
|
||||
myWebView.setWebViewClient(new HiFiWebViewClient());
|
||||
myWebView.setWebChromeClient(new HiFiWebChromeClient());
|
||||
WebSettings webSettings = myWebView.getSettings();
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
webSettings.setBuiltInZoomControls(true);
|
||||
webSettings.setDisplayZoomControls(false);
|
||||
myWebView.loadUrl(mUrl);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
// Check if the key event was the Back button and if there's history
|
||||
if ((keyCode == KeyEvent.KEYCODE_BACK) && myWebView.canGoBack()) {
|
||||
myWebView.goBack();
|
||||
return true;
|
||||
}
|
||||
// If it wasn't the Back key or there's no web page history, bubble up to the default
|
||||
// system behavior (probably exit the activity)
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
|
||||
private void showSubtitleWithUrl(String url) {
|
||||
try {
|
||||
mActionBar.setSubtitle(safenessLevel.icon + new URL(url.toString()).getHost());
|
||||
} catch (MalformedURLException e) {
|
||||
Toast.makeText(WebViewActivity.this, "Error loading page: " + "bad url", Toast.LENGTH_LONG).show();
|
||||
Log.e("openUrl", "bad url");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
MenuInflater inflater = getMenuInflater();
|
||||
inflater.inflate(R.menu.web_view_menu, menu);
|
||||
return true;
|
||||
}
|
||||
|
||||
private String intentUrlOrWebUrl() {
|
||||
return myWebView==null || myWebView.getUrl()==null?mUrl:myWebView.getUrl();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item){
|
||||
switch (item.getItemId()) {
|
||||
// Respond to the action bar's Up/Home/back button
|
||||
case android.R.id.home:
|
||||
finish();
|
||||
break;
|
||||
case R.id.action_browser:
|
||||
Intent i = new Intent(Intent.ACTION_VIEW);
|
||||
i.setData(Uri.parse(intentUrlOrWebUrl()));
|
||||
startActivity(i);
|
||||
return true;
|
||||
case R.id.action_share:
|
||||
Intent is = new Intent(Intent.ACTION_SEND);
|
||||
is.setType("text/plain");
|
||||
is.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.web_view_action_share_subject));
|
||||
is.putExtra(Intent.EXTRA_TEXT, intentUrlOrWebUrl());
|
||||
startActivity(Intent.createChooser(is, getString(R.string.web_view_action_share_chooser)));
|
||||
return true;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
class HiFiWebViewClient extends WebViewClient {
|
||||
|
||||
@Override
|
||||
public void onPageFinished(WebView view, String url) {
|
||||
super.onPageFinished(view, url);
|
||||
mProgressBar.setVisibility(View.GONE);
|
||||
if (safenessLevel!=SafenessLevel.BAD_SECURE) {
|
||||
if (url.startsWith("https:")) {
|
||||
safenessLevel=SafenessLevel.SECURE;
|
||||
} else {
|
||||
safenessLevel=SafenessLevel.NOT_SECURE;
|
||||
}
|
||||
}
|
||||
showSubtitleWithUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPageStarted(WebView view, String url, Bitmap favicon) {
|
||||
super.onPageStarted(view, url, favicon);
|
||||
safenessLevel = SafenessLevel.NOT_ANALYZED_YET;
|
||||
mProgressBar.setVisibility(View.VISIBLE);
|
||||
mProgressBar.setProgress(0);
|
||||
showSubtitleWithUrl(url);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedError(WebView view, WebResourceRequest request, WebResourceError error) {
|
||||
Toast.makeText(WebViewActivity.this, "Error loading page: " + error.getDescription(), Toast.LENGTH_LONG).show();
|
||||
if (ERROR_FAILED_SSL_HANDSHAKE == error.getErrorCode()) {
|
||||
safenessLevel = SafenessLevel.BAD_SECURE;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedHttpError(WebView view, WebResourceRequest request, WebResourceResponse errorResponse) {
|
||||
Toast.makeText(WebViewActivity.this, "Network Error loading page: " + errorResponse.getReasonPhrase(), Toast.LENGTH_LONG).show();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedSslError(WebView view, SslErrorHandler handler, SslError error) {
|
||||
super.onReceivedSslError(view, handler, error);
|
||||
Toast.makeText(WebViewActivity.this, "SSL error loading page: " + error.toString(), Toast.LENGTH_LONG).show();
|
||||
safenessLevel = SafenessLevel.BAD_SECURE;
|
||||
}
|
||||
|
||||
private boolean isFst(WebResourceRequest request) {
|
||||
return isFst(request.getUrl().toString());
|
||||
}
|
||||
|
||||
private boolean isFst(String url) {
|
||||
return url.endsWith(".fst");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) {
|
||||
// managing avatar selections
|
||||
if (isFst(request)) {
|
||||
final String url = request.getUrl().toString();
|
||||
new Thread(new Runnable() {
|
||||
public void run() {
|
||||
nativeProcessURL(url);
|
||||
}
|
||||
}).start(); // Avoid deadlock in Qt dialog
|
||||
WebViewActivity.this.finish();
|
||||
return true;
|
||||
}
|
||||
return super.shouldOverrideUrlLoading(view, request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoadResource(WebView view, String url) {
|
||||
if (isFst(url)) {
|
||||
// processed separately
|
||||
} else {
|
||||
super.onLoadResource(view, url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class HiFiWebChromeClient extends WebChromeClient {
|
||||
|
||||
@Override
|
||||
public void onProgressChanged(WebView view, int newProgress) {
|
||||
super.onProgressChanged(view, newProgress);
|
||||
mProgressBar.setProgress(newProgress);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedTitle(WebView view, String title) {
|
||||
super.onReceivedTitle(view, title);
|
||||
mActionBar.setTitle(title);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,951 @@
|
|||
/*
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt.io/licensing/
|
||||
|
||||
Commercial License Usage
|
||||
Licensees holding valid commercial Qt licenses may use this file in
|
||||
accordance with the commercial license agreement provided with the
|
||||
Software or, alternatively, in accordance with the terms contained in
|
||||
a written agreement between you and The Qt Company. For licensing terms
|
||||
and conditions see http://www.qt.io/terms-conditions. For further
|
||||
information use the contact form at http://www.qt.io/contact-us.
|
||||
|
||||
BSD License Usage
|
||||
Alternatively, this file may be used under the BSD license as follows:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.Dialog;
|
||||
import android.app.Fragment;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources.Theme;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.ActionMode;
|
||||
import android.view.ActionMode.Callback;
|
||||
import android.view.ContextMenu;
|
||||
import android.view.ContextMenu.ContextMenuInfo;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.accessibility.AccessibilityEvent;
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public class QtActivity extends Activity {
|
||||
public String APPLICATION_PARAMETERS = null;
|
||||
public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
|
||||
public final String[] QT_ANDROID_THEMES = new String[]{"Theme_Holo_Light"};
|
||||
public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme.
|
||||
private QtActivityLoader m_loader = new QtActivityLoader(this);
|
||||
|
||||
public QtActivity() {
|
||||
}
|
||||
|
||||
/////////////////////////// forward all notifications ////////////////////////////
|
||||
/////////////////////////// Super class calls ////////////////////////////////////
|
||||
/////////////// PLEASE DO NOT CHANGE THE FOLLOWING CODE //////////////////////////
|
||||
//////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
@Override
|
||||
public boolean dispatchKeyEvent(KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyEvent, event);
|
||||
} else {
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchKeyEvent(KeyEvent event) {
|
||||
return super.dispatchKeyEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchPopulateAccessibilityEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchPopulateAccessibilityEvent, event);
|
||||
} else {
|
||||
return super.dispatchPopulateAccessibilityEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
|
||||
return super_dispatchPopulateAccessibilityEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchTouchEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTouchEvent, ev);
|
||||
} else {
|
||||
return super.dispatchTouchEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchTouchEvent(MotionEvent event) {
|
||||
return super.dispatchTouchEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean dispatchTrackballEvent(MotionEvent ev) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchTrackballEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchTrackballEvent, ev);
|
||||
} else {
|
||||
return super.dispatchTrackballEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchTrackballEvent(MotionEvent event) {
|
||||
return super.dispatchTrackballEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onActivityResult != null) {
|
||||
QtApplication.invokeDelegateMethod(QtApplication.onActivityResult, requestCode, resultCode, data);
|
||||
return;
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
|
||||
public void super_onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onApplyThemeResource(Theme theme, int resid, boolean first) {
|
||||
if (!QtApplication.invokeDelegate(theme, resid, first).invoked) {
|
||||
super.onApplyThemeResource(theme, resid, first);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onApplyThemeResource(Theme theme, int resid, boolean first) {
|
||||
super.onApplyThemeResource(theme, resid, first);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
protected void onChildTitleChanged(Activity childActivity, CharSequence title) {
|
||||
if (!QtApplication.invokeDelegate(childActivity, title).invoked) {
|
||||
super.onChildTitleChanged(childActivity, title);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onChildTitleChanged(Activity childActivity, CharSequence title) {
|
||||
super.onChildTitleChanged(childActivity, title);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(Configuration newConfig) {
|
||||
if (!QtApplication.invokeDelegate(newConfig).invoked) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onContentChanged() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onContentChanged();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onContentChanged() {
|
||||
super.onContentChanged();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onContextItemSelected(MenuItem item) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(item);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onContextItemSelected(MenuItem item) {
|
||||
return super.onContextItemSelected(item);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onContextMenuClosed(Menu menu) {
|
||||
if (!QtApplication.invokeDelegate(menu).invoked) {
|
||||
super.onContextMenuClosed(menu);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onContextMenuClosed(Menu menu) {
|
||||
super.onContextMenuClosed(menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
protected void onCreateHook(Bundle savedInstanceState) {
|
||||
m_loader.APPLICATION_PARAMETERS = APPLICATION_PARAMETERS;
|
||||
m_loader.ENVIRONMENT_VARIABLES = ENVIRONMENT_VARIABLES;
|
||||
m_loader.QT_ANDROID_THEMES = QT_ANDROID_THEMES;
|
||||
m_loader.QT_ANDROID_DEFAULT_THEME = QT_ANDROID_DEFAULT_THEME;
|
||||
m_loader.onCreate(savedInstanceState);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
onCreateHook(savedInstanceState);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
if (!QtApplication.invokeDelegate(menu, v, menuInfo).invoked) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
|
||||
super.onCreateContextMenu(menu, v, menuInfo);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public CharSequence onCreateDescription() {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate();
|
||||
if (res.invoked) {
|
||||
return (CharSequence) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateDescription();
|
||||
}
|
||||
}
|
||||
|
||||
public CharSequence super_onCreateDescription() {
|
||||
return super.onCreateDescription();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(id);
|
||||
if (res.invoked) {
|
||||
return (Dialog) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
}
|
||||
|
||||
public Dialog super_onCreateDialog(int id) {
|
||||
return super.onCreateDialog(id);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onCreateOptionsMenu(Menu menu) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onCreateOptionsMenu(Menu menu) {
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onCreatePanelMenu(int featureId, Menu menu) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreatePanelMenu(featureId, menu);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onCreatePanelMenu(int featureId, Menu menu) {
|
||||
return super.onCreatePanelMenu(featureId, menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
public View onCreatePanelView(int featureId) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId);
|
||||
if (res.invoked) {
|
||||
return (View) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreatePanelView(featureId);
|
||||
}
|
||||
}
|
||||
|
||||
public View super_onCreatePanelView(int featureId) {
|
||||
return super.onCreatePanelView(featureId);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onCreateThumbnail(Bitmap outBitmap, Canvas canvas) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(outBitmap, canvas);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateThumbnail(outBitmap, canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onCreateThumbnail(Bitmap outBitmap, Canvas canvas) {
|
||||
return super.onCreateThumbnail(outBitmap, canvas);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public View onCreateView(String name, Context context, AttributeSet attrs) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(name, context, attrs);
|
||||
if (res.invoked) {
|
||||
return (View) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateView(name, context, attrs);
|
||||
}
|
||||
}
|
||||
|
||||
public View super_onCreateView(String name, Context context, AttributeSet attrs) {
|
||||
return super.onCreateView(name, context, attrs);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onKeyDown != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyDown, keyCode, event);
|
||||
} else {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onKeyDown(int keyCode, KeyEvent event) {
|
||||
return super.onKeyDown(keyCode, event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@Override
|
||||
public boolean onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onKeyMultiple != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyMultiple, keyCode, repeatCount, event);
|
||||
} else {
|
||||
return super.onKeyMultiple(keyCode, repeatCount, event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onKeyMultiple(int keyCode, int repeatCount, KeyEvent event) {
|
||||
return super.onKeyMultiple(keyCode, repeatCount, event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onKeyUp != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyUp, keyCode, event);
|
||||
} else {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onKeyUp(int keyCode, KeyEvent event) {
|
||||
return super.onKeyUp(keyCode, event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onLowMemory();
|
||||
}
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemSelected(int featureId, MenuItem item) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, item);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onMenuItemSelected(featureId, item);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onMenuItemSelected(int featureId, MenuItem item) {
|
||||
return super.onMenuItemSelected(featureId, item);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onMenuOpened(int featureId, Menu menu) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, menu);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onMenuOpened(featureId, menu);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onMenuOpened(int featureId, Menu menu) {
|
||||
return super.onMenuOpened(featureId, menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onNewIntent(Intent intent) {
|
||||
if (!QtApplication.invokeDelegate(intent).invoked) {
|
||||
super.onNewIntent(intent);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onNewIntent(Intent intent) {
|
||||
super.onNewIntent(intent);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(item);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onOptionsItemSelected(MenuItem item) {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onOptionsMenuClosed(Menu menu) {
|
||||
if (!QtApplication.invokeDelegate(menu).invoked) {
|
||||
super.onOptionsMenuClosed(menu);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onOptionsMenuClosed(Menu menu) {
|
||||
super.onOptionsMenuClosed(menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onPanelClosed(int featureId, Menu menu) {
|
||||
if (!QtApplication.invokeDelegate(featureId, menu).invoked) {
|
||||
super.onPanelClosed(featureId, menu);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onPanelClosed(int featureId, Menu menu) {
|
||||
super.onPanelClosed(featureId, menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onPostCreate(Bundle savedInstanceState) {
|
||||
super.onPostCreate(savedInstanceState);
|
||||
QtApplication.invokeDelegate(savedInstanceState);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onPostResume() {
|
||||
super.onPostResume();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialog(int id, Dialog dialog) {
|
||||
if (!QtApplication.invokeDelegate(id, dialog).invoked) {
|
||||
super.onPrepareDialog(id, dialog);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onPrepareDialog(int id, Dialog dialog) {
|
||||
super.onPrepareDialog(id, dialog);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onPrepareOptionsMenu(Menu menu) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(menu);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onPrepareOptionsMenu(Menu menu) {
|
||||
return super.onPrepareOptionsMenu(menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onPreparePanel(int featureId, View view, Menu menu) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(featureId, view, menu);
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onPreparePanel(featureId, view, menu);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onPreparePanel(int featureId, View view, Menu menu) {
|
||||
return super.onPreparePanel(featureId, view, menu);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onRestart() {
|
||||
super.onRestart();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
if (!QtApplication.invokeDelegate(savedInstanceState).invoked) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onRestoreInstanceState(Bundle savedInstanceState) {
|
||||
super.onRestoreInstanceState(savedInstanceState);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public Object onRetainNonConfigurationInstance() {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate();
|
||||
if (res.invoked) {
|
||||
return res.methodReturns;
|
||||
} else {
|
||||
return super.onRetainNonConfigurationInstance();
|
||||
}
|
||||
}
|
||||
|
||||
public Object super_onRetainNonConfigurationInstance() {
|
||||
return super.onRetainNonConfigurationInstance();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(Bundle outState) {
|
||||
if (!QtApplication.invokeDelegate(outState).invoked) {
|
||||
super.onSaveInstanceState(outState);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onSaveInstanceState(Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onSearchRequested() {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate();
|
||||
if (res.invoked) {
|
||||
return (Boolean) res.methodReturns;
|
||||
} else {
|
||||
return super.onSearchRequested();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onSearchRequested() {
|
||||
return super.onSearchRequested();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
super.onStop();
|
||||
QtApplication.invokeDelegate();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onTitleChanged(CharSequence title, int color) {
|
||||
if (!QtApplication.invokeDelegate(title, color).invoked) {
|
||||
super.onTitleChanged(title, color);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onTitleChanged(CharSequence title, int color) {
|
||||
super.onTitleChanged(title, color);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onTouchEvent(MotionEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onTouchEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTouchEvent, event);
|
||||
} else {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onTouchEvent(MotionEvent event) {
|
||||
return super.onTouchEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onTrackballEvent(MotionEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onTrackballEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onTrackballEvent, event);
|
||||
} else {
|
||||
return super.onTrackballEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onTrackballEvent(MotionEvent event) {
|
||||
return super.onTrackballEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onUserInteraction() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onUserInteraction();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onUserInteraction() {
|
||||
super.onUserInteraction();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onUserLeaveHint() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onUserLeaveHint();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onUserLeaveHint() {
|
||||
super.onUserLeaveHint();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onWindowAttributesChanged(LayoutParams params) {
|
||||
if (!QtApplication.invokeDelegate(params).invoked) {
|
||||
super.onWindowAttributesChanged(params);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onWindowAttributesChanged(LayoutParams params) {
|
||||
super.onWindowAttributesChanged(params);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
if (!QtApplication.invokeDelegate(hasFocus).invoked) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
//////////////// Activity API 5 /////////////
|
||||
//@ANDROID-5
|
||||
@Override
|
||||
public void onAttachedToWindow() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onBackPressed() {
|
||||
super.onBackPressed();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onDetachedFromWindow() {
|
||||
if (!QtApplication.invokeDelegate().invoked) {
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onKeyLongPress(int keyCode, KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onKeyLongPress != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyLongPress, keyCode, event);
|
||||
} else {
|
||||
return super.onKeyLongPress(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onKeyLongPress(int keyCode, KeyEvent event) {
|
||||
return super.onKeyLongPress(keyCode, event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
//@ANDROID-5
|
||||
|
||||
//////////////// Activity API 8 /////////////
|
||||
//@ANDROID-8
|
||||
@Override
|
||||
protected Dialog onCreateDialog(int id, Bundle args) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(id, args);
|
||||
if (res.invoked) {
|
||||
return (Dialog) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateDialog(id, args);
|
||||
}
|
||||
}
|
||||
|
||||
public Dialog super_onCreateDialog(int id, Bundle args) {
|
||||
return super.onCreateDialog(id, args);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
|
||||
if (!QtApplication.invokeDelegate(id, dialog, args).invoked) {
|
||||
super.onPrepareDialog(id, dialog, args);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onPrepareDialog(int id, Dialog dialog, Bundle args) {
|
||||
super.onPrepareDialog(id, dialog, args);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
//@ANDROID-8
|
||||
//////////////// Activity API 11 /////////////
|
||||
|
||||
//@ANDROID-11
|
||||
@Override
|
||||
public boolean dispatchKeyShortcutEvent(KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchKeyShortcutEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchKeyShortcutEvent, event);
|
||||
} else {
|
||||
return super.dispatchKeyShortcutEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchKeyShortcutEvent(KeyEvent event) {
|
||||
return super.dispatchKeyShortcutEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onActionModeFinished(ActionMode mode) {
|
||||
if (!QtApplication.invokeDelegate(mode).invoked) {
|
||||
super.onActionModeFinished(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onActionModeFinished(ActionMode mode) {
|
||||
super.onActionModeFinished(mode);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onActionModeStarted(ActionMode mode) {
|
||||
if (!QtApplication.invokeDelegate(mode).invoked) {
|
||||
super.onActionModeStarted(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onActionModeStarted(ActionMode mode) {
|
||||
super.onActionModeStarted(mode);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public void onAttachFragment(Fragment fragment) {
|
||||
if (!QtApplication.invokeDelegate(fragment).invoked) {
|
||||
super.onAttachFragment(fragment);
|
||||
}
|
||||
}
|
||||
|
||||
public void super_onAttachFragment(Fragment fragment) {
|
||||
super.onAttachFragment(fragment);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(parent, name, context, attrs);
|
||||
if (res.invoked) {
|
||||
return (View) res.methodReturns;
|
||||
} else {
|
||||
return super.onCreateView(parent, name, context, attrs);
|
||||
}
|
||||
}
|
||||
|
||||
public View super_onCreateView(View parent, String name, Context context,
|
||||
AttributeSet attrs) {
|
||||
return super.onCreateView(parent, name, context, attrs);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onKeyShortcut(int keyCode, KeyEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onKeyShortcut != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onKeyShortcut, keyCode, event);
|
||||
} else {
|
||||
return super.onKeyShortcut(keyCode, event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onKeyShortcut(int keyCode, KeyEvent event) {
|
||||
return super.onKeyShortcut(keyCode, event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public ActionMode onWindowStartingActionMode(Callback callback) {
|
||||
QtApplication.InvokeResult res = QtApplication.invokeDelegate(callback);
|
||||
if (res.invoked) {
|
||||
return (ActionMode) res.methodReturns;
|
||||
} else {
|
||||
return super.onWindowStartingActionMode(callback);
|
||||
}
|
||||
}
|
||||
|
||||
public ActionMode super_onWindowStartingActionMode(Callback callback) {
|
||||
return super.onWindowStartingActionMode(callback);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
//@ANDROID-11
|
||||
//////////////// Activity API 12 /////////////
|
||||
|
||||
//@ANDROID-12
|
||||
@Override
|
||||
public boolean dispatchGenericMotionEvent(MotionEvent ev) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.dispatchGenericMotionEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.dispatchGenericMotionEvent, ev);
|
||||
} else {
|
||||
return super.dispatchGenericMotionEvent(ev);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_dispatchGenericMotionEvent(MotionEvent event) {
|
||||
return super.dispatchGenericMotionEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
@Override
|
||||
public boolean onGenericMotionEvent(MotionEvent event) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onGenericMotionEvent != null) {
|
||||
return (Boolean) QtApplication.invokeDelegateMethod(QtApplication.onGenericMotionEvent, event);
|
||||
} else {
|
||||
return super.onGenericMotionEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
public boolean super_onGenericMotionEvent(MotionEvent event) {
|
||||
return super.onGenericMotionEvent(event);
|
||||
}
|
||||
//---------------------------------------------------------------------------
|
||||
//@ANDROID-12
|
||||
|
||||
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onRequestPermissionsResult != null) {
|
||||
QtApplication.invokeDelegateMethod(QtApplication.onRequestPermissionsResult, requestCode, permissions, grantResults);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,502 @@
|
|||
/*
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt-project.org/legal
|
||||
|
||||
Commercial License Usage
|
||||
Licensees holding valid commercial Qt licenses may use this file in
|
||||
accordance with the commercial license agreement provided with the
|
||||
Software or, alternatively, in accordance with the terms contained in
|
||||
a written agreement between you and Digia. For licensing terms and
|
||||
conditions see http://qt.digia.com/licensing. For further information
|
||||
use the contact form at http://qt.digia.com/contact-us.
|
||||
|
||||
BSD License Usage
|
||||
Alternatively, this file may be used under the BSD license as follows:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ActivityInfo;
|
||||
import android.content.pm.ComponentInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.os.Bundle;
|
||||
import android.util.Log;
|
||||
import android.view.Window;
|
||||
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import dalvik.system.DexClassLoader;
|
||||
|
||||
public class QtActivityLoader {
|
||||
private static final String DEX_PATH_KEY = "dex.path";
|
||||
private static final String LIB_PATH_KEY = "lib.path";
|
||||
private static final String NATIVE_LIBRARIES_KEY = "native.libraries";
|
||||
private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
|
||||
private static final String APPLICATION_PARAMETERS_KEY = "application.parameters";
|
||||
private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries";
|
||||
private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id";
|
||||
private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id";
|
||||
private static final String MAIN_LIBRARY_KEY = "main.library";
|
||||
private static final String STATIC_INIT_CLASSES_KEY = "static.init.classes";
|
||||
private static final String EXTRACT_STYLE_KEY = "extract.android.style";
|
||||
private static final String EXTRACT_STYLE_MINIMAL_KEY = "extract.android.style.option";
|
||||
private static final int BUFFER_SIZE = 1024;
|
||||
|
||||
String APPLICATION_PARAMETERS = null; // use this variable to pass any parameters to your application,
|
||||
String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
|
||||
String[] QT_ANDROID_THEMES = null;
|
||||
String QT_ANDROID_DEFAULT_THEME = null;
|
||||
|
||||
private String[] m_qtLibs = null; // required qt libs
|
||||
private int m_displayDensity = -1;
|
||||
private ContextWrapper m_context;
|
||||
private ComponentInfo m_contextInfo;
|
||||
private Class<?> m_delegateClass;
|
||||
|
||||
// this function is used to load and start the loader
|
||||
private void loadApplication(Bundle loaderParams) {
|
||||
try {
|
||||
// add all bundled Qt libs to loader params
|
||||
ArrayList<String> libs = new ArrayList<>();
|
||||
|
||||
String libName = m_contextInfo.metaData.getString("android.app.lib_name");
|
||||
loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function
|
||||
loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs);
|
||||
|
||||
// load and start QtLoader class
|
||||
DexClassLoader classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files
|
||||
m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written.
|
||||
loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists)
|
||||
m_context.getClassLoader()); // parent loader
|
||||
|
||||
Class<?> loaderClass = classLoader.loadClass(loaderClassName()); // load QtLoader class
|
||||
Object qtLoader = loaderClass.newInstance(); // create an instance
|
||||
Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication",
|
||||
contextClassName(),
|
||||
ClassLoader.class,
|
||||
Bundle.class);
|
||||
if (!(Boolean) prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams)) {
|
||||
throw new Exception("");
|
||||
}
|
||||
|
||||
QtApplication.setQtContextDelegate(m_delegateClass, qtLoader);
|
||||
|
||||
// now load the application library so it's accessible from this class loader
|
||||
if (libName != null) {
|
||||
System.loadLibrary(libName);
|
||||
}
|
||||
|
||||
Method startAppMethod = qtLoader.getClass().getMethod("startApplication");
|
||||
if (!(Boolean) startAppMethod.invoke(qtLoader)) {
|
||||
throw new Exception("");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
AlertDialog errorDialog = new AlertDialog.Builder(m_context).create();
|
||||
errorDialog.setMessage("Fatal error, your application can't be started.");
|
||||
errorDialog.setButton(DialogInterface.BUTTON_NEUTRAL, m_context.getResources().getString(android.R.string.ok), (dialog, which) -> {
|
||||
finish();
|
||||
});
|
||||
errorDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
static private void copyFile(InputStream inputStream, OutputStream outputStream)
|
||||
throws IOException {
|
||||
byte[] buffer = new byte[BUFFER_SIZE];
|
||||
|
||||
int count;
|
||||
while ((count = inputStream.read(buffer)) > 0) {
|
||||
outputStream.write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
|
||||
private void copyAsset(String source, String destination)
|
||||
throws IOException {
|
||||
// Already exists, we don't have to do anything
|
||||
File destinationFile = new File(destination);
|
||||
if (destinationFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File parentDirectory = destinationFile.getParentFile();
|
||||
if (!parentDirectory.exists()) {
|
||||
parentDirectory.mkdirs();
|
||||
}
|
||||
|
||||
destinationFile.createNewFile();
|
||||
|
||||
AssetManager assetsManager = m_context.getAssets();
|
||||
InputStream inputStream = assetsManager.open(source);
|
||||
OutputStream outputStream = new FileOutputStream(destinationFile);
|
||||
copyFile(inputStream, outputStream);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
private static void createBundledBinary(String source, String destination)
|
||||
throws IOException {
|
||||
// Already exists, we don't have to do anything
|
||||
File destinationFile = new File(destination);
|
||||
if (destinationFile.exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
File parentDirectory = destinationFile.getParentFile();
|
||||
if (!parentDirectory.exists()) {
|
||||
parentDirectory.mkdirs();
|
||||
}
|
||||
|
||||
destinationFile.createNewFile();
|
||||
|
||||
InputStream inputStream = new FileInputStream(source);
|
||||
OutputStream outputStream = new FileOutputStream(destinationFile);
|
||||
copyFile(inputStream, outputStream);
|
||||
|
||||
inputStream.close();
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) {
|
||||
File versionFile = new File(pluginsPrefix + "cache.version");
|
||||
|
||||
long cacheVersion = 0;
|
||||
if (versionFile.exists() && versionFile.canRead()) {
|
||||
try {
|
||||
DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile));
|
||||
cacheVersion = inputStream.readLong();
|
||||
inputStream.close();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheVersion != packageVersion) {
|
||||
deleteRecursively(new File(pluginsPrefix));
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private void extractBundledPluginsAndImports(String pluginsPrefix) throws IOException {
|
||||
String libsDir = m_context.getApplicationInfo().nativeLibraryDir + "/";
|
||||
long packageVersion = -1;
|
||||
try {
|
||||
PackageInfo packageInfo = m_context.getPackageManager().getPackageInfo(m_context.getPackageName(), 0);
|
||||
packageVersion = packageInfo.lastUpdateTime;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
|
||||
if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) {
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
File versionFile = new File(pluginsPrefix + "cache.version");
|
||||
|
||||
File parentDirectory = versionFile.getParentFile();
|
||||
if (!parentDirectory.exists()) {
|
||||
parentDirectory.mkdirs();
|
||||
}
|
||||
|
||||
versionFile.createNewFile();
|
||||
|
||||
DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile));
|
||||
outputStream.writeLong(packageVersion);
|
||||
outputStream.close();
|
||||
}
|
||||
|
||||
{
|
||||
String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY;
|
||||
if (m_contextInfo.metaData.containsKey(key)) {
|
||||
String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key));
|
||||
|
||||
for (String bundledImportBinary : list) {
|
||||
String[] split = bundledImportBinary.split(":");
|
||||
String sourceFileName = libsDir + split[0];
|
||||
String destinationFileName = pluginsPrefix + split[1];
|
||||
createBundledBinary(sourceFileName, destinationFileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY;
|
||||
if (m_contextInfo.metaData.containsKey(key)) {
|
||||
String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key));
|
||||
|
||||
for (String fileName : list) {
|
||||
String[] split = fileName.split(":");
|
||||
String sourceFileName = split[0];
|
||||
String destinationFileName = pluginsPrefix + split[1];
|
||||
copyAsset(sourceFileName, destinationFileName);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteRecursively(File directory) {
|
||||
File[] files = directory.listFiles();
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
deleteRecursively(file);
|
||||
} else {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
|
||||
directory.delete();
|
||||
}
|
||||
}
|
||||
|
||||
private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) {
|
||||
File newCache = new File(localPrefix);
|
||||
if (!newCache.exists()) {
|
||||
{
|
||||
File oldPluginsCache = new File(oldLocalPrefix + "plugins/");
|
||||
if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) {
|
||||
deleteRecursively(oldPluginsCache);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
File oldImportsCache = new File(oldLocalPrefix + "imports/");
|
||||
if (oldImportsCache.exists() && oldImportsCache.isDirectory()) {
|
||||
deleteRecursively(oldImportsCache);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
File oldQmlCache = new File(oldLocalPrefix + "qml/");
|
||||
if (oldQmlCache.exists() && oldQmlCache.isDirectory()) {
|
||||
deleteRecursively(oldQmlCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void startApp() {
|
||||
try {
|
||||
if (m_contextInfo.metaData.containsKey("android.app.qt_libs_resource_id")) {
|
||||
int resourceId = m_contextInfo.metaData.getInt("android.app.qt_libs_resource_id");
|
||||
m_qtLibs = m_context.getResources().getStringArray(resourceId);
|
||||
}
|
||||
ArrayList<String> libraryList = new ArrayList<>();
|
||||
String localPrefix = m_context.getApplicationInfo().dataDir + "/";
|
||||
String pluginsPrefix = localPrefix + "qt-reserved-files/";
|
||||
cleanOldCacheIfNecessary(localPrefix, pluginsPrefix);
|
||||
extractBundledPluginsAndImports(pluginsPrefix);
|
||||
|
||||
for (String lib : m_qtLibs) {
|
||||
libraryList.add(localPrefix + "lib/lib" + lib + ".so");
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.load_local_libs")) {
|
||||
String[] extraLibs = m_contextInfo.metaData.getString("android.app.load_local_libs").split(":");
|
||||
for (String lib : extraLibs) {
|
||||
if (lib.length() > 0) {
|
||||
if (lib.startsWith("lib/")) {
|
||||
libraryList.add(localPrefix + lib);
|
||||
} else {
|
||||
libraryList.add(pluginsPrefix + lib);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Bundle loaderParams = new Bundle();
|
||||
loaderParams.putString(DEX_PATH_KEY, new String());
|
||||
if (m_contextInfo.metaData.containsKey("android.app.static_init_classes")) {
|
||||
loaderParams.putStringArray(STATIC_INIT_CLASSES_KEY,
|
||||
m_contextInfo.metaData.getString("android.app.static_init_classes").split(":"));
|
||||
}
|
||||
loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList);
|
||||
String themePath = m_context.getApplicationInfo().dataDir + "/qt-reserved-files/android-style/";
|
||||
String stylePath = themePath + m_displayDensity + "/";
|
||||
String extractOption = "full";
|
||||
if (m_contextInfo.metaData.containsKey("android.app.extract_android_style")) {
|
||||
extractOption = m_contextInfo.metaData.getString("android.app.extract_android_style");
|
||||
if (!extractOption.equals("full") && !extractOption.equals("minimal") && !extractOption.equals("none")) {
|
||||
Log.e(QtApplication.QtTAG, "Invalid extract_android_style option \"" + extractOption + "\", defaulting to full");
|
||||
extractOption = "full";
|
||||
}
|
||||
}
|
||||
|
||||
if (!(new File(stylePath)).exists() && !extractOption.equals("none")) {
|
||||
loaderParams.putString(EXTRACT_STYLE_KEY, stylePath);
|
||||
loaderParams.putBoolean(EXTRACT_STYLE_MINIMAL_KEY, extractOption.equals("minimal"));
|
||||
}
|
||||
|
||||
if (extractOption.equals("full")) {
|
||||
ENVIRONMENT_VARIABLES += "\tQT_USE_ANDROID_NATIVE_STYLE=1";
|
||||
}
|
||||
|
||||
ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEMES_ROOT_PATH=" + themePath;
|
||||
|
||||
loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES
|
||||
+ "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml"
|
||||
+ "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports"
|
||||
+ "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins");
|
||||
|
||||
String appParams = null;
|
||||
if (APPLICATION_PARAMETERS != null) {
|
||||
appParams = APPLICATION_PARAMETERS;
|
||||
}
|
||||
|
||||
Intent intent = getIntent();
|
||||
if (intent != null) {
|
||||
String parameters = intent.getStringExtra("applicationArguments");
|
||||
if (parameters != null) {
|
||||
if (appParams == null) {
|
||||
appParams = parameters;
|
||||
} else {
|
||||
appParams += '\t' + parameters;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.arguments")) {
|
||||
String parameters = m_contextInfo.metaData.getString("android.app.arguments");
|
||||
if (appParams == null) {
|
||||
appParams = parameters;
|
||||
} else {
|
||||
appParams += '\t' + parameters;
|
||||
}
|
||||
}
|
||||
|
||||
if (appParams != null) {
|
||||
loaderParams.putString(APPLICATION_PARAMETERS_KEY, appParams.replace(' ', '\t').trim());
|
||||
}
|
||||
loadApplication(loaderParams);
|
||||
} catch (Exception e) {
|
||||
Log.e(QtApplication.QtTAG, "Can't create main activity", e);
|
||||
}
|
||||
}
|
||||
|
||||
QtActivity m_activity;
|
||||
|
||||
QtActivityLoader(QtActivity activity) {
|
||||
m_context = activity;
|
||||
m_delegateClass = QtActivity.class;
|
||||
m_activity = activity;
|
||||
}
|
||||
|
||||
protected String loaderClassName() {
|
||||
return "org.qtproject.qt5.android.QtActivityDelegate";
|
||||
}
|
||||
|
||||
protected Class<?> contextClassName() {
|
||||
return android.app.Activity.class;
|
||||
}
|
||||
|
||||
protected void finish() {
|
||||
m_activity.finish();
|
||||
}
|
||||
|
||||
protected String getTitle() {
|
||||
return (String) m_activity.getTitle();
|
||||
}
|
||||
|
||||
protected void runOnUiThread(Runnable run) {
|
||||
m_activity.runOnUiThread(run);
|
||||
}
|
||||
|
||||
Intent getIntent() {
|
||||
return m_activity.getIntent();
|
||||
}
|
||||
|
||||
public void onCreate(Bundle savedInstanceState) {
|
||||
try {
|
||||
m_contextInfo = m_activity.getPackageManager().getActivityInfo(m_activity.getComponentName(), PackageManager.GET_META_DATA);
|
||||
int theme = ((ActivityInfo) m_contextInfo).getThemeResource();
|
||||
for (Field f : Class.forName("android.R$style").getDeclaredFields()) {
|
||||
if (f.getInt(null) == theme) {
|
||||
QT_ANDROID_THEMES = new String[]{f.getName()};
|
||||
QT_ANDROID_DEFAULT_THEME = f.getName();
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
finish();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
m_activity.setTheme(Class.forName("android.R$style").getDeclaredField(QT_ANDROID_DEFAULT_THEME).getInt(null));
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
m_activity.requestWindowFeature(Window.FEATURE_ACTION_BAR);
|
||||
|
||||
if (QtApplication.m_delegateObject != null && QtApplication.onCreate != null) {
|
||||
QtApplication.invokeDelegateMethod(QtApplication.onCreate, savedInstanceState);
|
||||
return;
|
||||
}
|
||||
|
||||
m_displayDensity = m_activity.getResources().getDisplayMetrics().densityDpi;
|
||||
|
||||
ENVIRONMENT_VARIABLES += "\tQT_ANDROID_THEME=" + QT_ANDROID_DEFAULT_THEME
|
||||
+ "/\tQT_ANDROID_THEME_DISPLAY_DPI=" + m_displayDensity + "\t";
|
||||
|
||||
if (null == m_activity.getLastNonConfigurationInstance()) {
|
||||
if (m_contextInfo.metaData.containsKey("android.app.background_running")
|
||||
&& m_contextInfo.metaData.getBoolean("android.app.background_running")) {
|
||||
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=0\t";
|
||||
} else {
|
||||
ENVIRONMENT_VARIABLES += "QT_BLOCK_EVENT_LOOPS_WHEN_SUSPENDED=1\t";
|
||||
}
|
||||
|
||||
if (m_contextInfo.metaData.containsKey("android.app.auto_screen_scale_factor")
|
||||
&& m_contextInfo.metaData.getBoolean("android.app.auto_screen_scale_factor")) {
|
||||
ENVIRONMENT_VARIABLES += "QT_AUTO_SCREEN_SCALE_FACTOR=1\t";
|
||||
}
|
||||
|
||||
startApp();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,168 @@
|
|||
/*
|
||||
Copyright (c) 2016, BogDan Vatra <bogdan@kde.org>
|
||||
Contact: http://www.qt.io/licensing/
|
||||
|
||||
Commercial License Usage
|
||||
Licensees holding valid commercial Qt licenses may use this file in
|
||||
accordance with the commercial license agreement provided with the
|
||||
Software or, alternatively, in accordance with the terms contained in
|
||||
a written agreement between you and The Qt Company. For licensing terms
|
||||
and conditions see http://www.qt.io/terms-conditions. For further
|
||||
information use the contact form at http://www.qt.io/contact-us.
|
||||
|
||||
BSD License Usage
|
||||
Alternatively, this file may be used under the BSD license as follows:
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions
|
||||
are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
|
||||
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
||||
THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.qtproject.qt5.android.bindings;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class QtApplication extends Application {
|
||||
public final static String QtTAG = "Qt";
|
||||
public static Object m_delegateObject = null;
|
||||
public static Map<String, ArrayList<Method>> m_delegateMethods = new HashMap<>();
|
||||
public static Method dispatchKeyEvent = null;
|
||||
public static Method dispatchPopulateAccessibilityEvent = null;
|
||||
public static Method dispatchTouchEvent = null;
|
||||
public static Method dispatchTrackballEvent = null;
|
||||
public static Method onKeyDown = null;
|
||||
public static Method onKeyMultiple = null;
|
||||
public static Method onKeyUp = null;
|
||||
public static Method onTouchEvent = null;
|
||||
public static Method onTrackballEvent = null;
|
||||
public static Method onActivityResult = null;
|
||||
public static Method onCreate = null;
|
||||
public static Method onKeyLongPress = null;
|
||||
public static Method dispatchKeyShortcutEvent = null;
|
||||
public static Method onKeyShortcut = null;
|
||||
public static Method dispatchGenericMotionEvent = null;
|
||||
public static Method onGenericMotionEvent = null;
|
||||
public static Method onRequestPermissionsResult = null;
|
||||
private static String activityClassName;
|
||||
|
||||
public static void setQtContextDelegate(Class<?> clazz, Object listener) {
|
||||
m_delegateObject = listener;
|
||||
activityClassName = clazz.getCanonicalName();
|
||||
|
||||
ArrayList<Method> delegateMethods = new ArrayList<>();
|
||||
for (Method m : listener.getClass().getMethods()) {
|
||||
if (m.getDeclaringClass().getName().startsWith("org.qtproject.qt5.android")) {
|
||||
delegateMethods.add(m);
|
||||
}
|
||||
}
|
||||
|
||||
ArrayList<Field> applicationFields = new ArrayList<>();
|
||||
for (Field f : QtApplication.class.getFields()) {
|
||||
if (f.getDeclaringClass().getName().equals(QtApplication.class.getName())) {
|
||||
applicationFields.add(f);
|
||||
}
|
||||
}
|
||||
|
||||
for (Method delegateMethod : delegateMethods) {
|
||||
try {
|
||||
clazz.getDeclaredMethod(delegateMethod.getName(), delegateMethod.getParameterTypes());
|
||||
if (m_delegateMethods.containsKey(delegateMethod.getName())) {
|
||||
m_delegateMethods.get(delegateMethod.getName()).add(delegateMethod);
|
||||
} else {
|
||||
ArrayList<Method> delegateSet = new ArrayList<>();
|
||||
delegateSet.add(delegateMethod);
|
||||
m_delegateMethods.put(delegateMethod.getName(), delegateSet);
|
||||
}
|
||||
for (Field applicationField : applicationFields) {
|
||||
if (applicationField.getName().equals(delegateMethod.getName())) {
|
||||
try {
|
||||
applicationField.set(null, delegateMethod);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTerminate() {
|
||||
if (m_delegateObject != null && m_delegateMethods.containsKey("onTerminate")) {
|
||||
invokeDelegateMethod(m_delegateMethods.get("onTerminate").get(0));
|
||||
}
|
||||
super.onTerminate();
|
||||
}
|
||||
|
||||
static class InvokeResult {
|
||||
boolean invoked = false;
|
||||
Object methodReturns = null;
|
||||
}
|
||||
|
||||
private static int stackDeep = -1;
|
||||
|
||||
public static InvokeResult invokeDelegate(Object... args) {
|
||||
InvokeResult result = new InvokeResult();
|
||||
if (m_delegateObject == null) {
|
||||
return result;
|
||||
}
|
||||
StackTraceElement[] elements = Thread.currentThread().getStackTrace();
|
||||
if (-1 == stackDeep) {
|
||||
for (int it = 0; it < elements.length; it++) {
|
||||
if (elements[it].getClassName().equals(activityClassName)) {
|
||||
stackDeep = it;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (-1 == stackDeep) {
|
||||
return result;
|
||||
}
|
||||
|
||||
final String methodName = elements[stackDeep].getMethodName();
|
||||
if (!m_delegateMethods.containsKey(methodName)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
for (Method m : m_delegateMethods.get(methodName)) {
|
||||
if (m.getParameterTypes().length == args.length) {
|
||||
result.methodReturns = invokeDelegateMethod(m, args);
|
||||
result.invoked = true;
|
||||
return result;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public static Object invokeDelegateMethod(Method m, Object... args) {
|
||||
try {
|
||||
return m.invoke(m_delegateObject, args);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -1,93 +0,0 @@
|
|||
package org.saintandreas.testapp;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.opengl.GLSurfaceView;
|
||||
import android.os.Bundle;
|
||||
import android.view.View;
|
||||
|
||||
import com.google.vr.ndk.base.AndroidCompat;
|
||||
import com.google.vr.ndk.base.GvrLayout;
|
||||
|
||||
import javax.microedition.khronos.egl.EGLConfig;
|
||||
import javax.microedition.khronos.opengles.GL10;
|
||||
|
||||
public class MainActivity extends Activity {
|
||||
private final static int IMMERSIVE_STICKY_VIEW_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_STABLE |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
||||
|
||||
static {
|
||||
System.loadLibrary("gvr");
|
||||
System.loadLibrary("native-lib");
|
||||
}
|
||||
|
||||
private long nativeRenderer;
|
||||
private GLSurfaceView surfaceView;
|
||||
|
||||
private native long nativeCreateRenderer(ClassLoader appClassLoader, Context context);
|
||||
private native void nativeDestroyRenderer(long renderer);
|
||||
private native void nativeInitializeGl(long renderer);
|
||||
private native void nativeDrawFrame(long renderer);
|
||||
private native void nativeOnTriggerEvent(long renderer);
|
||||
private native void nativeOnPause(long renderer);
|
||||
private native void nativeOnResume(long renderer);
|
||||
|
||||
class NativeRenderer implements GLSurfaceView.Renderer {
|
||||
@Override public void onSurfaceCreated(GL10 gl, EGLConfig config) { nativeInitializeGl(nativeRenderer); }
|
||||
@Override public void onSurfaceChanged(GL10 gl, int width, int height) { }
|
||||
@Override public void onDrawFrame(GL10 gl) {
|
||||
nativeDrawFrame(nativeRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
setImmersiveSticky();
|
||||
getWindow()
|
||||
.getDecorView()
|
||||
.setOnSystemUiVisibilityChangeListener((int visibility)->{
|
||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) { setImmersiveSticky(); }
|
||||
});
|
||||
|
||||
nativeRenderer = nativeCreateRenderer(
|
||||
getClass().getClassLoader(),
|
||||
getApplicationContext());
|
||||
|
||||
surfaceView = new GLSurfaceView(this);
|
||||
surfaceView.setEGLContextClientVersion(3);
|
||||
surfaceView.setEGLConfigChooser(8, 8, 8, 0, 0, 0);
|
||||
surfaceView.setPreserveEGLContextOnPause(true);
|
||||
surfaceView.setRenderer(new NativeRenderer());
|
||||
setContentView(surfaceView);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
nativeDestroyRenderer(nativeRenderer);
|
||||
nativeRenderer = 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
surfaceView.queueEvent(()->nativeOnPause(nativeRenderer));
|
||||
surfaceView.onPause();
|
||||
super.onPause();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
surfaceView.onResume();
|
||||
surfaceView.queueEvent(()->nativeOnResume(nativeRenderer));
|
||||
}
|
||||
|
||||
private void setImmersiveSticky() {
|
||||
getWindow().getDecorView().setSystemUiVisibility(IMMERSIVE_STICKY_VIEW_FLAGS);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
|
||||
</vector>
|
BIN
android/app/src/main/res/drawable/icon.png
Normal file
After Width: | Height: | Size: 9.7 KiB |
34
android/app/src/main/res/layout/activity_web_view.xml
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<Toolbar
|
||||
android:id="@+id/toolbar_actionbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?android:attr/actionBarSize"
|
||||
android:layout_alignParentTop="true"
|
||||
style="@android:style/Widget.Material.ActionBar"
|
||||
android:titleTextAppearance="@style/ActionBarTitleStyle"
|
||||
android:subtitleTextAppearance="@style/ActionBarSubtitleStyle"
|
||||
android:navigationIcon="@drawable/ic_close_black_24dp"
|
||||
android:contentInsetStart="0dp"
|
||||
android:contentInsetStartWithNavigation="0dp"
|
||||
android:title="">
|
||||
</Toolbar>
|
||||
|
||||
<WebView
|
||||
android:id="@+id/web_view"
|
||||
android:layout_below="@id/toolbar_actionbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
<ProgressBar
|
||||
android:id="@+id/toolbarProgressBar"
|
||||
android:layout_below="@id/toolbar_actionbar"
|
||||
style="?android:attr/progressBarStyleHorizontal"
|
||||
android:visibility="gone"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="3dp"
|
||||
android:indeterminate="false"
|
||||
android:padding="0dp" />
|
||||
</RelativeLayout>
|
10
android/app/src/main/res/menu/web_view_menu.xml
Normal file
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item
|
||||
android:id="@+id/action_share"
|
||||
android:title="@string/web_view_action_share"
|
||||
android:showAsAction="never"/>
|
||||
<item android:id="@+id/action_browser"
|
||||
android:title="@string/web_view_action_open_in_browser"
|
||||
android:showAsAction="never"/>
|
||||
</menu>
|
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 5.1 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.9 KiB |
Before Width: | Height: | Size: 2.2 KiB After Width: | Height: | Size: 4.2 KiB |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 4.3 KiB |
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 5.8 KiB |
Before Width: | Height: | Size: 6 KiB After Width: | Height: | Size: 5.5 KiB |
Before Width: | Height: | Size: 7.5 KiB After Width: | Height: | Size: 7.3 KiB |
Before Width: | Height: | Size: 9.8 KiB After Width: | Height: | Size: 6.7 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 8.9 KiB |
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 8.2 KiB |
12
android/app/src/main/res/values/dimens.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
|
||||
<!-- Hack to have a desired text size for both orientations (default portrait is too big) -->
|
||||
<!-- Default text size for action bar title.-->
|
||||
<dimen name="text_size_title_material_toolbar">14dp</dimen>
|
||||
<!-- Default text size for action bar subtitle.-->
|
||||
<dimen name="text_size_subtitle_material_toolbar">12dp</dimen>
|
||||
</resources>
|
|
@ -1,3 +1,8 @@
|
|||
<resources>
|
||||
<string name="app_name">TestApp</string>
|
||||
<string name="app_name" translatable="false">Interface</string>
|
||||
<string name="web_view_action_open_in_browser" translatable="false">Open in browser</string>
|
||||
<string name="web_view_action_share" translatable="false">Share link</string>
|
||||
<string name="web_view_action_share_subject" translatable="false">Shared a link</string>
|
||||
<string name="web_view_action_share_chooser" translatable="false">Share link</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -11,5 +11,15 @@
|
|||
<!--item name="android:background">@color/white_opaque</item-->
|
||||
</style>
|
||||
|
||||
|
||||
<!-- Overriding text size so it's not so big in portrait -->
|
||||
<style name="ActionBarTitleStyle"
|
||||
parent="@android:style/TextAppearance.Material.Widget.ActionBar.Title">
|
||||
<item name="android:textSize">@dimen/text_size_title_material_toolbar</item>
|
||||
</style>
|
||||
<style name="ActionBarSubtitleStyle"
|
||||
parent="@android:style/TextAppearance.Material.Widget.ActionBar.Subtitle">
|
||||
<item name="android:textSize">@dimen/text_size_subtitle_material_toolbar</item>
|
||||
</style>
|
||||
|
||||
|
||||
</resources>
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
import de.undercouch.gradle.tasks.download.Download
|
||||
import de.undercouch.gradle.tasks.download.Verify
|
||||
import groovy.io.FileType
|
||||
import groovy.json.JsonSlurper
|
||||
import groovy.xml.XmlUtil
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
|
||||
import java.util.regex.Matcher
|
||||
import java.util.regex.Pattern
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
|
@ -10,6 +20,7 @@ buildscript {
|
|||
|
||||
plugins {
|
||||
id 'de.undercouch.download' version '3.3.0'
|
||||
id "cz.malohlava" version "1.0.3"
|
||||
}
|
||||
|
||||
allprojects {
|
||||
|
@ -19,65 +30,306 @@ allprojects {
|
|||
}
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
ext {
|
||||
RELEASE_NUMBER = project.hasProperty('RELEASE_NUMBER') ? project.getProperty('RELEASE_NUMBER') : '0'
|
||||
RELEASE_TYPE = project.hasProperty('RELEASE_TYPE') ? project.getProperty('RELEASE_TYPE') : 'DEV'
|
||||
BUILD_BRANCH = project.hasProperty('BUILD_BRANCH') ? project.getProperty('BUILD_BRANCH') : ''
|
||||
EXEC_SUFFIX = Os.isFamily(Os.FAMILY_WINDOWS) ? '.exe' : ''
|
||||
QT5_DEPS = [
|
||||
'Qt5Concurrent',
|
||||
'Qt5Core',
|
||||
'Qt5Gui',
|
||||
'Qt5Multimedia',
|
||||
'Qt5Network',
|
||||
'Qt5OpenGL',
|
||||
'Qt5Qml',
|
||||
'Qt5Quick',
|
||||
'Qt5QuickControls2',
|
||||
'Qt5QuickTemplates2',
|
||||
'Qt5Script',
|
||||
'Qt5ScriptTools',
|
||||
'Qt5Svg',
|
||||
'Qt5WebChannel',
|
||||
'Qt5WebSockets',
|
||||
'Qt5Widgets',
|
||||
'Qt5XmlPatterns',
|
||||
// Android specific
|
||||
'Qt5AndroidExtras',
|
||||
'Qt5WebView',
|
||||
]
|
||||
}
|
||||
|
||||
def baseFolder = new File(HIFI_ANDROID_PRECOMPILED)
|
||||
def jniFolder = new File('app/src/main/jniLibs/arm64-v8a')
|
||||
|
||||
import org.apache.tools.ant.taskdefs.condition.Os
|
||||
|
||||
def appDir = new File(projectDir, 'app')
|
||||
def jniFolder = new File(appDir, 'src/main/jniLibs/arm64-v8a')
|
||||
def baseUrl = 'https://hifi-public.s3.amazonaws.com/austin/android/'
|
||||
def qtFile='qt-5.9.3_linux_armv8-libcpp.tgz'
|
||||
def qtChecksum='547da3547d5690144e23d6504c6d6e91'
|
||||
|
||||
def qtFile='qt-5.9.3_linux_armv8-libcpp_openssl.tgz'
|
||||
def qtChecksum='04599670ccca84bd2b15f6915568eb2d'
|
||||
def qtVersionId='PeoqzN31n.YvLfs9JE2SgHgZ4.IaKAlt'
|
||||
if (Os.isFamily(Os.FAMILY_MAC)) {
|
||||
qtFile = 'qt-5.9.3_osx_armv8-libcpp.tgz'
|
||||
qtChecksum='6fa3e068cfdee863fc909b294a3a0cc6'
|
||||
qtFile = 'qt-5.9.3_osx_armv8-libcpp_openssl.tgz'
|
||||
qtChecksum='4b02de9d67d6bfb202355a808d2d9c59'
|
||||
qtVersionId='HygCmtMLPYioyil0DfXckGVzhw2SXZA9'
|
||||
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
qtFile = 'qt-5.9.3_win_armv8-libcpp.tgz'
|
||||
qtChecksum='3a757378a7e9dbbfc662177e0eb46408'
|
||||
qtFile = 'qt-5.9.3_win_armv8-libcpp_openssl.tgz'
|
||||
qtChecksum='a93d22c0c59aa112fda18c4c6d157d17'
|
||||
qtVersionId='0Bl9NSUWb5CBKLT_NXaxTt75SNBBZ9sB'
|
||||
}
|
||||
|
||||
def packages = [
|
||||
qt: [
|
||||
file: qtFile,
|
||||
versionId: qtVersionId,
|
||||
checksum: qtChecksum,
|
||||
sharedLibFolder: '',
|
||||
includeLibs: ['lib/*.so', 'plugins/*/*.so']
|
||||
],
|
||||
bullet: [
|
||||
file: 'bullet-2.83_armv8-libcpp.tgz',
|
||||
checksum: '2c558d604fce337f5eba3eb7ec1252fd'
|
||||
versionId: 'ljb7v.1IjVRqyopUKVDbVnLA4z88J8Eo',
|
||||
checksum: '2c558d604fce337f5eba3eb7ec1252fd',
|
||||
],
|
||||
draco: [
|
||||
file: 'draco_armv8-libcpp.tgz',
|
||||
checksum: '617a80d213a5ec69fbfa21a1f2f738cd'
|
||||
versionId: 'cA3tVJSmkvb1naA3l6D_Jv2Noh.4yc4m',
|
||||
checksum: '617a80d213a5ec69fbfa21a1f2f738cd',
|
||||
],
|
||||
glad: [
|
||||
file: 'glad_armv8-libcpp.zip',
|
||||
versionId: 'Q9szthzeye8fFyAA.cY26Lgn2B8kezEE',
|
||||
checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449',
|
||||
],
|
||||
glm: [
|
||||
file: 'glm-0.9.8.tgz',
|
||||
versionId: 'BlkJNwaYV2Gfy5XwMeU7K0uzPDRKFMt2',
|
||||
checksum: 'd2b42cee31d2bc17bab6ce69e6b3f30a',
|
||||
],
|
||||
gvr: [
|
||||
file: 'gvrsdk_v1.101.0.tgz',
|
||||
checksum: '57fd02baa069176ba18597a29b6b4fc7'
|
||||
versionId: 'UTberAIFraEfF9IVjoV66u1DTPTopgeY',
|
||||
checksum: '57fd02baa069176ba18597a29b6b4fc7',
|
||||
],
|
||||
openssl: [
|
||||
file: 'openssl-1.1.0g_armv8.tgz',
|
||||
checksum: 'cabb681fbccd79594f65fcc266e02f32'
|
||||
versionId: 'DmahmSGFS4ltpHyTdyQvv35WOeUOiib9',
|
||||
checksum: 'cabb681fbccd79594f65fcc266e02f32',
|
||||
],
|
||||
polyvox: [
|
||||
file: 'polyvox_armv8-libcpp.tgz',
|
||||
checksum: '5c918288741ee754c16aeb12bb46b9e1',
|
||||
versionId: 'LDJtzMTvdm4SAc2KYg8Cg6uwWk4Vq3e3',
|
||||
checksum: '349ad5b72aaf2749ca95d847e60c5314',
|
||||
sharedLibFolder: 'lib',
|
||||
includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so']
|
||||
includeLibs: ['Release/libPolyVoxCore.so', 'libPolyVoxUtil.so'],
|
||||
],
|
||||
tbb: [
|
||||
file: 'tbb-2018_U1_armv8_libcpp.tgz',
|
||||
versionId: 'YZliDD8.Menh1IVXKEuLPeO3xAjJ1UdF',
|
||||
checksum: '20768f298f53b195e71b414b0ae240c4',
|
||||
sharedLibFolder: 'lib/release',
|
||||
includeLibs: ['libtbb.so', 'libtbbmalloc.so']
|
||||
includeLibs: ['libtbb.so', 'libtbbmalloc.so'],
|
||||
]
|
||||
]
|
||||
|
||||
|
||||
def scribeLocalFile='scribe' + EXEC_SUFFIX
|
||||
|
||||
def scribeFile='scribe_linux_x86_64'
|
||||
def scribeChecksum='ca4b904f52f4f993c29175ba96798fa6'
|
||||
def scribeVersion='wgpf4dB2Ltzg4Lb2jJ4nPFsHoDkmK_OO'
|
||||
if (Os.isFamily(Os.FAMILY_MAC)) {
|
||||
scribeFile = 'scribe_osx_x86_64'
|
||||
scribeChecksum='72db9d32d4e1e50add755570ac5eb749'
|
||||
scribeVersion='o_NbPrktzEYtBkQf3Tn7zc1nZWzM52w6'
|
||||
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
scribeFile = 'scribe_win32_x86_64.exe'
|
||||
scribeChecksum='678e43d290c90fda670c6fefe038a06d'
|
||||
scribeVersion='GCCJxlmd2irvNOFWfZR0U1UCLHndHQrC'
|
||||
}
|
||||
|
||||
def options = [
|
||||
files: new TreeSet<File>(),
|
||||
features: new HashSet<String>(),
|
||||
permissions: new HashSet<String>()
|
||||
]
|
||||
|
||||
def qmlRoot = new File(HIFI_ANDROID_PRECOMPILED, 'qt')
|
||||
|
||||
def captureOutput = { String command, List<String> commandArgs ->
|
||||
def result
|
||||
new ByteArrayOutputStream().withStream { os ->
|
||||
def execResult = exec {
|
||||
executable = command
|
||||
args = commandArgs
|
||||
standardOutput = os
|
||||
errorOutput = new ByteArrayOutputStream()
|
||||
}
|
||||
result = os.toString()
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
def relativize = { File root, File absolute ->
|
||||
def relativeURI = root.toURI().relativize(absolute.toURI())
|
||||
return new File(relativeURI.toString())
|
||||
}
|
||||
|
||||
def scanQmlImports = { File qmlRootPath ->
|
||||
def qmlImportCommandFile = new File(qmlRoot, 'bin/qmlimportscanner' + EXEC_SUFFIX)
|
||||
if (!qmlImportCommandFile.exists()) {
|
||||
throw new GradleException('Unable to find required qmlimportscanner executable at ' + qmlImportCommandFile.parent.toString())
|
||||
}
|
||||
|
||||
def command = qmlImportCommandFile.absolutePath
|
||||
def args = [
|
||||
'-rootPath', qmlRootPath.absolutePath,
|
||||
'-importPath', "${qmlRoot.absolutePath}/qml"
|
||||
]
|
||||
|
||||
def commandResult = captureOutput(command, args)
|
||||
new JsonSlurper().parseText(commandResult).each {
|
||||
if (!it.containsKey('path')) {
|
||||
println "Warning: QML import could not be resolved in any of the import paths: ${it.name}"
|
||||
return
|
||||
}
|
||||
def file = new File(it.path)
|
||||
// Ignore non-existent files
|
||||
if (!file.exists()) {
|
||||
return
|
||||
}
|
||||
// Ignore files in the import path
|
||||
if (file.canonicalPath.startsWith(qmlRootPath.canonicalPath)) {
|
||||
return
|
||||
}
|
||||
if (file.isFile()) {
|
||||
options.files.add(file)
|
||||
} else {
|
||||
file.eachFileRecurse(FileType.FILES, {
|
||||
options.files.add(it)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def parseQtDependencies = { List qtLibs ->
|
||||
qtLibs.each({
|
||||
def libFile = new File(qmlRoot, "lib/lib${it}.so")
|
||||
options.files.add(libFile)
|
||||
|
||||
def androidDeps = new File(qmlRoot, "lib/${it}-android-dependencies.xml")
|
||||
if (!libFile.exists()) return
|
||||
if (!androidDeps.exists()) return
|
||||
|
||||
new XmlSlurper().parse(androidDeps).dependencies.lib.depends.'*'.each{ node ->
|
||||
switch (node.name()) {
|
||||
case 'lib':
|
||||
case 'bundled':
|
||||
def relativeFilename = node.@file.toString()
|
||||
|
||||
// Special case, since this is handled by qmlimportscanner instead
|
||||
if (relativeFilename.startsWith('qml'))
|
||||
return
|
||||
|
||||
def file = new File(qmlRoot, relativeFilename)
|
||||
|
||||
if (!file.exists())
|
||||
return
|
||||
|
||||
if (file.isFile()) {
|
||||
options.files.add(file)
|
||||
} else {
|
||||
file.eachFileRecurse(FileType.FILES, { options.files.add(it) })
|
||||
}
|
||||
break
|
||||
|
||||
|
||||
case 'jar':
|
||||
if (node.@bundling == "1") {
|
||||
def jar = new File(qmlRoot, node.@file.toString())
|
||||
if (!jar.exists()) {
|
||||
throw new GradleException('Unable to find required JAR ' + jar.path)
|
||||
}
|
||||
options.files.add(jar)
|
||||
}
|
||||
break
|
||||
|
||||
case 'permission':
|
||||
options.permissions.add(node.@name)
|
||||
break
|
||||
|
||||
case 'feature':
|
||||
options.features.add(node.@name)
|
||||
break
|
||||
|
||||
default:
|
||||
throw new GradleException('Unhandled Android Dependency node ' + node.name())
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
def generateLibsXml = {
|
||||
def libDestinationDirectory = jniFolder
|
||||
def jarDestinationDirectory = new File(appDir, 'libs')
|
||||
def assetDestinationDirectory = new File(appDir, 'src/main/assets/--Added-by-androiddeployqt--');
|
||||
def libsXmlFile = new File(appDir, 'src/main/res/values/libs.xml')
|
||||
def libPrefix = 'lib' + File.separator
|
||||
def jarPrefix = 'jar' + File.separator
|
||||
|
||||
def xmlParser = new XmlParser()
|
||||
def libsXmlRoot = xmlParser.parseText('<?xml version="1.0" encoding="UTF-8"?><resources/>')
|
||||
def qtLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'qt_libs'])
|
||||
def bundledLibsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_lib'])
|
||||
def bundledAssetsNode = xmlParser.createNode(libsXmlRoot, 'array', [name: 'bundled_in_assets'])
|
||||
|
||||
options.files.each {
|
||||
def sourceFile = it
|
||||
if (!sourceFile.exists()) {
|
||||
throw new GradleException("Unable to find dependency file " + sourceFile.toString())
|
||||
}
|
||||
|
||||
def relativePath = relativize( qmlRoot, sourceFile ).toString()
|
||||
def destinationFile
|
||||
if (relativePath.endsWith('.so')) {
|
||||
def garbledFileName
|
||||
if (relativePath.startsWith(libPrefix)) {
|
||||
garbledFileName = relativePath.substring(libPrefix.size())
|
||||
Pattern p = ~/lib(Qt5.*).so/
|
||||
Matcher m = p.matcher(garbledFileName)
|
||||
assert m.matches()
|
||||
def libName = m.group(1)
|
||||
xmlParser.createNode(qtLibsNode, 'item', [:]).setValue(libName)
|
||||
} else {
|
||||
garbledFileName = 'lib' + relativePath.replace(File.separator, '_'[0])
|
||||
xmlParser.createNode(bundledLibsNode, 'item', [:]).setValue("${garbledFileName}:${relativePath}".replace(File.separator, '/'))
|
||||
}
|
||||
destinationFile = new File(libDestinationDirectory, garbledFileName)
|
||||
} else if (relativePath.startsWith('jar')) {
|
||||
destinationFile = new File(jarDestinationDirectory, relativePath.substring(jarPrefix.size()))
|
||||
} else {
|
||||
xmlParser.createNode(bundledAssetsNode, 'item', [:]).setValue("--Added-by-androiddeployqt--/${relativePath}:${relativePath}".replace(File.separator, '/'))
|
||||
destinationFile = new File(assetDestinationDirectory, relativePath)
|
||||
}
|
||||
|
||||
copy { from sourceFile; into destinationFile.parent; rename(sourceFile.name, destinationFile.name) }
|
||||
assert destinationFile.exists() && destinationFile.isFile()
|
||||
}
|
||||
def xml = XmlUtil.serialize(libsXmlRoot)
|
||||
new FileWriter(libsXmlFile).withPrintWriter { writer ->
|
||||
writer.write(xml)
|
||||
}
|
||||
}
|
||||
|
||||
task downloadDependencies {
|
||||
doLast {
|
||||
packages.each { entry ->
|
||||
def filename = entry.value['file'];
|
||||
def url = baseUrl + filename;
|
||||
if (entry.value.containsKey('versionId')) {
|
||||
url = url + '?versionId=' + entry.value['versionId']
|
||||
}
|
||||
download {
|
||||
src url
|
||||
dest new File(baseFolder, filename)
|
||||
|
@ -87,8 +339,6 @@ task downloadDependencies {
|
|||
}
|
||||
}
|
||||
|
||||
import de.undercouch.gradle.tasks.download.Verify
|
||||
|
||||
task verifyQt(type: Verify) { def p = packages['qt']; src new File(baseFolder, p['file']); checksum p['checksum']; }
|
||||
task verifyBullet(type: Verify) { def p = packages['bullet']; src new File(baseFolder, p['file']); checksum p['checksum'] }
|
||||
task verifyDraco(type: Verify) { def p = packages['draco']; src new File(baseFolder, p['file']); checksum p['checksum'] }
|
||||
|
@ -109,19 +359,26 @@ verifyDependencyDownloads.dependsOn verifyTBB
|
|||
task extractDependencies(dependsOn: verifyDependencyDownloads) {
|
||||
doLast {
|
||||
packages.each { entry ->
|
||||
def folder = entry.key;
|
||||
def filename = entry.value['file'];
|
||||
def folder = entry.key
|
||||
def filename = entry.value['file']
|
||||
def localFile = new File(HIFI_ANDROID_PRECOMPILED, filename)
|
||||
def localFolder = new File(HIFI_ANDROID_PRECOMPILED, folder)
|
||||
def fileTree;
|
||||
if (filename.endsWith('zip')) {
|
||||
fileTree = zipTree(localFile)
|
||||
} else {
|
||||
fileTree = tarTree(resources.gzip(localFile))
|
||||
}
|
||||
copy {
|
||||
from tarTree(resources.gzip(localFile))
|
||||
from fileTree
|
||||
into localFolder
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task copyDependencies(dependsOn: extractDependencies) {
|
||||
// Copies the non Qt dependencies. Qt dependencies (primary libraries and plugins) are handled by the qtBundle task
|
||||
task copyDependencies(dependsOn: [ extractDependencies ]) {
|
||||
doLast {
|
||||
packages.each { entry ->
|
||||
def packageName = entry.key
|
||||
|
@ -132,33 +389,19 @@ task copyDependencies(dependsOn: extractDependencies) {
|
|||
if (currentPackage.containsKey('includeLibs')) {
|
||||
currentPackage['includeLibs'].each { includeSpec -> tree.include includeSpec }
|
||||
}
|
||||
tree.visit { element ->
|
||||
tree.visit { element ->
|
||||
if (!element.file.isDirectory()) {
|
||||
println "Copying " + element.file + " to " + jniFolder
|
||||
copy { from element.file; into jniFolder }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def scribeFile='scribe_linux_x86_64'
|
||||
def scribeLocalFile='scribe'
|
||||
def scribeChecksum='c98678d9726bd8bbf1bab792acf3ff6c'
|
||||
if (Os.isFamily(Os.FAMILY_MAC)) {
|
||||
scribeFile = 'scribe_osx_x86_64'
|
||||
scribeChecksum='a137ad62c1bf7cca739da219544a9a16'
|
||||
} else if (Os.isFamily(Os.FAMILY_WINDOWS)) {
|
||||
scribeFile = 'scribe_win32_x86_64.exe'
|
||||
scribeLocalFile = 'scribe.exe'
|
||||
scribeChecksum='75c2ce9ed45d17de375e3988bfaba816'
|
||||
|
||||
}
|
||||
|
||||
import de.undercouch.gradle.tasks.download.Download
|
||||
|
||||
task downloadScribe(type: Download) {
|
||||
src baseUrl + scribeFile
|
||||
src baseUrl + scribeFile + '?versionId=' + scribeVersion
|
||||
dest new File(baseFolder, scribeLocalFile)
|
||||
onlyIfNewer true
|
||||
}
|
||||
|
@ -200,17 +443,153 @@ task extractGvrBinaries(dependsOn: extractDependencies) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
task setupDependencies(dependsOn: [setupScribe, copyDependencies, extractGvrBinaries]) {
|
||||
def generateAssetsFileList = {
|
||||
def assetsPath = "${appDir}/src/main/assets/"
|
||||
def addedByAndroidDeployQtName = "--Added-by-androiddeployqt--/"
|
||||
|
||||
def addedByAndroidDeployQtPath = assetsPath + addedByAndroidDeployQtName
|
||||
|
||||
def addedByAndroidDeployQt = new File(addedByAndroidDeployQtPath)
|
||||
if (!addedByAndroidDeployQt.exists() && !addedByAndroidDeployQt.mkdirs()) {
|
||||
throw new GradleScriptException("Failed to create directory " + addedByAndroidDeployQtPath, null);
|
||||
}
|
||||
def outputFilename = "/qt_cache_pregenerated_file_list"
|
||||
def outputFile = new File(addedByAndroidDeployQtPath + outputFilename);
|
||||
Map<String, List<String>> directoryContents = new TreeMap<>();
|
||||
|
||||
def dir = new File(assetsPath)
|
||||
dir.eachFileRecurse (FileType.ANY) { file ->
|
||||
|
||||
def name = file.path.substring(assetsPath.length())
|
||||
int slashIndex = name.lastIndexOf('/')
|
||||
def pathName = slashIndex >= 0 ? name.substring(0, slashIndex) : "/"
|
||||
def fileName = slashIndex >= 0 ? name.substring(pathName.length() + 1) : name
|
||||
if (!fileName.isEmpty() && file.isDirectory() && !fileName.endsWith("/")) {
|
||||
fileName += "/"
|
||||
}
|
||||
|
||||
if (!directoryContents.containsKey(pathName)) {
|
||||
directoryContents[pathName] = new ArrayList<String>()
|
||||
}
|
||||
if (!fileName.isEmpty()) {
|
||||
directoryContents[pathName].add(fileName);
|
||||
}
|
||||
}
|
||||
DataOutputStream fos = new DataOutputStream(new FileOutputStream(outputFile));
|
||||
for (Map.Entry<String, List<String>> e: directoryContents.entrySet()) {
|
||||
def entryList = e.getValue()
|
||||
fos.writeInt(e.key.length()*2); // 2 bytes per char
|
||||
fos.writeChars(e.key);
|
||||
fos.writeInt(entryList.size());
|
||||
for (String entry: entryList) {
|
||||
fos.writeInt(entry.length()*2);
|
||||
fos.writeChars(entry);
|
||||
}
|
||||
}
|
||||
fos.close();
|
||||
}
|
||||
|
||||
|
||||
// Copy required Qt main libraries and required plugins based on the predefined list here
|
||||
// FIXME eventually we would like to use the readelf functionality to automatically detect dependencies
|
||||
// from our built applications and use that during the full build process. However doing so would mean
|
||||
// hooking existing Android build tasks since the output from the qtBundle logic adds JNI libs, asset
|
||||
// files and resources files and potentially modifies the AndroidManifest.xml
|
||||
task qtBundle {
|
||||
doLast {
|
||||
parseQtDependencies(QT5_DEPS)
|
||||
def qmlImportFolder = new File("${appDir}/../../interface/resources/qml/")
|
||||
//def qmlImportFolder = new File("${projectDir}/app/src/main/cpp")
|
||||
scanQmlImports(qmlImportFolder)
|
||||
generateLibsXml()
|
||||
generateAssetsFileList()
|
||||
}
|
||||
}
|
||||
|
||||
task setupDependencies(dependsOn: [setupScribe, copyDependencies, extractGvrBinaries, qtBundle]) { }
|
||||
|
||||
task cleanDependencies(type: Delete) {
|
||||
delete HIFI_ANDROID_PRECOMPILED
|
||||
delete 'app/src/main/jniLibs/arm64-v8a'
|
||||
delete 'app/src/main/assets/--Added-by-androiddeployqt--'
|
||||
delete 'app/src/main/res/values/libs.xml'
|
||||
}
|
||||
|
||||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
|
||||
|
||||
// FIXME this code is prototyping the desired functionality for doing build time binary dependency resolution.
|
||||
// See the comment on the qtBundle task above
|
||||
/*
|
||||
// FIXME derive the path from the gradle environment
|
||||
def toolchain = [
|
||||
version: '4.9',
|
||||
prefix: 'aarch64-linux-android',
|
||||
// FIXME derive from the host OS
|
||||
ndkHost: 'windows-x86_64',
|
||||
]
|
||||
|
||||
def findDependentLibrary = { String name ->
|
||||
def libFolders = [
|
||||
new File(qmlRoot, 'lib'),
|
||||
new File("${HIFI_ANDROID_PRECOMPILED}/tbb/lib/release"),
|
||||
new File("${HIFI_ANDROID_PRECOMPILED}/polyvox/lib/Release"),
|
||||
new File("${HIFI_ANDROID_PRECOMPILED}/polyvox/lib/"),
|
||||
new File("${HIFI_ANDROID_PRECOMPILED}/gvr/gvr-android-sdk-1.101.0/libraries"),
|
||||
]
|
||||
|
||||
}
|
||||
|
||||
def readElfBinary = new File(android.ndkDirectory, "/toolchains/${toolchain.prefix}-${toolchain.version}/prebuilt/${toolchain.ndkHost}/bin/${toolchain.prefix}-readelf${EXEC_SUFFIX}")
|
||||
|
||||
def getDependencies = { File elfBinary ->
|
||||
Set<File> result = []
|
||||
Queue<File> pending = new LinkedList<>()
|
||||
pending.add(elfBinary)
|
||||
Set<File> scanned = []
|
||||
|
||||
Pattern p = ~/.*\(NEEDED\).*Shared library: \[(.*\.so)\]/
|
||||
while (!pending.isEmpty()) {
|
||||
File current = pending.remove()
|
||||
if (scanned.contains(current)) {
|
||||
continue
|
||||
}
|
||||
scanned.add(current)
|
||||
def command = "${readElfBinary} -d -W ${current.absolutePath}"
|
||||
captureOutput(command).split('[\r\n]').each { line ->
|
||||
Matcher m = p.matcher(line)
|
||||
if (!m.matches()) {
|
||||
return
|
||||
}
|
||||
def libName = m.group(1)
|
||||
def file = new File(qmlRoot, "lib/${libName}")
|
||||
if (file.exists()) {
|
||||
result.add(file)
|
||||
pending.add(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
task testElf (dependsOn: 'externalNativeBuildDebug') {
|
||||
doLast {
|
||||
def appLibraries = new HashSet<File>()
|
||||
def qtDependencies = new HashSet<File>()
|
||||
externalNativeBuildDebug.nativeBuildConfigurationsJsons.each { File file ->
|
||||
def json = new JsonSlurper().parse(file)
|
||||
json.libraries.each { node ->
|
||||
def outputFile = new File(node.value.output)
|
||||
if (outputFile.canonicalPath.startsWith(projectDir.canonicalPath)) {
|
||||
appLibraries.add(outputFile)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
appLibraries.each { File file ->
|
||||
println getDependencies(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
93
android/build_recipes.md
Normal file
|
@ -0,0 +1,93 @@
|
|||
Different libraries require different mechanism for building. Some are easiest with the standalone toolchain. Some are easiest with the ndk-build tool. Some can rely on CMake to do the right thing.
|
||||
|
||||
## Setup
|
||||
|
||||
### Android build environment
|
||||
|
||||
You need the Android NDK and SDK. The easiest way to get these is to download Android Studio for your platform and use the SDK manager in Studio to install the items. In particular you will need Android API levels 24 and 26, as well as the NDK, CMake, and LLDB. You should be using NDK version 16 or higher.
|
||||
|
||||
The Studio installation can install the SDK wherver you like. This guide assumes you install it to `$HOME/Android/SDK`. It will install the NDK inside the SDK in a folder named `ndk-bundle` but for convenience we will assume a symlink from `$HOME/Android/NDK` to the actual NDK folder exists
|
||||
|
||||
Additionally, some of the tools require a standalone toolchain in order to build. From the NDK build/tools directory you can execute the following command
|
||||
|
||||
`./make-standalone-toolchain.sh --arch=arm64 --platform=android-24 --install-dir=$HOME/Android/arm64_toolchain`
|
||||
|
||||
This will create the toolchain and install it in `$HOME/Android/arm64_toolchain`
|
||||
|
||||
When doing a build that relies on the toolchain you can execute the following commands to enable it
|
||||
|
||||
```
|
||||
target_host=aarch64-linux-android
|
||||
export PATH=$PATH:$HOME/Android/arm64_toolchain/bin
|
||||
export AR=$target_host-ar
|
||||
export AS=$target_host-as
|
||||
export CC=$target_host-gcc
|
||||
export CXX=$target_host-g++
|
||||
export LD=$target_host-ld
|
||||
export STRIP=$target_host-strip
|
||||
export CFLAGS="-fPIE -fPIC"
|
||||
export LDFLAGS="-pie"
|
||||
```
|
||||
|
||||
|
||||
|
||||
## Qt
|
||||
|
||||
### Windows host
|
||||
|
||||
* Install the Android SDK
|
||||
* Install the Android NDK
|
||||
* Install Git for Windows
|
||||
* Install Strawberry Perl
|
||||
* Install Java 8 (Do NOT use Java 9, it will fail)
|
||||
* Install Python 3.6 for Windows
|
||||
* Open a Git Bash command prompt
|
||||
* Ensure the following commands are visible in the path with `which <command>`
|
||||
* gcc
|
||||
* javac
|
||||
* python
|
||||
* gmake
|
||||
* If any of them fail, fix your path and restart the bash prompt
|
||||
* Fetch the pre-built OpenSSL binaries for Android/iOS from here: https://github.com/leenjewel/openssl_for_ios_and_android/releases
|
||||
* Grab the latest release of the 1.0.2 series
|
||||
* Open the archive and extract the `/android/openssl-arm64-v8a` folder
|
||||
|
||||
### All platforms
|
||||
|
||||
* Download the Qt sources
|
||||
* `git clone git://code.qt.io/qt/qt5.git`
|
||||
* `cd qt5`
|
||||
* `perl init-repository`
|
||||
* `git checkout v5.9.3`
|
||||
* `git submodule update --recursive`
|
||||
* `cd ..`
|
||||
* Create a build directory with the command `mkdir qt5build`
|
||||
* Configure the Qt5 build with the command `../qt5/configure -opensource -confirm-license -xplatform android-clang --disable-rpath -nomake tests -nomake examples -skip qttranslations -skip qtserialport -skip qt3d -skip qtwebengine -skip qtlocation -skip qtwayland -skip qtsensors -skip qtgamepad -skip qtgamepad -skip qtspeech -skip qtcharts -skip qtx11extras -skip qtmacextras -skip qtvirtualkeyboard -skip qtpurchasing -skip qtdatavis3d -android-toolchain-version 4.9 -android-ndk $HOME/Android/NDK -android-arch arm64-v8a -no-warnings-are-errors -android-ndk-platform android-24 -v -android-ndk-host windows-x86_64 -platform win32-g++ -prefix C:/qt5build_debug -android-sdk $HOME/Android/SDK -c++std c++14 -openssl-linked -L<PATH_TO_SSL>/lib -I<PATH_TO_SSL>/include`
|
||||
* Some of those entries must be customized depending on platform.
|
||||
* `-platform win32-g++`
|
||||
* `-android-ndk-host windows-x86_64`
|
||||
* `-prefix C:/qt5build_debug`
|
||||
|
||||
|
||||
|
||||
## TBB
|
||||
|
||||
Use the ndk-build tool
|
||||
|
||||
ndk-build tbb tbbmalloc target=android arch=ia32 tbb_os=windows ndk_version=16
|
||||
|
||||
## OpenSSL
|
||||
|
||||
Use a standalone toolchain
|
||||
|
||||
* Grab the latest 1.1.0x series source from https://github.com/openssl/openssl/releases
|
||||
* Follow the NDK guidelines for building a standalone toolchain for aarch64
|
||||
* Use the following script to configure and build OpenSSL
|
||||
* Enable the standalone toolchain with the export commands described above
|
||||
* Configure SSL with the command `./Configure android64-aarch64 no-asm no-ssl2 no-ssl3 no-comp no-hw no-engine --prefix=$HOME/Android/openssl_1.1.0g`
|
||||
* Build and install SSL with the command `make depend && make && make install`
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
org.gradle.jvmargs=-Xmx1536m
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
|
@ -1,6 +1,6 @@
|
|||
set(TARGET_NAME assignment-client)
|
||||
|
||||
setup_hifi_project(Core Gui Network Script Quick Widgets WebSockets)
|
||||
setup_hifi_project(Core Gui Network Script Quick WebSockets)
|
||||
|
||||
# Fix up the rpath so macdeployqt works
|
||||
if (APPLE)
|
||||
|
@ -11,11 +11,27 @@ setup_memory_debugger()
|
|||
|
||||
# link in the shared libraries
|
||||
link_hifi_libraries(
|
||||
audio avatars octree gpu model fbx entities
|
||||
audio avatars octree gpu graphics fbx entities
|
||||
networking animation recording shared script-engine embedded-webserver
|
||||
controllers physics plugins midi baking image
|
||||
controllers physics plugins midi image
|
||||
)
|
||||
|
||||
add_dependencies(${TARGET_NAME} oven)
|
||||
|
||||
if (WIN32)
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
$<TARGET_FILE_DIR:oven>
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>)
|
||||
else()
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E create_symlink
|
||||
$<TARGET_FILE:oven>
|
||||
$<TARGET_FILE_DIR:${TARGET_NAME}>/oven)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
package_libraries_for_deployment()
|
||||
endif()
|
||||
|
|
|
@ -94,7 +94,6 @@ Agent::Agent(ReceivedMessage& message) :
|
|||
packetReceiver.registerListenerForTypes(
|
||||
{ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||
this, "handleOctreePacket");
|
||||
packetReceiver.registerListener(PacketType::Jurisdiction, this, "handleJurisdictionPacket");
|
||||
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
||||
|
||||
|
||||
|
@ -149,17 +148,6 @@ void Agent::handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNo
|
|||
}
|
||||
}
|
||||
|
||||
void Agent::handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
NodeType_t nodeType;
|
||||
message->peekPrimitive(&nodeType);
|
||||
|
||||
// PacketType_JURISDICTION, first byte is the node type...
|
||||
if (nodeType == NodeType::EntityServer) {
|
||||
DependencyManager::get<EntityScriptingInterface>()->getJurisdictionListener()->
|
||||
queueReceivedPacket(message, senderNode);
|
||||
}
|
||||
}
|
||||
|
||||
void Agent::handleAudioPacket(QSharedPointer<ReceivedMessage> message) {
|
||||
_receivedAudioStream.parseData(*message);
|
||||
_lastReceivedAudioLoudness = _receivedAudioStream.getNextOutputFrameLoudness();
|
||||
|
@ -355,7 +343,6 @@ void Agent::scriptRequestFinished() {
|
|||
|
||||
void Agent::executeScript() {
|
||||
_scriptEngine = scriptEngineFactory(ScriptEngine::AGENT_SCRIPT, _scriptContents, _payload);
|
||||
_scriptEngine->setParent(this); // be the parent of the script engine so it gets moved when we do
|
||||
|
||||
DependencyManager::get<RecordingScriptingInterface>()->setScriptEngine(_scriptEngine);
|
||||
|
||||
|
@ -451,7 +438,7 @@ void Agent::executeScript() {
|
|||
encodedBuffer = audio;
|
||||
}
|
||||
|
||||
AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber,
|
||||
AbstractAudioInterface::emitAudioPacket(encodedBuffer.data(), encodedBuffer.size(), audioSequenceNumber, false,
|
||||
audioTransform, scriptedAvatar->getWorldPosition(), glm::vec3(0),
|
||||
packetType, _selectedCodecName);
|
||||
});
|
||||
|
@ -483,10 +470,7 @@ void Agent::executeScript() {
|
|||
auto recordingInterface = DependencyManager::get<RecordingScriptingInterface>();
|
||||
_scriptEngine->registerGlobalObject("Recording", recordingInterface.data());
|
||||
|
||||
// we need to make sure that init has been called for our EntityScriptingInterface
|
||||
// so that it actually has a jurisdiction listener when we ask it for it next
|
||||
entityScriptingInterface->init();
|
||||
_entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener());
|
||||
|
||||
_entityViewer.init();
|
||||
|
||||
|
|
|
@ -73,7 +73,6 @@ private slots:
|
|||
|
||||
void handleAudioPacket(QSharedPointer<ReceivedMessage> message);
|
||||
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void nodeActivated(SharedNodePointer activatedNode);
|
||||
|
|
|
@ -46,8 +46,14 @@ AssignmentClientApp::AssignmentClientApp(int argc, char* argv[]) :
|
|||
|
||||
const QCommandLineOption helpOption = parser.addHelpOption();
|
||||
|
||||
const QCommandLineOption clientTypeOption(ASSIGNMENT_TYPE_OVERRIDE_OPTION,
|
||||
"run single assignment client of given type", "type");
|
||||
QString typeDescription = "run single assignment client of given type\n# | Type\n============================";
|
||||
for (Assignment::Type type = Assignment::FirstType;
|
||||
type != Assignment::AllTypes;
|
||||
type = static_cast<Assignment::Type>(static_cast<int>(type) + 1)) {
|
||||
typeDescription.append(QStringLiteral("\n%1 | %2").arg(QString::number(type), Assignment::typeToString(type)));
|
||||
}
|
||||
const QCommandLineOption clientTypeOption(ASSIGNMENT_TYPE_OVERRIDE_OPTION, typeDescription, "type");
|
||||
|
||||
parser.addOption(clientTypeOption);
|
||||
|
||||
const QCommandLineOption poolOption(ASSIGNMENT_POOL_OPTION, "set assignment pool", "pool-name");
|
||||
|
|
|
@ -29,18 +29,16 @@
|
|||
#include <QtCore/QUrlQuery>
|
||||
|
||||
#include <ClientServerUtils.h>
|
||||
#include <FBXBaker.h>
|
||||
#include <JSBaker.h>
|
||||
#include <NodeType.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <PathUtils.h>
|
||||
#include <image/Image.h>
|
||||
|
||||
#include "AssetServerLogging.h"
|
||||
#include "BakeAssetTask.h"
|
||||
#include "SendAssetTask.h"
|
||||
#include "UploadAssetTask.h"
|
||||
|
||||
|
||||
static const uint8_t MIN_CORES_FOR_MULTICORE = 4;
|
||||
static const uint8_t CPU_AFFINITY_COUNT_HIGH = 2;
|
||||
static const uint8_t CPU_AFFINITY_COUNT_LOW = 1;
|
||||
|
@ -57,7 +55,7 @@ static const QString BAKED_MODEL_SIMPLE_NAME = "asset.fbx";
|
|||
static const QString BAKED_TEXTURE_SIMPLE_NAME = "texture.ktx";
|
||||
static const QString BAKED_SCRIPT_SIMPLE_NAME = "asset.js";
|
||||
|
||||
void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) {
|
||||
void AssetServer::bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) {
|
||||
qDebug() << "Starting bake for: " << assetPath << assetHash;
|
||||
auto it = _pendingBakes.find(assetHash);
|
||||
if (it == _pendingBakes.end()) {
|
||||
|
@ -75,23 +73,23 @@ void AssetServer::bakeAsset(const AssetHash& assetHash, const AssetPath& assetPa
|
|||
}
|
||||
}
|
||||
|
||||
QString AssetServer::getPathToAssetHash(const AssetHash& assetHash) {
|
||||
QString AssetServer::getPathToAssetHash(const AssetUtils::AssetHash& assetHash) {
|
||||
return _filesDirectory.absoluteFilePath(assetHash);
|
||||
}
|
||||
|
||||
std::pair<BakingStatus, QString> AssetServer::getAssetStatus(const AssetPath& path, const AssetHash& hash) {
|
||||
std::pair<AssetUtils::BakingStatus, QString> AssetServer::getAssetStatus(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) {
|
||||
auto it = _pendingBakes.find(hash);
|
||||
if (it != _pendingBakes.end()) {
|
||||
return { (*it)->isBaking() ? Baking : Pending, "" };
|
||||
return { (*it)->isBaking() ? AssetUtils::Baking : AssetUtils::Pending, "" };
|
||||
}
|
||||
|
||||
if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
return { Baked, "" };
|
||||
if (path.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
return { AssetUtils::Baked, "" };
|
||||
}
|
||||
|
||||
auto dotIndex = path.lastIndexOf(".");
|
||||
if (dotIndex == -1) {
|
||||
return { Irrelevant, "" };
|
||||
return { AssetUtils::Irrelevant, "" };
|
||||
}
|
||||
|
||||
auto extension = path.mid(dotIndex + 1);
|
||||
|
@ -105,16 +103,16 @@ std::pair<BakingStatus, QString> AssetServer::getAssetStatus(const AssetPath& pa
|
|||
} else if (BAKEABLE_SCRIPT_EXTENSIONS.contains(extension)) {
|
||||
bakedFilename = BAKED_SCRIPT_SIMPLE_NAME;
|
||||
} else {
|
||||
return { Irrelevant, "" };
|
||||
return { AssetUtils::Irrelevant, "" };
|
||||
}
|
||||
|
||||
auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename;
|
||||
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + bakedFilename;
|
||||
auto jt = _fileMappings.find(bakedPath);
|
||||
if (jt != _fileMappings.end()) {
|
||||
if (jt->second == hash) {
|
||||
return { NotBaked, "" };
|
||||
return { AssetUtils::NotBaked, "" };
|
||||
} else {
|
||||
return { Baked, "" };
|
||||
return { AssetUtils::Baked, "" };
|
||||
}
|
||||
} else {
|
||||
bool loaded;
|
||||
|
@ -122,11 +120,11 @@ std::pair<BakingStatus, QString> AssetServer::getAssetStatus(const AssetPath& pa
|
|||
|
||||
std::tie(loaded, meta) = readMetaFile(hash);
|
||||
if (loaded && meta.failedLastBake) {
|
||||
return { Error, meta.lastBakeErrors };
|
||||
return { AssetUtils::Error, meta.lastBakeErrors };
|
||||
}
|
||||
}
|
||||
|
||||
return { Pending, "" };
|
||||
return { AssetUtils::Pending, "" };
|
||||
}
|
||||
|
||||
void AssetServer::bakeAssets() {
|
||||
|
@ -138,14 +136,14 @@ void AssetServer::bakeAssets() {
|
|||
}
|
||||
}
|
||||
|
||||
void AssetServer::maybeBake(const AssetPath& path, const AssetHash& hash) {
|
||||
void AssetServer::maybeBake(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash) {
|
||||
if (needsToBeBaked(path, hash)) {
|
||||
qDebug() << "Queuing bake of: " << path;
|
||||
bakeAsset(hash, path, getPathToAssetHash(hash));
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::createEmptyMetaFile(const AssetHash& hash) {
|
||||
void AssetServer::createEmptyMetaFile(const AssetUtils::AssetHash& hash) {
|
||||
QString metaFilePath = "atp:/" + hash + "/meta.json";
|
||||
QFile metaFile { metaFilePath };
|
||||
|
||||
|
@ -158,14 +156,14 @@ void AssetServer::createEmptyMetaFile(const AssetHash& hash) {
|
|||
}
|
||||
}
|
||||
|
||||
bool AssetServer::hasMetaFile(const AssetHash& hash) {
|
||||
QString metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/meta.json";
|
||||
bool AssetServer::hasMetaFile(const AssetUtils::AssetHash& hash) {
|
||||
QString metaFilePath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/meta.json";
|
||||
|
||||
return _fileMappings.find(metaFilePath) != _fileMappings.end();
|
||||
}
|
||||
|
||||
bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHash) {
|
||||
if (path.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
bool AssetServer::needsToBeBaked(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& assetHash) {
|
||||
if (path.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -197,7 +195,7 @@ bool AssetServer::needsToBeBaked(const AssetPath& path, const AssetHash& assetHa
|
|||
return false;
|
||||
}
|
||||
|
||||
auto bakedPath = HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename;
|
||||
auto bakedPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + assetHash + "/" + bakedFilename;
|
||||
return _fileMappings.find(bakedPath) == _fileMappings.end();
|
||||
}
|
||||
|
||||
|
@ -236,7 +234,7 @@ AssetServer::AssetServer(ReceivedMessage& message) :
|
|||
ThreadedAssignment(message),
|
||||
_transferTaskPool(this),
|
||||
_bakingTaskPool(this),
|
||||
_filesizeLimit(MAX_UPLOAD_SIZE)
|
||||
_filesizeLimit(AssetUtils::MAX_UPLOAD_SIZE)
|
||||
{
|
||||
// store the current state of image compression so we can reset it when this assignment is complete
|
||||
_wasColorTextureCompressionEnabled = image::isColorTexturesCompressionEnabled();
|
||||
|
@ -250,7 +248,7 @@ AssetServer::AssetServer(ReceivedMessage& message) :
|
|||
image::setNormalTexturesCompressionEnabled(true);
|
||||
image::setCubeTexturesCompressionEnabled(true);
|
||||
|
||||
BAKEABLE_TEXTURE_EXTENSIONS = TextureBaker::getSupportedFormats();
|
||||
BAKEABLE_TEXTURE_EXTENSIONS = image::getSupportedFormats();
|
||||
qDebug() << "Supported baking texture formats:" << BAKEABLE_MODEL_EXTENSIONS;
|
||||
|
||||
// Most of the work will be I/O bound, reading from disk and constructing packet objects,
|
||||
|
@ -391,7 +389,7 @@ void AssetServer::completeSetup() {
|
|||
// Check the asset directory to output some information about what we have
|
||||
auto files = _filesDirectory.entryList(QDir::Files);
|
||||
|
||||
QRegExp hashFileRegex { ASSET_HASH_REGEX_STRING };
|
||||
QRegExp hashFileRegex { AssetUtils::ASSET_HASH_REGEX_STRING };
|
||||
auto hashedFiles = files.filter(hashFileRegex);
|
||||
|
||||
qCInfo(asset_server) << "There are" << hashedFiles.size() << "asset files in the asset directory.";
|
||||
|
@ -411,15 +409,18 @@ void AssetServer::completeSetup() {
|
|||
// get file size limit for an asset
|
||||
static const QString ASSETS_FILESIZE_LIMIT_OPTION = "assets_filesize_limit";
|
||||
auto assetsFilesizeLimitJSONValue = assetServerObject[ASSETS_FILESIZE_LIMIT_OPTION];
|
||||
auto assetsFilesizeLimit = (uint64_t)assetsFilesizeLimitJSONValue.toInt(MAX_UPLOAD_SIZE);
|
||||
auto assetsFilesizeLimit = (uint64_t)assetsFilesizeLimitJSONValue.toInt(AssetUtils::MAX_UPLOAD_SIZE);
|
||||
|
||||
if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < MAX_UPLOAD_SIZE) {
|
||||
if (assetsFilesizeLimit != 0 && assetsFilesizeLimit < AssetUtils::MAX_UPLOAD_SIZE) {
|
||||
_filesizeLimit = assetsFilesizeLimit * BITS_PER_MEGABITS;
|
||||
}
|
||||
|
||||
PathUtils::removeTemporaryApplicationDirs();
|
||||
PathUtils::removeTemporaryApplicationDirs("Oven");
|
||||
}
|
||||
|
||||
void AssetServer::cleanupUnmappedFiles() {
|
||||
QRegExp hashFileRegex { "^[a-f0-9]{" + QString::number(SHA256_HASH_HEX_LENGTH) + "}" };
|
||||
QRegExp hashFileRegex { "^[a-f0-9]{" + QString::number(AssetUtils::SHA256_HASH_HEX_LENGTH) + "}" };
|
||||
|
||||
auto files = _filesDirectory.entryInfoList(QDir::Files);
|
||||
|
||||
|
@ -452,6 +453,8 @@ void AssetServer::cleanupUnmappedFiles() {
|
|||
}
|
||||
|
||||
void AssetServer::handleAssetMappingOperation(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
using AssetMappingOperationType = AssetUtils::AssetMappingOperationType;
|
||||
|
||||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
|
@ -461,32 +464,41 @@ void AssetServer::handleAssetMappingOperation(QSharedPointer<ReceivedMessage> me
|
|||
auto replyPacket = NLPacketList::create(PacketType::AssetMappingOperationReply, QByteArray(), true, true);
|
||||
replyPacket->writePrimitive(messageID);
|
||||
|
||||
bool canWriteToAssetServer = true;
|
||||
if (senderNode) {
|
||||
canWriteToAssetServer = senderNode->getCanWriteToAssetServer();
|
||||
}
|
||||
|
||||
switch (operationType) {
|
||||
case AssetMappingOperationType::Get:
|
||||
handleGetMappingOperation(*message, senderNode, *replyPacket);
|
||||
handleGetMappingOperation(*message, *replyPacket);
|
||||
break;
|
||||
case AssetMappingOperationType::GetAll:
|
||||
handleGetAllMappingOperation(*message, senderNode, *replyPacket);
|
||||
handleGetAllMappingOperation(*replyPacket);
|
||||
break;
|
||||
case AssetMappingOperationType::Set:
|
||||
handleSetMappingOperation(*message, senderNode, *replyPacket);
|
||||
handleSetMappingOperation(*message, canWriteToAssetServer, *replyPacket);
|
||||
break;
|
||||
case AssetMappingOperationType::Delete:
|
||||
handleDeleteMappingsOperation(*message, senderNode, *replyPacket);
|
||||
handleDeleteMappingsOperation(*message, canWriteToAssetServer, *replyPacket);
|
||||
break;
|
||||
case AssetMappingOperationType::Rename:
|
||||
handleRenameMappingOperation(*message, senderNode, *replyPacket);
|
||||
handleRenameMappingOperation(*message, canWriteToAssetServer, *replyPacket);
|
||||
break;
|
||||
case AssetMappingOperationType::SetBakingEnabled:
|
||||
handleSetBakingEnabledOperation(*message, senderNode, *replyPacket);
|
||||
handleSetBakingEnabledOperation(*message, canWriteToAssetServer, *replyPacket);
|
||||
break;
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendPacketList(std::move(replyPacket), *senderNode);
|
||||
if (senderNode) {
|
||||
nodeList->sendPacketList(std::move(replyPacket), *senderNode);
|
||||
} else {
|
||||
nodeList->sendPacketList(std::move(replyPacket), message->getSenderSockAddr());
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
void AssetServer::handleGetMappingOperation(ReceivedMessage& message, NLPacketList& replyPacket) {
|
||||
QString assetPath = message.readString();
|
||||
|
||||
QUrl url { assetPath };
|
||||
|
@ -517,7 +529,7 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
|
|||
|
||||
if (!bakedRootFile.isEmpty()) {
|
||||
// we ran into an asset for which we could have a baked version, let's check if it's ready
|
||||
bakedAssetPath = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile;
|
||||
bakedAssetPath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + bakedRootFile;
|
||||
auto bakedIt = _fileMappings.find(bakedAssetPath);
|
||||
|
||||
if (bakedIt != _fileMappings.end()) {
|
||||
|
@ -535,7 +547,7 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
|
|||
}
|
||||
}
|
||||
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
|
||||
if (wasRedirected) {
|
||||
qDebug() << "Writing re-directed hash for" << originalAssetHash << "to" << redirectedAssetHash;
|
||||
|
@ -561,12 +573,12 @@ void AssetServer::handleGetMappingOperation(ReceivedMessage& message, SharedNode
|
|||
}
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::AssetNotFound);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::AssetNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
void AssetServer::handleGetAllMappingOperation(NLPacketList& replyPacket) {
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
|
||||
uint32_t count = (uint32_t)_fileMappings.size();
|
||||
|
||||
|
@ -578,41 +590,41 @@ void AssetServer::handleGetAllMappingOperation(ReceivedMessage& message, SharedN
|
|||
replyPacket.writeString(mapping);
|
||||
replyPacket.write(QByteArray::fromHex(hash.toUtf8()));
|
||||
|
||||
BakingStatus status;
|
||||
AssetUtils::BakingStatus status;
|
||||
QString lastBakeErrors;
|
||||
std::tie(status, lastBakeErrors) = getAssetStatus(mapping, hash);
|
||||
replyPacket.writePrimitive(status);
|
||||
if (status == Error) {
|
||||
if (status == AssetUtils::Error) {
|
||||
replyPacket.writeString(lastBakeErrors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
if (senderNode->getCanWriteToAssetServer()) {
|
||||
void AssetServer::handleSetMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) {
|
||||
if (hasWriteAccess) {
|
||||
QString assetPath = message.readString();
|
||||
|
||||
auto assetHash = message.read(SHA256_HASH_LENGTH).toHex();
|
||||
auto assetHash = message.read(AssetUtils::SHA256_HASH_LENGTH).toHex();
|
||||
|
||||
// don't process a set mapping operation that is inside the hidden baked folder
|
||||
if (assetPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
qCDebug(asset_server) << "Refusing to process a set mapping operation inside" << HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
if (assetPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
qCDebug(asset_server) << "Refusing to process a set mapping operation inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
} else {
|
||||
if (setMapping(assetPath, assetHash)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
if (senderNode->getCanWriteToAssetServer()) {
|
||||
void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) {
|
||||
if (hasWriteAccess) {
|
||||
int numberOfDeletedMappings { 0 };
|
||||
message.readPrimitive(&numberOfDeletedMappings);
|
||||
|
||||
|
@ -621,48 +633,48 @@ void AssetServer::handleDeleteMappingsOperation(ReceivedMessage& message, Shared
|
|||
for (int i = 0; i < numberOfDeletedMappings; ++i) {
|
||||
auto mapping = message.readString();
|
||||
|
||||
if (!mapping.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
if (!mapping.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
mappingsToDelete << mapping;
|
||||
} else {
|
||||
qCDebug(asset_server) << "Refusing to delete mapping" << mapping
|
||||
<< "that is inside" << HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
<< "that is inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
}
|
||||
}
|
||||
|
||||
if (deleteMappings(mappingsToDelete)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
if (senderNode->getCanWriteToAssetServer()) {
|
||||
void AssetServer::handleRenameMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) {
|
||||
if (hasWriteAccess) {
|
||||
QString oldPath = message.readString();
|
||||
QString newPath = message.readString();
|
||||
|
||||
if (oldPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER) || newPath.startsWith(HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
if (oldPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER) || newPath.startsWith(AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER)) {
|
||||
qCDebug(asset_server) << "Cannot rename" << oldPath << "to" << newPath
|
||||
<< "since one of the paths is inside" << HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
<< "since one of the paths is inside" << AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER;
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
} else {
|
||||
if (renameMapping(oldPath, newPath)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket) {
|
||||
if (senderNode->getCanWriteToAssetServer()) {
|
||||
void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket) {
|
||||
if (hasWriteAccess) {
|
||||
bool enabled { true };
|
||||
message.readPrimitive(&enabled);
|
||||
|
||||
|
@ -676,12 +688,12 @@ void AssetServer::handleSetBakingEnabledOperation(ReceivedMessage& message, Shar
|
|||
}
|
||||
|
||||
if (setBakingEnabled(mappings, enabled)) {
|
||||
replyPacket.writePrimitive(AssetServerError::NoError);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::MappingOperationFailed);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::MappingOperationFailed);
|
||||
}
|
||||
} else {
|
||||
replyPacket.writePrimitive(AssetServerError::PermissionDenied);
|
||||
replyPacket.writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -689,15 +701,15 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, Sh
|
|||
QByteArray assetHash;
|
||||
MessageID messageID;
|
||||
|
||||
if (message->getSize() < qint64(SHA256_HASH_LENGTH + sizeof(messageID))) {
|
||||
if (message->getSize() < qint64(AssetUtils::SHA256_HASH_LENGTH + sizeof(messageID))) {
|
||||
qCDebug(asset_server) << "ERROR bad file request";
|
||||
return;
|
||||
}
|
||||
|
||||
message->readPrimitive(&messageID);
|
||||
assetHash = message->readWithoutCopy(SHA256_HASH_LENGTH);
|
||||
assetHash = message->readWithoutCopy(AssetUtils::SHA256_HASH_LENGTH);
|
||||
|
||||
auto size = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(AssetServerError) + sizeof(qint64));
|
||||
auto size = qint64(sizeof(MessageID) + AssetUtils::SHA256_HASH_LENGTH + sizeof(AssetUtils::AssetServerError) + sizeof(qint64));
|
||||
auto replyPacket = NLPacket::create(PacketType::AssetGetInfoReply, size, true);
|
||||
|
||||
QByteArray hexHash = assetHash.toHex();
|
||||
|
@ -710,11 +722,11 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, Sh
|
|||
|
||||
if (fileInfo.exists() && fileInfo.isReadable()) {
|
||||
qCDebug(asset_server) << "Opening file: " << fileInfo.filePath();
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(fileInfo.size());
|
||||
} else {
|
||||
qCDebug(asset_server) << "Asset not found: " << QString(hexHash);
|
||||
replyPacket->writePrimitive(AssetServerError::AssetNotFound);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::AssetNotFound);
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
|
@ -723,7 +735,7 @@ void AssetServer::handleAssetGetInfo(QSharedPointer<ReceivedMessage> message, Sh
|
|||
|
||||
void AssetServer::handleAssetGet(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
|
||||
auto minSize = qint64(sizeof(MessageID) + SHA256_HASH_LENGTH + sizeof(DataOffset) + sizeof(DataOffset));
|
||||
auto minSize = qint64(sizeof(MessageID) + AssetUtils::SHA256_HASH_LENGTH + sizeof(AssetUtils::DataOffset) + sizeof(AssetUtils::DataOffset));
|
||||
|
||||
if (message->getSize() < minSize) {
|
||||
qCDebug(asset_server) << "ERROR bad file request";
|
||||
|
@ -736,9 +748,14 @@ void AssetServer::handleAssetGet(QSharedPointer<ReceivedMessage> message, Shared
|
|||
}
|
||||
|
||||
void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
bool canWriteToAssetServer = true;
|
||||
if (senderNode) {
|
||||
canWriteToAssetServer = senderNode->getCanWriteToAssetServer();
|
||||
}
|
||||
|
||||
if (senderNode->getCanWriteToAssetServer()) {
|
||||
qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(senderNode->getUUID());
|
||||
|
||||
if (canWriteToAssetServer) {
|
||||
qCDebug(asset_server) << "Starting an UploadAssetTask for upload from" << uuidStringWithoutCurlyBraces(message->getSourceID());
|
||||
|
||||
auto task = new UploadAssetTask(message, senderNode, _filesDirectory, _filesizeLimit);
|
||||
_transferTaskPool.start(task);
|
||||
|
@ -747,18 +764,22 @@ void AssetServer::handleAssetUpload(QSharedPointer<ReceivedMessage> message, Sha
|
|||
// for now this also means it isn't allowed to add assets
|
||||
// so return a packet with error that indicates that
|
||||
|
||||
auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetServerError), true);
|
||||
auto permissionErrorPacket = NLPacket::create(PacketType::AssetUploadReply, sizeof(MessageID) + sizeof(AssetUtils::AssetServerError), true);
|
||||
|
||||
MessageID messageID;
|
||||
message->readPrimitive(&messageID);
|
||||
|
||||
// write the message ID and a permission denied error
|
||||
permissionErrorPacket->writePrimitive(messageID);
|
||||
permissionErrorPacket->writePrimitive(AssetServerError::PermissionDenied);
|
||||
permissionErrorPacket->writePrimitive(AssetUtils::AssetServerError::PermissionDenied);
|
||||
|
||||
// send off the packet
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendPacket(std::move(permissionErrorPacket), *senderNode);
|
||||
if (senderNode) {
|
||||
nodeList->sendPacket(std::move(permissionErrorPacket), *senderNode);
|
||||
} else {
|
||||
nodeList->sendPacket(std::move(permissionErrorPacket), message->getSenderSockAddr());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -861,12 +882,12 @@ bool AssetServer::loadMappingsFromFile() {
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!isValidFilePath(key)) {
|
||||
if (!AssetUtils::isValidFilePath(key)) {
|
||||
qCWarning(asset_server) << "Will not keep mapping for" << key << "since it is not a valid path.";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!isValidHash(value.toString())) {
|
||||
if (!AssetUtils::isValidHash(value.toString())) {
|
||||
qCWarning(asset_server) << "Will not keep mapping for" << key << "since it does not have a valid hash.";
|
||||
continue;
|
||||
}
|
||||
|
@ -916,15 +937,15 @@ bool AssetServer::writeMappingsToFile() {
|
|||
return false;
|
||||
}
|
||||
|
||||
bool AssetServer::setMapping(AssetPath path, AssetHash hash) {
|
||||
bool AssetServer::setMapping(AssetUtils::AssetPath path, AssetUtils::AssetHash hash) {
|
||||
path = path.trimmed();
|
||||
|
||||
if (!isValidFilePath(path)) {
|
||||
if (!AssetUtils::isValidFilePath(path)) {
|
||||
qCWarning(asset_server) << "Cannot set a mapping for invalid path:" << path << "=>" << hash;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isValidHash(hash)) {
|
||||
if (!AssetUtils::isValidHash(hash)) {
|
||||
qCWarning(asset_server) << "Cannot set a mapping for invalid hash" << path << "=>" << hash;
|
||||
return false;
|
||||
}
|
||||
|
@ -956,23 +977,23 @@ bool AssetServer::setMapping(AssetPath path, AssetHash hash) {
|
|||
}
|
||||
}
|
||||
|
||||
bool pathIsFolder(const AssetPath& path) {
|
||||
bool pathIsFolder(const AssetUtils::AssetPath& path) {
|
||||
return path.endsWith('/');
|
||||
}
|
||||
|
||||
void AssetServer::removeBakedPathsForDeletedAsset(AssetHash hash) {
|
||||
void AssetServer::removeBakedPathsForDeletedAsset(AssetUtils::AssetHash hash) {
|
||||
// we deleted the file with this hash
|
||||
|
||||
// check if we had baked content for that file that should also now be removed
|
||||
// by calling deleteMappings for the hidden baked content folder for this hash
|
||||
AssetPathList hiddenBakedFolder { HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" };
|
||||
AssetUtils::AssetPathList hiddenBakedFolder { AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" };
|
||||
|
||||
qCDebug(asset_server) << "Deleting baked content below" << hiddenBakedFolder << "since" << hash << "was deleted";
|
||||
|
||||
deleteMappings(hiddenBakedFolder);
|
||||
}
|
||||
|
||||
bool AssetServer::deleteMappings(const AssetPathList& paths) {
|
||||
bool AssetServer::deleteMappings(const AssetUtils::AssetPathList& paths) {
|
||||
// take a copy of the current mappings in case persistence of these deletes fails
|
||||
auto oldMappings = _fileMappings;
|
||||
|
||||
|
@ -1058,11 +1079,11 @@ bool AssetServer::deleteMappings(const AssetPathList& paths) {
|
|||
}
|
||||
}
|
||||
|
||||
bool AssetServer::renameMapping(AssetPath oldPath, AssetPath newPath) {
|
||||
bool AssetServer::renameMapping(AssetUtils::AssetPath oldPath, AssetUtils::AssetPath newPath) {
|
||||
oldPath = oldPath.trimmed();
|
||||
newPath = newPath.trimmed();
|
||||
|
||||
if (!isValidFilePath(oldPath) || !isValidFilePath(newPath)) {
|
||||
if (!AssetUtils::isValidFilePath(oldPath) || !AssetUtils::isValidFilePath(newPath)) {
|
||||
qCWarning(asset_server) << "Cannot perform rename with invalid paths - both should have leading forward and no ending slashes:"
|
||||
<< oldPath << "=>" << newPath;
|
||||
|
||||
|
@ -1162,8 +1183,8 @@ static const QString BAKED_ASSET_SIMPLE_FBX_NAME = "asset.fbx";
|
|||
static const QString BAKED_ASSET_SIMPLE_TEXTURE_NAME = "texture.ktx";
|
||||
static const QString BAKED_ASSET_SIMPLE_JS_NAME = "asset.js";
|
||||
|
||||
QString getBakeMapping(const AssetHash& hash, const QString& relativeFilePath) {
|
||||
return HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
|
||||
QString getBakeMapping(const AssetUtils::AssetHash& hash, const QString& relativeFilePath) {
|
||||
return AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + relativeFilePath;
|
||||
}
|
||||
|
||||
void AssetServer::handleFailedBake(QString originalAssetHash, QString assetPath, QString errors) {
|
||||
|
@ -1195,7 +1216,7 @@ void AssetServer::handleCompletedBake(QString originalAssetHash, QString origina
|
|||
|
||||
qDebug() << "File path: " << filePath;
|
||||
|
||||
AssetHash bakedFileHash;
|
||||
AssetUtils::AssetHash bakedFileHash;
|
||||
|
||||
if (file.open(QIODevice::ReadOnly)) {
|
||||
QCryptographicHash hasher(QCryptographicHash::Sha256);
|
||||
|
@ -1288,8 +1309,8 @@ static const QString BAKE_VERSION_KEY = "bake_version";
|
|||
static const QString FAILED_LAST_BAKE_KEY = "failed_last_bake";
|
||||
static const QString LAST_BAKE_ERRORS_KEY = "last_bake_errors";
|
||||
|
||||
std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetHash hash) {
|
||||
auto metaFilePath = HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json";
|
||||
std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetUtils::AssetHash hash) {
|
||||
auto metaFilePath = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + hash + "/" + "meta.json";
|
||||
|
||||
auto it = _fileMappings.find(metaFilePath);
|
||||
if (it == _fileMappings.end()) {
|
||||
|
@ -1333,7 +1354,7 @@ std::pair<bool, AssetMeta> AssetServer::readMetaFile(AssetHash hash) {
|
|||
return { false, {} };
|
||||
}
|
||||
|
||||
bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta) {
|
||||
bool AssetServer::writeMetaFile(AssetUtils::AssetHash originalAssetHash, const AssetMeta& meta) {
|
||||
// construct the JSON that will be in the meta file
|
||||
QJsonObject metaFileObject;
|
||||
|
||||
|
@ -1347,7 +1368,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me
|
|||
auto metaFileJSON = metaFileDoc.toJson();
|
||||
|
||||
// get a hash for the contents of the meta-file
|
||||
AssetHash metaFileHash = QCryptographicHash::hash(metaFileJSON, QCryptographicHash::Sha256).toHex();
|
||||
AssetUtils::AssetHash metaFileHash = QCryptographicHash::hash(metaFileJSON, QCryptographicHash::Sha256).toHex();
|
||||
|
||||
// create the meta file in our files folder, named by the hash of its contents
|
||||
QFile metaFile(_filesDirectory.absoluteFilePath(metaFileHash));
|
||||
|
@ -1357,7 +1378,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me
|
|||
metaFile.close();
|
||||
|
||||
// add a mapping to the meta file so it doesn't get deleted because it is unmapped
|
||||
auto metaFileMapping = HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + "meta.json";
|
||||
auto metaFileMapping = AssetUtils::HIDDEN_BAKED_CONTENT_FOLDER + originalAssetHash + "/" + "meta.json";
|
||||
|
||||
return setMapping(metaFileMapping, metaFileHash);
|
||||
} else {
|
||||
|
@ -1365,7 +1386,7 @@ bool AssetServer::writeMetaFile(AssetHash originalAssetHash, const AssetMeta& me
|
|||
}
|
||||
}
|
||||
|
||||
bool AssetServer::setBakingEnabled(const AssetPathList& paths, bool enabled) {
|
||||
bool AssetServer::setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled) {
|
||||
for (const auto& path : paths) {
|
||||
auto it = _fileMappings.find(path);
|
||||
if (it != _fileMappings.end()) {
|
||||
|
|
|
@ -21,7 +21,6 @@
|
|||
#include "AssetUtils.h"
|
||||
#include "ReceivedMessage.h"
|
||||
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<QString> {
|
||||
|
@ -30,9 +29,6 @@ namespace std {
|
|||
}
|
||||
|
||||
struct AssetMeta {
|
||||
AssetMeta() {
|
||||
}
|
||||
|
||||
int bakeVersion { 0 };
|
||||
bool failedLastBake { false };
|
||||
QString lastBakeErrors;
|
||||
|
@ -61,43 +57,44 @@ private slots:
|
|||
void sendStatsPacket() override;
|
||||
|
||||
private:
|
||||
using Mappings = std::unordered_map<QString, QString>;
|
||||
void handleGetMappingOperation(ReceivedMessage& message, NLPacketList& replyPacket);
|
||||
void handleGetAllMappingOperation(NLPacketList& replyPacket);
|
||||
void handleSetMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket);
|
||||
void handleDeleteMappingsOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket);
|
||||
void handleRenameMappingOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket);
|
||||
void handleSetBakingEnabledOperation(ReceivedMessage& message, bool hasWriteAccess, NLPacketList& replyPacket);
|
||||
|
||||
void handleGetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
|
||||
void handleGetAllMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
|
||||
void handleSetMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
|
||||
void handleDeleteMappingsOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
|
||||
void handleRenameMappingOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
|
||||
void handleSetBakingEnabledOperation(ReceivedMessage& message, SharedNodePointer senderNode, NLPacketList& replyPacket);
|
||||
void handleAssetServerBackup(ReceivedMessage& message, NLPacketList& replyPacket);
|
||||
void handleAssetServerRestore(ReceivedMessage& message, NLPacketList& replyPacket);
|
||||
|
||||
// Mapping file operations must be called from main assignment thread only
|
||||
bool loadMappingsFromFile();
|
||||
bool writeMappingsToFile();
|
||||
|
||||
/// Set the mapping for path to hash
|
||||
bool setMapping(AssetPath path, AssetHash hash);
|
||||
bool setMapping(AssetUtils::AssetPath path, AssetUtils::AssetHash hash);
|
||||
|
||||
/// Delete mapping `path`. Returns `true` if deletion of mappings succeeds, else `false`.
|
||||
bool deleteMappings(const AssetPathList& paths);
|
||||
bool deleteMappings(const AssetUtils::AssetPathList& paths);
|
||||
|
||||
/// Rename mapping from `oldPath` to `newPath`. Returns true if successful
|
||||
bool renameMapping(AssetPath oldPath, AssetPath newPath);
|
||||
bool renameMapping(AssetUtils::AssetPath oldPath, AssetUtils::AssetPath newPath);
|
||||
|
||||
bool setBakingEnabled(const AssetPathList& paths, bool enabled);
|
||||
bool setBakingEnabled(const AssetUtils::AssetPathList& paths, bool enabled);
|
||||
|
||||
/// Delete any unmapped files from the local asset directory
|
||||
void cleanupUnmappedFiles();
|
||||
|
||||
QString getPathToAssetHash(const AssetHash& assetHash);
|
||||
QString getPathToAssetHash(const AssetUtils::AssetHash& assetHash);
|
||||
|
||||
std::pair<BakingStatus, QString> getAssetStatus(const AssetPath& path, const AssetHash& hash);
|
||||
std::pair<AssetUtils::BakingStatus, QString> getAssetStatus(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash);
|
||||
|
||||
void bakeAssets();
|
||||
void maybeBake(const AssetPath& path, const AssetHash& hash);
|
||||
void createEmptyMetaFile(const AssetHash& hash);
|
||||
bool hasMetaFile(const AssetHash& hash);
|
||||
bool needsToBeBaked(const AssetPath& path, const AssetHash& assetHash);
|
||||
void bakeAsset(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath);
|
||||
void maybeBake(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& hash);
|
||||
void createEmptyMetaFile(const AssetUtils::AssetHash& hash);
|
||||
bool hasMetaFile(const AssetUtils::AssetHash& hash);
|
||||
bool needsToBeBaked(const AssetUtils::AssetPath& path, const AssetUtils::AssetHash& assetHash);
|
||||
void bakeAsset(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath);
|
||||
|
||||
/// Move baked content for asset to baked directory and update baked status
|
||||
void handleCompletedBake(QString originalAssetHash, QString assetPath, QString bakedTempOutputDir,
|
||||
|
@ -106,13 +103,13 @@ private:
|
|||
void handleAbortedBake(QString originalAssetHash, QString assetPath);
|
||||
|
||||
/// Create meta file to describe baked content for original asset
|
||||
std::pair<bool, AssetMeta> readMetaFile(AssetHash hash);
|
||||
bool writeMetaFile(AssetHash originalAssetHash, const AssetMeta& meta = AssetMeta());
|
||||
std::pair<bool, AssetMeta> readMetaFile(AssetUtils::AssetHash hash);
|
||||
bool writeMetaFile(AssetUtils::AssetHash originalAssetHash, const AssetMeta& meta = AssetMeta());
|
||||
|
||||
/// Remove baked paths when the original asset is deleteds
|
||||
void removeBakedPathsForDeletedAsset(AssetHash originalAssetHash);
|
||||
void removeBakedPathsForDeletedAsset(AssetUtils::AssetHash originalAssetHash);
|
||||
|
||||
Mappings _fileMappings;
|
||||
AssetUtils::Mappings _fileMappings;
|
||||
|
||||
QDir _resourcesDirectory;
|
||||
QDir _filesDirectory;
|
||||
|
@ -120,7 +117,7 @@ private:
|
|||
/// Task pool for handling uploads and downloads of assets
|
||||
QThreadPool _transferTaskPool;
|
||||
|
||||
QHash<AssetHash, std::shared_ptr<BakeAssetTask>> _pendingBakes;
|
||||
QHash<AssetUtils::AssetHash, std::shared_ptr<BakeAssetTask>> _pendingBakes;
|
||||
QThreadPool _bakingTaskPool;
|
||||
|
||||
bool _wasColorTextureCompressionEnabled { false };
|
||||
|
|
|
@ -11,18 +11,29 @@
|
|||
|
||||
#include "BakeAssetTask.h"
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#include <QtCore/QThread>
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include <FBXBaker.h>
|
||||
#include <PathUtils.h>
|
||||
#include <JSBaker.h>
|
||||
|
||||
BakeAssetTask::BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath) :
|
||||
static const int OVEN_STATUS_CODE_SUCCESS { 0 };
|
||||
static const int OVEN_STATUS_CODE_FAIL { 1 };
|
||||
static const int OVEN_STATUS_CODE_ABORT { 2 };
|
||||
|
||||
std::once_flag registerMetaTypesFlag;
|
||||
|
||||
BakeAssetTask::BakeAssetTask(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath) :
|
||||
_assetHash(assetHash),
|
||||
_assetPath(assetPath),
|
||||
_filePath(filePath)
|
||||
{
|
||||
|
||||
std::call_once(registerMetaTypesFlag, []() {
|
||||
qRegisterMetaType<QProcess::ProcessError>("QProcess::ProcessError");
|
||||
qRegisterMetaType<QProcess::ExitStatus>("QProcess::ExitStatus");
|
||||
});
|
||||
}
|
||||
|
||||
void cleanupTempFiles(QString tempOutputDir, std::vector<QString> files) {
|
||||
|
@ -41,67 +52,76 @@ void cleanupTempFiles(QString tempOutputDir, std::vector<QString> files) {
|
|||
};
|
||||
|
||||
void BakeAssetTask::run() {
|
||||
_isBaking.store(true);
|
||||
|
||||
qRegisterMetaType<QVector<QString> >("QVector<QString>");
|
||||
TextureBakerThreadGetter fn = []() -> QThread* { return QThread::currentThread(); };
|
||||
|
||||
QString tempOutputDir;
|
||||
|
||||
if (_assetPath.endsWith(".fbx")) {
|
||||
tempOutputDir = PathUtils::generateTemporaryDir();
|
||||
_baker = std::unique_ptr<FBXBaker> {
|
||||
new FBXBaker(QUrl("file:///" + _filePath), fn, tempOutputDir)
|
||||
};
|
||||
} else if (_assetPath.endsWith(".js", Qt::CaseInsensitive)) {
|
||||
_baker = std::unique_ptr<JSBaker>{
|
||||
new JSBaker(QUrl("file:///" + _filePath), PathUtils::generateTemporaryDir())
|
||||
};
|
||||
} else {
|
||||
tempOutputDir = PathUtils::generateTemporaryDir();
|
||||
_baker = std::unique_ptr<TextureBaker> {
|
||||
new TextureBaker(QUrl("file:///" + _filePath), image::TextureUsage::CUBE_TEXTURE,
|
||||
tempOutputDir)
|
||||
};
|
||||
if (_isBaking.exchange(true)) {
|
||||
qWarning() << "Tried to start bake asset task while already baking";
|
||||
return;
|
||||
}
|
||||
|
||||
QEventLoop loop;
|
||||
connect(_baker.get(), &Baker::finished, &loop, &QEventLoop::quit);
|
||||
connect(_baker.get(), &Baker::aborted, &loop, &QEventLoop::quit);
|
||||
QMetaObject::invokeMethod(_baker.get(), "bake", Qt::QueuedConnection);
|
||||
loop.exec();
|
||||
QString tempOutputDir = PathUtils::generateTemporaryDir();
|
||||
auto base = QFileInfo(QCoreApplication::applicationFilePath()).absoluteDir();
|
||||
QString path = base.absolutePath() + "/oven";
|
||||
QString extension = _assetPath.mid(_assetPath.lastIndexOf('.') + 1);
|
||||
QStringList args {
|
||||
"-i", _filePath,
|
||||
"-o", tempOutputDir,
|
||||
"-t", extension,
|
||||
};
|
||||
|
||||
if (_baker->wasAborted()) {
|
||||
qDebug() << "Aborted baking: " << _assetHash << _assetPath;
|
||||
_ovenProcess.reset(new QProcess());
|
||||
|
||||
_wasAborted.store(true);
|
||||
connect(_ovenProcess.get(), static_cast<void(QProcess::*)(int, QProcess::ExitStatus)>(&QProcess::finished),
|
||||
this, [this, tempOutputDir](int exitCode, QProcess::ExitStatus exitStatus) {
|
||||
qDebug() << "Baking process finished: " << exitCode << exitStatus;
|
||||
|
||||
cleanupTempFiles(tempOutputDir, _baker->getOutputFiles());
|
||||
if (exitStatus == QProcess::CrashExit) {
|
||||
if (_wasAborted) {
|
||||
emit bakeAborted(_assetHash, _assetPath);
|
||||
} else {
|
||||
QString errors = "Fatal error occurred while baking";
|
||||
emit bakeFailed(_assetHash, _assetPath, errors);
|
||||
}
|
||||
} else if (exitCode == OVEN_STATUS_CODE_SUCCESS) {
|
||||
QDir outputDir = tempOutputDir;
|
||||
auto files = outputDir.entryInfoList(QDir::Files);
|
||||
QVector<QString> outputFiles;
|
||||
for (auto& file : files) {
|
||||
outputFiles.push_back(file.absoluteFilePath());
|
||||
}
|
||||
|
||||
emit bakeAborted(_assetHash, _assetPath);
|
||||
} else if (_baker->hasErrors()) {
|
||||
qDebug() << "Failed to bake: " << _assetHash << _assetPath << _baker->getErrors();
|
||||
emit bakeComplete(_assetHash, _assetPath, tempOutputDir, outputFiles);
|
||||
} else if (exitStatus == QProcess::NormalExit && exitCode == OVEN_STATUS_CODE_ABORT) {
|
||||
_wasAborted.store(true);
|
||||
emit bakeAborted(_assetHash, _assetPath);
|
||||
} else {
|
||||
QString errors;
|
||||
if (exitCode == OVEN_STATUS_CODE_FAIL) {
|
||||
QDir outputDir = tempOutputDir;
|
||||
auto errorFilePath = outputDir.absoluteFilePath("errors.txt");
|
||||
QFile errorFile { errorFilePath };
|
||||
if (errorFile.open(QIODevice::ReadOnly)) {
|
||||
errors = errorFile.readAll();
|
||||
errorFile.close();
|
||||
} else {
|
||||
errors = "Unknown error occurred while baking";
|
||||
}
|
||||
}
|
||||
emit bakeFailed(_assetHash, _assetPath, errors);
|
||||
}
|
||||
|
||||
auto errors = _baker->getErrors().join('\n'); // Join error list into a single string for convenience
|
||||
|
||||
_didFinish.store(true);
|
||||
|
||||
cleanupTempFiles(tempOutputDir, _baker->getOutputFiles());
|
||||
});
|
||||
|
||||
qDebug() << "Starting oven for " << _assetPath;
|
||||
_ovenProcess->start(path, args, QIODevice::ReadOnly);
|
||||
if (!_ovenProcess->waitForStarted(-1)) {
|
||||
QString errors = "Oven process failed to start";
|
||||
emit bakeFailed(_assetHash, _assetPath, errors);
|
||||
} else {
|
||||
auto vectorOutputFiles = QVector<QString>::fromStdVector(_baker->getOutputFiles());
|
||||
|
||||
qDebug() << "Finished baking: " << _assetHash << _assetPath << vectorOutputFiles;
|
||||
|
||||
_didFinish.store(true);
|
||||
|
||||
emit bakeComplete(_assetHash, _assetPath, tempOutputDir, vectorOutputFiles);
|
||||
return;
|
||||
}
|
||||
_ovenProcess->waitForFinished();
|
||||
}
|
||||
|
||||
void BakeAssetTask::abort() {
|
||||
if (_baker) {
|
||||
_baker->abort();
|
||||
if (!_wasAborted.exchange(true)) {
|
||||
_ovenProcess->terminate();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,14 +17,15 @@
|
|||
#include <QtCore/QDebug>
|
||||
#include <QtCore/QObject>
|
||||
#include <QtCore/QRunnable>
|
||||
#include <QDir>
|
||||
#include <QProcess>
|
||||
|
||||
#include <AssetUtils.h>
|
||||
#include <Baker.h>
|
||||
|
||||
class BakeAssetTask : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
BakeAssetTask(const AssetHash& assetHash, const AssetPath& assetPath, const QString& filePath);
|
||||
BakeAssetTask(const AssetUtils::AssetHash& assetHash, const AssetUtils::AssetPath& assetPath, const QString& filePath);
|
||||
|
||||
bool isBaking() { return _isBaking.load(); }
|
||||
|
||||
|
@ -32,7 +33,6 @@ public:
|
|||
|
||||
void abort();
|
||||
bool wasAborted() const { return _wasAborted.load(); }
|
||||
bool didFinish() const { return _didFinish.load(); }
|
||||
|
||||
signals:
|
||||
void bakeComplete(QString assetHash, QString assetPath, QString tempOutputDir, QVector<QString> outputFiles);
|
||||
|
@ -41,12 +41,11 @@ signals:
|
|||
|
||||
private:
|
||||
std::atomic<bool> _isBaking { false };
|
||||
AssetHash _assetHash;
|
||||
AssetPath _assetPath;
|
||||
AssetUtils::AssetHash _assetHash;
|
||||
AssetUtils::AssetPath _assetPath;
|
||||
QString _filePath;
|
||||
std::unique_ptr<Baker> _baker;
|
||||
std::unique_ptr<QProcess> _ovenProcess { nullptr };
|
||||
std::atomic<bool> _wasAborted { false };
|
||||
std::atomic<bool> _didFinish { false };
|
||||
};
|
||||
|
||||
#endif // hifi_BakeAssetTask_h
|
||||
|
|
|
@ -40,7 +40,7 @@ void SendAssetTask::run() {
|
|||
ByteRange byteRange;
|
||||
|
||||
_message->readPrimitive(&messageID);
|
||||
QByteArray assetHash = _message->read(SHA256_HASH_LENGTH);
|
||||
QByteArray assetHash = _message->read(AssetUtils::SHA256_HASH_LENGTH);
|
||||
|
||||
// `start` and `end` indicate the range of data to retrieve for the asset identified by `assetHash`.
|
||||
// `start` is inclusive, `end` is exclusive. Requesting `start` = 1, `end` = 10 will retrieve 9 bytes of data,
|
||||
|
@ -61,7 +61,7 @@ void SendAssetTask::run() {
|
|||
replyPacketList->writePrimitive(messageID);
|
||||
|
||||
if (!byteRange.isValid()) {
|
||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::InvalidByteRange);
|
||||
} else {
|
||||
QString filePath = _resourcesDir.filePath(QString(hexHash));
|
||||
|
||||
|
@ -75,7 +75,7 @@ void SendAssetTask::run() {
|
|||
// check if we're being asked to read data that we just don't have
|
||||
// because of the file size
|
||||
if (file.size() < byteRange.fromInclusive || file.size() < byteRange.toExclusive) {
|
||||
replyPacketList->writePrimitive(AssetServerError::InvalidByteRange);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::InvalidByteRange);
|
||||
qCDebug(networking) << "Bad byte range: " << hexHash << " "
|
||||
<< byteRange.fromInclusive << ":" << byteRange.toExclusive;
|
||||
} else {
|
||||
|
@ -86,7 +86,7 @@ void SendAssetTask::run() {
|
|||
|
||||
// this range is positive, meaning we just need to seek into the file and then read from there
|
||||
file.seek(byteRange.fromInclusive);
|
||||
replyPacketList->writePrimitive(AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(size);
|
||||
replyPacketList->write(file.read(size));
|
||||
} else {
|
||||
|
@ -95,7 +95,7 @@ void SendAssetTask::run() {
|
|||
// seek to the part of the file where the negative range begins
|
||||
file.seek(file.size() + byteRange.fromInclusive);
|
||||
|
||||
replyPacketList->writePrimitive(AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacketList->writePrimitive(size);
|
||||
|
||||
// first write everything from the negative range to the end of the file
|
||||
|
@ -107,10 +107,14 @@ void SendAssetTask::run() {
|
|||
file.close();
|
||||
} else {
|
||||
qCDebug(networking) << "Asset not found: " << filePath << "(" << hexHash << ")";
|
||||
replyPacketList->writePrimitive(AssetServerError::AssetNotFound);
|
||||
replyPacketList->writePrimitive(AssetUtils::AssetServerError::AssetNotFound);
|
||||
}
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendPacketList(std::move(replyPacketList), *_senderNode);
|
||||
if (_senderNode) {
|
||||
nodeList->sendPacketList(std::move(replyPacketList), *_senderNode);
|
||||
} else {
|
||||
nodeList->sendPacketList(std::move(replyPacketList), _message->getSenderSockAddr());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,7 +20,6 @@
|
|||
|
||||
#include "ClientServerUtils.h"
|
||||
|
||||
|
||||
UploadAssetTask::UploadAssetTask(QSharedPointer<ReceivedMessage> receivedMessage, SharedNodePointer senderNode,
|
||||
const QDir& resourcesDir, uint64_t filesizeLimit) :
|
||||
_receivedMessage(receivedMessage),
|
||||
|
@ -42,23 +41,29 @@ void UploadAssetTask::run() {
|
|||
|
||||
uint64_t fileSize;
|
||||
buffer.read(reinterpret_cast<char*>(&fileSize), sizeof(fileSize));
|
||||
|
||||
qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from"
|
||||
<< uuidStringWithoutCurlyBraces(_senderNode->getUUID());
|
||||
|
||||
if (_senderNode) {
|
||||
qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID());
|
||||
} else {
|
||||
qDebug() << "UploadAssetTask reading a file of " << fileSize << "bytes from" << _receivedMessage->getSenderSockAddr();
|
||||
}
|
||||
|
||||
auto replyPacket = NLPacket::create(PacketType::AssetUploadReply, -1, true);
|
||||
replyPacket->writePrimitive(messageID);
|
||||
|
||||
if (fileSize > _filesizeLimit) {
|
||||
replyPacket->writePrimitive(AssetServerError::AssetTooLarge);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::AssetTooLarge);
|
||||
} else {
|
||||
QByteArray fileData = buffer.read(fileSize);
|
||||
|
||||
auto hash = hashData(fileData);
|
||||
auto hash = AssetUtils::hashData(fileData);
|
||||
auto hexHash = hash.toHex();
|
||||
|
||||
qDebug() << "Hash for uploaded file from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID())
|
||||
<< "is: (" << hexHash << ") ";
|
||||
|
||||
if (_senderNode) {
|
||||
qDebug() << "Hash for uploaded file from" << uuidStringWithoutCurlyBraces(_senderNode->getUUID()) << "is: (" << hexHash << ")";
|
||||
} else {
|
||||
qDebug() << "Hash for uploaded file from" << _receivedMessage->getSenderSockAddr() << "is: (" << hexHash << ")";
|
||||
}
|
||||
|
||||
QFile file { _resourcesDir.filePath(QString(hexHash)) };
|
||||
|
||||
|
@ -66,12 +71,12 @@ void UploadAssetTask::run() {
|
|||
|
||||
if (file.exists()) {
|
||||
// check if the local file has the correct contents, otherwise we overwrite
|
||||
if (file.open(QIODevice::ReadOnly) && hashData(file.readAll()) == hash) {
|
||||
if (file.open(QIODevice::ReadOnly) && AssetUtils::hashData(file.readAll()) == hash) {
|
||||
qDebug() << "Not overwriting existing verified file: " << hexHash;
|
||||
|
||||
existingCorrectFile = true;
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacket->write(hash);
|
||||
} else {
|
||||
qDebug() << "Overwriting an existing file whose contents did not match the expected hash: " << hexHash;
|
||||
|
@ -84,7 +89,7 @@ void UploadAssetTask::run() {
|
|||
qDebug() << "Wrote file" << hexHash << "to disk. Upload complete";
|
||||
file.close();
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::NoError);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::NoError);
|
||||
replyPacket->write(hash);
|
||||
} else {
|
||||
qWarning() << "Failed to upload or write to file" << hexHash << " - upload failed.";
|
||||
|
@ -96,7 +101,7 @@ void UploadAssetTask::run() {
|
|||
qWarning() << "Removal of failed upload file" << hexHash << "failed.";
|
||||
}
|
||||
|
||||
replyPacket->writePrimitive(AssetServerError::FileOperationFailed);
|
||||
replyPacket->writePrimitive(AssetUtils::AssetServerError::FileOperationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,5 +109,9 @@ void UploadAssetTask::run() {
|
|||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->sendPacket(std::move(replyPacket), *_senderNode);
|
||||
if (_senderNode) {
|
||||
nodeList->sendPacket(std::move(replyPacket), *_senderNode);
|
||||
} else {
|
||||
nodeList->sendPacket(std::move(replyPacket), _receivedMessage->getSenderSockAddr());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -275,17 +275,28 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
if (micStreamIt == _audioStreams.end()) {
|
||||
// we don't have a mic stream yet, so add it
|
||||
|
||||
// read the channel flag to see if our stream is stereo or not
|
||||
// hop past the sequence number that leads the packet
|
||||
message.seek(sizeof(quint16));
|
||||
|
||||
quint8 channelFlag;
|
||||
message.readPrimitive(&channelFlag);
|
||||
// pull the codec string from the packet
|
||||
auto codecString = message.readString();
|
||||
|
||||
bool isStereo = channelFlag == 1;
|
||||
// determine if the stream is stereo or not
|
||||
bool isStereo;
|
||||
if (packetType == PacketType::SilentAudioFrame
|
||||
|| packetType == PacketType::ReplicatedSilentAudioFrame) {
|
||||
quint16 numSilentSamples;
|
||||
message.readPrimitive(&numSilentSamples);
|
||||
isStereo = numSilentSamples == AudioConstants::NETWORK_FRAME_SAMPLES_STEREO;
|
||||
} else {
|
||||
quint8 channelFlag;
|
||||
message.readPrimitive(&channelFlag);
|
||||
isStereo = channelFlag == 1;
|
||||
}
|
||||
|
||||
auto avatarAudioStream = new AvatarAudioStream(isStereo, AudioMixer::getStaticJitterFrames());
|
||||
avatarAudioStream->setupCodec(_codec, _selectedCodecName, AudioConstants::MONO);
|
||||
qCDebug(audio) << "creating new AvatarAudioStream... codec:" << _selectedCodecName;
|
||||
avatarAudioStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
qCDebug(audio) << "creating new AvatarAudioStream... codec:" << _selectedCodecName << "isStereo:" << isStereo;
|
||||
|
||||
connect(avatarAudioStream, &InboundAudioStream::mismatchedAudioCodec,
|
||||
this, &AudioMixerClientData::handleMismatchAudioFormat);
|
||||
|
@ -324,7 +335,7 @@ int AudioMixerClientData::parseData(ReceivedMessage& message) {
|
|||
|
||||
#if INJECTORS_SUPPORT_CODECS
|
||||
injectorStream->setupCodec(_codec, _selectedCodecName, isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
qCDebug(audio) << "creating new injectorStream... codec:" << _selectedCodecName;
|
||||
qCDebug(audio) << "creating new injectorStream... codec:" << _selectedCodecName << "isStereo:" << isStereo;
|
||||
#endif
|
||||
|
||||
auto emplaced = _audioStreams.emplace(
|
||||
|
@ -567,7 +578,8 @@ void AudioMixerClientData::setupCodec(CodecPluginPointer codec, const QString& c
|
|||
|
||||
auto avatarAudioStream = getAvatarAudioStream();
|
||||
if (avatarAudioStream) {
|
||||
avatarAudioStream->setupCodec(codec, codecName, AudioConstants::MONO);
|
||||
avatarAudioStream->setupCodec(codec, codecName, avatarAudioStream->isStereo() ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
qCDebug(audio) << "setting AvatarAudioStream... codec:" << _selectedCodecName << "isStereo:" << avatarAudioStream->isStereo();
|
||||
}
|
||||
|
||||
#if INJECTORS_SUPPORT_CODECS
|
||||
|
|
|
@ -129,6 +129,12 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
AvatarAudioStream* listenerAudioStream = static_cast<AudioMixerClientData*>(listener->getLinkedData())->getAvatarAudioStream();
|
||||
AudioMixerClientData* listenerData = static_cast<AudioMixerClientData*>(listener->getLinkedData());
|
||||
|
||||
// if we received an invalid position from this listener, then refuse to make them a mix
|
||||
// because we don't know how to do it properly
|
||||
if (!listenerAudioStream->hasValidPosition()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// zero out the mix for this listener
|
||||
memset(_mixSamples, 0, sizeof(_mixSamples));
|
||||
|
||||
|
@ -170,7 +176,7 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
auto nodeID = node->getUUID();
|
||||
|
||||
// compute the node's max relative volume
|
||||
float nodeVolume;
|
||||
float nodeVolume = 0.0f;
|
||||
for (auto& streamPair : nodeData->getAudioStreams()) {
|
||||
auto nodeStream = streamPair.second;
|
||||
|
||||
|
@ -187,10 +193,8 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
}
|
||||
|
||||
// max-heapify the nodes by relative volume
|
||||
throttledNodes.push_back(std::make_pair(nodeVolume, node));
|
||||
if (!throttledNodes.empty()) {
|
||||
std::push_heap(throttledNodes.begin(), throttledNodes.end());
|
||||
}
|
||||
throttledNodes.push_back({ nodeVolume, node });
|
||||
std::push_heap(throttledNodes.begin(), throttledNodes.end());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -244,12 +248,18 @@ bool AudioMixerSlave::prepareMix(const SharedNodePointer& listener) {
|
|||
|
||||
void AudioMixerSlave::throttleStream(AudioMixerClientData& listenerNodeData, const QUuid& sourceNodeID,
|
||||
const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd) {
|
||||
addStream(listenerNodeData, sourceNodeID, listeningNodeStream, streamToAdd, true);
|
||||
// only throttle this stream to the mix if it has a valid position, we won't know how to mix it otherwise
|
||||
if (streamToAdd.hasValidPosition()) {
|
||||
addStream(listenerNodeData, sourceNodeID, listeningNodeStream, streamToAdd, true);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixerSlave::mixStream(AudioMixerClientData& listenerNodeData, const QUuid& sourceNodeID,
|
||||
const AvatarAudioStream& listeningNodeStream, const PositionalAudioStream& streamToAdd) {
|
||||
addStream(listenerNodeData, sourceNodeID, listeningNodeStream, streamToAdd, false);
|
||||
// only add the stream to the mix if it has a valid position, we won't know how to mix it otherwise
|
||||
if (streamToAdd.hasValidPosition()) {
|
||||
addStream(listenerNodeData, sourceNodeID, listeningNodeStream, streamToAdd, false);
|
||||
}
|
||||
}
|
||||
|
||||
void AudioMixerSlave::addStream(AudioMixerClientData& listenerNodeData, const QUuid& sourceNodeID,
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
|
||||
#include <udt/PacketHeaders.h>
|
||||
|
||||
#include "AudioLogging.h"
|
||||
#include "AvatarAudioStream.h"
|
||||
|
||||
AvatarAudioStream::AvatarAudioStream(bool isStereo, int numStaticJitterFrames) :
|
||||
|
@ -41,6 +42,15 @@ int AvatarAudioStream::parseStreamProperties(PacketType type, const QByteArray&
|
|||
_ringBuffer.resizeForFrameSize(isStereo
|
||||
? AudioConstants::NETWORK_FRAME_SAMPLES_STEREO
|
||||
: AudioConstants::NETWORK_FRAME_SAMPLES_PER_CHANNEL);
|
||||
// restart the codec
|
||||
if (_codec) {
|
||||
if (_decoder) {
|
||||
_codec->releaseDecoder(_decoder);
|
||||
}
|
||||
_decoder = _codec->createDecoder(AudioConstants::SAMPLE_RATE, isStereo ? AudioConstants::STEREO : AudioConstants::MONO);
|
||||
}
|
||||
qCDebug(audio) << "resetting AvatarAudioStream... codec:" << _selectedCodecName << "isStereo:" << isStereo;
|
||||
|
||||
_isStereo = isStereo;
|
||||
}
|
||||
|
||||
|
|
|
@ -870,8 +870,8 @@ AvatarMixerClientData* AvatarMixer::getOrCreateClientData(SharedNodePointer node
|
|||
node->setLinkedData(std::unique_ptr<NodeData> { new AvatarMixerClientData(node->getUUID()) });
|
||||
clientData = dynamic_cast<AvatarMixerClientData*>(node->getLinkedData());
|
||||
auto& avatar = clientData->getAvatar();
|
||||
avatar.setDomainMinimumScale(_domainMinimumScale);
|
||||
avatar.setDomainMaximumScale(_domainMaximumScale);
|
||||
avatar.setDomainMinimumHeight(_domainMinimumHeight);
|
||||
avatar.setDomainMaximumHeight(_domainMaximumHeight);
|
||||
}
|
||||
|
||||
return clientData;
|
||||
|
@ -939,21 +939,21 @@ void AvatarMixer::parseDomainServerSettings(const QJsonObject& domainSettings) {
|
|||
|
||||
const QString AVATARS_SETTINGS_KEY = "avatars";
|
||||
|
||||
static const QString MIN_SCALE_OPTION = "min_avatar_scale";
|
||||
float settingMinScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_SCALE_OPTION].toDouble(MIN_AVATAR_SCALE);
|
||||
_domainMinimumScale = glm::clamp(settingMinScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
|
||||
static const QString MIN_HEIGHT_OPTION = "min_avatar_height";
|
||||
float settingMinHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MIN_HEIGHT_OPTION].toDouble(MIN_AVATAR_HEIGHT);
|
||||
_domainMinimumHeight = glm::clamp(settingMinHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
|
||||
|
||||
static const QString MAX_SCALE_OPTION = "max_avatar_scale";
|
||||
float settingMaxScale = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_SCALE_OPTION].toDouble(MAX_AVATAR_SCALE);
|
||||
_domainMaximumScale = glm::clamp(settingMaxScale, MIN_AVATAR_SCALE, MAX_AVATAR_SCALE);
|
||||
static const QString MAX_HEIGHT_OPTION = "max_avatar_height";
|
||||
float settingMaxHeight = domainSettings[AVATARS_SETTINGS_KEY].toObject()[MAX_HEIGHT_OPTION].toDouble(MAX_AVATAR_HEIGHT);
|
||||
_domainMaximumHeight = glm::clamp(settingMaxHeight, MIN_AVATAR_HEIGHT, MAX_AVATAR_HEIGHT);
|
||||
|
||||
// make sure that the domain owner didn't flip min and max
|
||||
if (_domainMinimumScale > _domainMaximumScale) {
|
||||
std::swap(_domainMinimumScale, _domainMaximumScale);
|
||||
if (_domainMinimumHeight > _domainMaximumHeight) {
|
||||
std::swap(_domainMinimumHeight, _domainMaximumHeight);
|
||||
}
|
||||
|
||||
qCDebug(avatars) << "This domain requires a minimum avatar scale of" << _domainMinimumScale
|
||||
<< "and a maximum avatar scale of" << _domainMaximumScale;
|
||||
qCDebug(avatars) << "This domain requires a minimum avatar height of" << _domainMinimumHeight
|
||||
<< "and a maximum avatar height of" << _domainMaximumHeight;
|
||||
|
||||
const QString AVATAR_WHITELIST_DEFAULT{ "" };
|
||||
static const QString AVATAR_WHITELIST_OPTION = "avatar_whitelist";
|
||||
|
|
|
@ -90,8 +90,8 @@ private:
|
|||
|
||||
float _maxKbpsPerNode = 0.0f;
|
||||
|
||||
float _domainMinimumScale { MIN_AVATAR_SCALE };
|
||||
float _domainMaximumScale { MAX_AVATAR_SCALE };
|
||||
float _domainMinimumHeight { MIN_AVATAR_HEIGHT };
|
||||
float _domainMaximumHeight { MAX_AVATAR_HEIGHT };
|
||||
|
||||
RateCounter<> _broadcastRate;
|
||||
p_high_resolution_clock::time_point _lastDebugMessage;
|
||||
|
|
|
@ -25,6 +25,23 @@ AvatarMixerClientData::AvatarMixerClientData(const QUuid& nodeID) :
|
|||
_avatar->setID(nodeID);
|
||||
}
|
||||
|
||||
uint64_t AvatarMixerClientData::getLastOtherAvatarEncodeTime(QUuid otherAvatar) const {
|
||||
std::unordered_map<QUuid, uint64_t>::const_iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
|
||||
if (itr != _lastOtherAvatarEncodeTime.end()) {
|
||||
return itr->second;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time) {
|
||||
std::unordered_map<QUuid, uint64_t>::iterator itr = _lastOtherAvatarEncodeTime.find(otherAvatar);
|
||||
if (itr != _lastOtherAvatarEncodeTime.end()) {
|
||||
itr->second = time;
|
||||
} else {
|
||||
_lastOtherAvatarEncodeTime.emplace(std::pair<QUuid, uint64_t>(otherAvatar, time));
|
||||
}
|
||||
}
|
||||
|
||||
void AvatarMixerClientData::queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node) {
|
||||
if (!_packetQueue.node) {
|
||||
_packetQueue.node = node;
|
||||
|
|
|
@ -110,20 +110,15 @@ public:
|
|||
bool getRequestsDomainListData() { return _requestsDomainListData; }
|
||||
void setRequestsDomainListData(bool requesting) { _requestsDomainListData = requesting; }
|
||||
|
||||
ViewFrustum getViewFrustom() const { return _currentViewFrustum; }
|
||||
ViewFrustum getViewFrustum() const { return _currentViewFrustum; }
|
||||
|
||||
quint64 getLastOtherAvatarEncodeTime(QUuid otherAvatar) {
|
||||
quint64 result = 0;
|
||||
if (_lastOtherAvatarEncodeTime.find(otherAvatar) != _lastOtherAvatarEncodeTime.end()) {
|
||||
result = _lastOtherAvatarEncodeTime[otherAvatar];
|
||||
}
|
||||
_lastOtherAvatarEncodeTime[otherAvatar] = usecTimestampNow();
|
||||
return result;
|
||||
}
|
||||
uint64_t getLastOtherAvatarEncodeTime(QUuid otherAvatar) const;
|
||||
void setLastOtherAvatarEncodeTime(const QUuid& otherAvatar, const uint64_t& time);
|
||||
|
||||
QVector<JointData>& getLastOtherAvatarSentJoints(QUuid otherAvatar) {
|
||||
_lastOtherAvatarSentJoints[otherAvatar].resize(_avatar->getJointCount());
|
||||
return _lastOtherAvatarSentJoints[otherAvatar];
|
||||
auto& lastOtherAvatarSentJoints = _lastOtherAvatarSentJoints[otherAvatar];
|
||||
lastOtherAvatarSentJoints.resize(_avatar->getJointCount());
|
||||
return lastOtherAvatarSentJoints;
|
||||
}
|
||||
|
||||
void queuePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer node);
|
||||
|
@ -143,7 +138,7 @@ private:
|
|||
|
||||
// this is a map of the last time we encoded an "other" avatar for
|
||||
// sending to "this" node
|
||||
std::unordered_map<QUuid, quint64> _lastOtherAvatarEncodeTime;
|
||||
std::unordered_map<QUuid, uint64_t> _lastOtherAvatarEncodeTime;
|
||||
std::unordered_map<QUuid, QVector<JointData>> _lastOtherAvatarSentJoints;
|
||||
|
||||
uint64_t _identityChangeTimestamp;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <NodeList.h>
|
||||
#include <Node.h>
|
||||
#include <OctreeConstants.h>
|
||||
#include <PrioritySortUtil.h>
|
||||
#include <udt/PacketHeaders.h>
|
||||
#include <SharedUtil.h>
|
||||
#include <StDev.h>
|
||||
|
@ -32,7 +33,6 @@
|
|||
#include "AvatarMixerClientData.h"
|
||||
#include "AvatarMixerSlave.h"
|
||||
|
||||
|
||||
void AvatarMixerSlave::configure(ConstIter begin, ConstIter end) {
|
||||
_begin = begin;
|
||||
_end = end;
|
||||
|
@ -184,10 +184,9 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|
||||
|
||||
// setup list of AvatarData as well as maps to map betweeen the AvatarData and the original nodes
|
||||
// for calling the AvatarData::sortAvatars() function and getting our sorted list of client nodes
|
||||
QList<AvatarSharedPointer> avatarList;
|
||||
std::vector<AvatarSharedPointer> avatarsToSort;
|
||||
std::unordered_map<AvatarSharedPointer, SharedNodePointer> avatarDataToNodes;
|
||||
|
||||
std::unordered_map<QUuid, uint64_t> avatarEncodeTimes;
|
||||
std::for_each(_begin, _end, [&](const SharedNodePointer& otherNode) {
|
||||
// make sure this is an agent that we have avatar data for before considering it for inclusion
|
||||
if (otherNode->getType() == NodeType::Agent
|
||||
|
@ -195,36 +194,56 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
const AvatarMixerClientData* otherNodeData = reinterpret_cast<const AvatarMixerClientData*>(otherNode->getLinkedData());
|
||||
|
||||
AvatarSharedPointer otherAvatar = otherNodeData->getAvatarSharedPointer();
|
||||
avatarList << otherAvatar;
|
||||
avatarsToSort.push_back(otherAvatar);
|
||||
avatarDataToNodes[otherAvatar] = otherNode;
|
||||
QUuid id = otherAvatar->getSessionUUID();
|
||||
avatarEncodeTimes[id] = nodeData->getLastOtherAvatarEncodeTime(id);
|
||||
}
|
||||
});
|
||||
|
||||
AvatarSharedPointer thisAvatar = nodeData->getAvatarSharedPointer();
|
||||
ViewFrustum cameraView = nodeData->getViewFrustom();
|
||||
std::priority_queue<AvatarPriority> sortedAvatars;
|
||||
AvatarData::sortAvatars(avatarList, cameraView, sortedAvatars,
|
||||
[&](AvatarSharedPointer avatar)->uint64_t {
|
||||
auto avatarNode = avatarDataToNodes[avatar];
|
||||
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||
return nodeData->getLastBroadcastTime(avatarNode->getUUID());
|
||||
}, [&](AvatarSharedPointer avatar)->float{
|
||||
glm::vec3 nodeBoxHalfScale = (avatar->getWorldPosition() - avatar->getGlobalBoundingBoxCorner() * avatar->getSensorToWorldScale());
|
||||
return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z));
|
||||
}, [&](AvatarSharedPointer avatar)->bool {
|
||||
class SortableAvatar: public PrioritySortUtil::Sortable {
|
||||
public:
|
||||
SortableAvatar() = delete;
|
||||
SortableAvatar(const AvatarSharedPointer& avatar, uint64_t lastEncodeTime)
|
||||
: _avatar(avatar), _lastEncodeTime(lastEncodeTime) {}
|
||||
glm::vec3 getPosition() const override { return _avatar->getWorldPosition(); }
|
||||
float getRadius() const override {
|
||||
glm::vec3 nodeBoxHalfScale = (_avatar->getWorldPosition() - _avatar->getGlobalBoundingBoxCorner() * _avatar->getSensorToWorldScale());
|
||||
return glm::max(nodeBoxHalfScale.x, glm::max(nodeBoxHalfScale.y, nodeBoxHalfScale.z));
|
||||
}
|
||||
uint64_t getTimestamp() const override {
|
||||
return _lastEncodeTime;
|
||||
}
|
||||
AvatarSharedPointer getAvatar() const { return _avatar; }
|
||||
|
||||
private:
|
||||
AvatarSharedPointer _avatar;
|
||||
uint64_t _lastEncodeTime;
|
||||
};
|
||||
|
||||
// prepare to sort
|
||||
ViewFrustum cameraView = nodeData->getViewFrustum();
|
||||
PrioritySortUtil::PriorityQueue<SortableAvatar> sortedAvatars(cameraView,
|
||||
AvatarData::_avatarSortCoefficientSize,
|
||||
AvatarData::_avatarSortCoefficientCenter,
|
||||
AvatarData::_avatarSortCoefficientAge);
|
||||
|
||||
// ignore or sort
|
||||
const AvatarSharedPointer& thisAvatar = nodeData->getAvatarSharedPointer();
|
||||
for (const auto& avatar : avatarsToSort) {
|
||||
if (avatar == thisAvatar) {
|
||||
return true; // ignore ourselves...
|
||||
// don't echo updates to self
|
||||
continue;
|
||||
}
|
||||
|
||||
bool shouldIgnore = false;
|
||||
|
||||
// We will also ignore other nodes for a couple of different reasons:
|
||||
// We ignore other nodes for a couple of reasons:
|
||||
// 1) ignore bubbles and ignore specific node
|
||||
// 2) the node hasn't really updated it's frame data recently, this can
|
||||
// happen if for example the avatar is connected on a desktop and sending
|
||||
// updates at ~30hz. So every 3 frames we skip a frame.
|
||||
auto avatarNode = avatarDataToNodes[avatar];
|
||||
|
||||
auto avatarNode = avatarDataToNodes[avatar];
|
||||
assert(avatarNode); // we can't have gotten here without the avatarData being a valid key in the map
|
||||
|
||||
const AvatarMixerClientData* avatarNodeData = reinterpret_cast<const AvatarMixerClientData*>(avatarNode->getLinkedData());
|
||||
|
@ -240,7 +259,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
|| (avatarNode->isIgnoringNodeWithID(node->getUUID()) && !getsAnyIgnored)) {
|
||||
shouldIgnore = true;
|
||||
} else {
|
||||
|
||||
// Check to see if the space bubble is enabled
|
||||
// Don't bother with these checks if the other avatar has their bubble enabled and we're gettingAnyIgnored
|
||||
if (node->isIgnoreRadiusEnabled() || (avatarNode->isIgnoreRadiusEnabled() && !getsAnyIgnored)) {
|
||||
|
@ -267,8 +285,6 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
nodeData->removeFromRadiusIgnoringSet(node, avatarNode->getUUID());
|
||||
}
|
||||
}
|
||||
quint64 endIgnoreCalculation = usecTimestampNow();
|
||||
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
|
||||
|
||||
if (!shouldIgnore) {
|
||||
AvatarDataSequenceNumber lastSeqToReceiver = nodeData->getLastBroadcastSequenceNumber(avatarNode->getUUID());
|
||||
|
@ -292,20 +308,26 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
++numAvatarsWithSkippedFrames;
|
||||
}
|
||||
}
|
||||
return shouldIgnore;
|
||||
});
|
||||
quint64 endIgnoreCalculation = usecTimestampNow();
|
||||
_stats.ignoreCalculationElapsedTime += (endIgnoreCalculation - startIgnoreCalculation);
|
||||
|
||||
if (!shouldIgnore) {
|
||||
// sort this one for later
|
||||
uint64_t lastEncodeTime = 0;
|
||||
std::unordered_map<QUuid, uint64_t>::const_iterator itr = avatarEncodeTimes.find(avatar->getSessionUUID());
|
||||
if (itr != avatarEncodeTimes.end()) {
|
||||
lastEncodeTime = itr->second;
|
||||
}
|
||||
sortedAvatars.push(SortableAvatar(avatar, lastEncodeTime));
|
||||
}
|
||||
}
|
||||
|
||||
// loop through our sorted avatars and allocate our bandwidth to them accordingly
|
||||
int avatarRank = 0;
|
||||
|
||||
// this is overly conservative, because it includes some avatars we might not consider
|
||||
int remainingAvatars = (int)sortedAvatars.size();
|
||||
|
||||
while (!sortedAvatars.empty()) {
|
||||
AvatarPriority sortData = sortedAvatars.top();
|
||||
const auto avatarData = sortedAvatars.top().getAvatar();
|
||||
sortedAvatars.pop();
|
||||
const auto& avatarData = sortData.avatar;
|
||||
avatarRank++;
|
||||
remainingAvatars--;
|
||||
|
||||
auto otherNode = avatarDataToNodes[avatarData];
|
||||
|
@ -332,10 +354,8 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
nodeData->setLastBroadcastTime(otherNode->getUUID(), usecTimestampNow());
|
||||
}
|
||||
|
||||
// determine if avatar is in view which determines how much data to send
|
||||
glm::vec3 otherPosition = otherAvatar->getClientGlobalPosition();
|
||||
|
||||
|
||||
// determine if avatar is in view, to determine how much data to include...
|
||||
glm::vec3 otherNodeBoxScale = (otherPosition - otherNodeData->getGlobalBoundingBoxCorner()) * 2.0f * otherAvatar->getSensorToWorldScale();
|
||||
AABox otherNodeBox(otherNodeData->getGlobalBoundingBoxCorner(), otherNodeBoxScale);
|
||||
bool isInView = nodeData->otherAvatarInView(otherNodeBox);
|
||||
|
@ -405,14 +425,18 @@ void AvatarMixerSlave::broadcastAvatarDataToAgent(const SharedNodePointer& node)
|
|||
// set the last sent sequence number for this sender on the receiver
|
||||
nodeData->setLastBroadcastSequenceNumber(otherNode->getUUID(),
|
||||
otherNodeData->getLastReceivedSequenceNumber());
|
||||
nodeData->setLastOtherAvatarEncodeTime(otherNode->getUUID(), usecTimestampNow());
|
||||
}
|
||||
} else {
|
||||
// TODO? this avatar is not included now, and will probably not be included next frame.
|
||||
// It would be nice if we could tweak its future sort priority to put it at the back of the list.
|
||||
}
|
||||
|
||||
avatarPacketList->endSegment();
|
||||
|
||||
quint64 endAvatarDataPacking = usecTimestampNow();
|
||||
_stats.avatarDataPackingElapsedTime += (endAvatarDataPacking - startAvatarDataPacking);
|
||||
};
|
||||
}
|
||||
|
||||
quint64 startPacketSending = usecTimestampNow();
|
||||
|
||||
|
|
|
@ -123,12 +123,12 @@ void ScriptableAvatar::update(float deltatime) {
|
|||
AnimPose& absPose = absPoses[i];
|
||||
if (data.rotation != absPose.rot()) {
|
||||
data.rotation = absPose.rot();
|
||||
data.rotationSet = true;
|
||||
data.rotationIsDefaultPose = false;
|
||||
}
|
||||
AnimPose& relPose = poses[i];
|
||||
if (data.translation != relPose.trans()) {
|
||||
data.translation = relPose.trans();
|
||||
data.translationSet = true;
|
||||
data.translationIsDefaultPose = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -453,7 +453,7 @@ void EntityServer::domainSettingsRequestFailed() {
|
|||
void EntityServer::startDynamicDomainVerification() {
|
||||
qCDebug(entities) << "Starting Dynamic Domain Verification...";
|
||||
|
||||
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainId().remove(QRegExp("\\{|\\}"));
|
||||
QString thisDomainID = DependencyManager::get<AddressManager>()->getDomainID().remove(QRegExp("\\{|\\}"));
|
||||
|
||||
EntityTreePointer tree = std::static_pointer_cast<EntityTree>(_tree);
|
||||
QHash<QString, EntityItemID> localMap(tree->getEntityCertificateIDMap());
|
||||
|
@ -477,7 +477,7 @@ void EntityServer::startDynamicDomainVerification() {
|
|||
QNetworkRequest networkRequest;
|
||||
networkRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
networkRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL;
|
||||
QUrl requestURL = NetworkingConstants::METAVERSE_SERVER_URL();
|
||||
requestURL.setPath("/api/v1/commerce/proof_of_purchase_status/location");
|
||||
QJsonObject request;
|
||||
request["certificate_id"] = i.key();
|
||||
|
|
|
@ -23,23 +23,6 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
char serverType = getMyNodeType();
|
||||
PacketType packetType = getMyQueryMessageType();
|
||||
|
||||
NodeToJurisdictionMap& jurisdictions = *_jurisdictionListener->getJurisdictions();
|
||||
|
||||
bool wantExtraDebugging = false;
|
||||
|
||||
if (wantExtraDebugging) {
|
||||
qCDebug(octree) << "OctreeHeadlessViewer::queryOctree() _jurisdictionListener=" << _jurisdictionListener;
|
||||
qCDebug(octree) << "---------------";
|
||||
qCDebug(octree) << "_jurisdictionListener=" << _jurisdictionListener;
|
||||
qCDebug(octree) << "Jurisdictions...";
|
||||
jurisdictions.withReadLock([&] {
|
||||
for (NodeToJurisdictionMapIterator i = jurisdictions.begin(); i != jurisdictions.end(); ++i) {
|
||||
qCDebug(octree) << i.key() << ": " << &i.value();
|
||||
}
|
||||
});
|
||||
qCDebug(octree) << "---------------";
|
||||
}
|
||||
|
||||
_octreeQuery.setCameraPosition(_viewFrustum.getPosition());
|
||||
_octreeQuery.setCameraOrientation(_viewFrustum.getOrientation());
|
||||
_octreeQuery.setCameraFov(_viewFrustum.getFieldOfView());
|
||||
|
@ -51,159 +34,22 @@ void OctreeHeadlessViewer::queryOctree() {
|
|||
_octreeQuery.setOctreeSizeScale(_voxelSizeScale);
|
||||
_octreeQuery.setBoundaryLevelAdjust(_boundaryLevelAdjust);
|
||||
|
||||
// Iterate all of the nodes, and get a count of how many voxel servers we have...
|
||||
int totalServers = 0;
|
||||
int inViewServers = 0;
|
||||
int unknownJurisdictionServers = 0;
|
||||
|
||||
DependencyManager::get<NodeList>()->eachNode([&](const SharedNodePointer& node){
|
||||
// only send to the NodeTypes that are serverType
|
||||
if (node->getActiveSocket() && node->getType() == serverType) {
|
||||
totalServers++;
|
||||
|
||||
// get the server bounds for this server
|
||||
QUuid nodeUUID = node->getUUID();
|
||||
|
||||
// if we haven't heard from this voxel server, go ahead and send it a query, so we
|
||||
// can get the jurisdiction...
|
||||
VoxelPositionSize rootDetails;
|
||||
bool foundRootDetails = false;
|
||||
jurisdictions.withReadLock([&] {
|
||||
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
|
||||
unknownJurisdictionServers++;
|
||||
return;
|
||||
}
|
||||
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
|
||||
|
||||
auto rootCode = map.getRootOctalCode();
|
||||
if (!rootCode) {
|
||||
return;
|
||||
}
|
||||
|
||||
voxelDetailsForCode(rootCode.get(), rootDetails);
|
||||
foundRootDetails = true;
|
||||
});
|
||||
|
||||
if (foundRootDetails) {
|
||||
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
|
||||
if ((bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds))) {
|
||||
inViewServers++;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (wantExtraDebugging) {
|
||||
qCDebug(octree, "Servers: total %d, in view %d, unknown jurisdiction %d",
|
||||
totalServers, inViewServers, unknownJurisdictionServers);
|
||||
}
|
||||
|
||||
int perServerPPS = 0;
|
||||
const int SMALL_BUDGET = 10;
|
||||
int perUnknownServer = SMALL_BUDGET;
|
||||
int totalPPS = getMaxPacketsPerSecond();
|
||||
|
||||
// determine PPS based on number of servers
|
||||
if (inViewServers >= 1) {
|
||||
// set our preferred PPS to be exactly evenly divided among all of the voxel servers... and allocate 1 PPS
|
||||
// for each unknown jurisdiction server
|
||||
perServerPPS = (totalPPS / inViewServers) - (unknownJurisdictionServers * perUnknownServer);
|
||||
} else {
|
||||
if (unknownJurisdictionServers > 0) {
|
||||
perUnknownServer = (totalPPS / unknownJurisdictionServers);
|
||||
}
|
||||
}
|
||||
|
||||
if (wantExtraDebugging) {
|
||||
qCDebug(octree, "perServerPPS: %d perUnknownServer: %d", perServerPPS, perUnknownServer);
|
||||
}
|
||||
|
||||
auto nodeList = DependencyManager::get<NodeList>();
|
||||
nodeList->eachNode([&](const SharedNodePointer& node){
|
||||
// only send to the NodeTypes that are serverType
|
||||
if (node->getActiveSocket() && node->getType() == serverType) {
|
||||
|
||||
// get the server bounds for this server
|
||||
QUuid nodeUUID = node->getUUID();
|
||||
auto node = nodeList->soloNodeOfType(serverType);
|
||||
if (node && node->getActiveSocket()) {
|
||||
_octreeQuery.setMaxQueryPacketsPerSecond(getMaxPacketsPerSecond());
|
||||
|
||||
bool inView = false;
|
||||
bool unknownView = false;
|
||||
auto queryPacket = NLPacket::create(packetType);
|
||||
|
||||
// if we haven't heard from this voxel server, go ahead and send it a query, so we
|
||||
// can get the jurisdiction...
|
||||
VoxelPositionSize rootDetails;
|
||||
bool foundRootDetails = false;
|
||||
jurisdictions.withReadLock([&] {
|
||||
if (jurisdictions.find(nodeUUID) == jurisdictions.end()) {
|
||||
unknownView = true; // assume it's in view
|
||||
if (wantExtraDebugging) {
|
||||
qCDebug(octree) << "no known jurisdiction for node " << *node << ", assume it's visible.";
|
||||
}
|
||||
return;
|
||||
}
|
||||
// encode the query data
|
||||
auto packetData = reinterpret_cast<unsigned char*>(queryPacket->getPayload());
|
||||
int packetSize = _octreeQuery.getBroadcastData(packetData);
|
||||
queryPacket->setPayloadSize(packetSize);
|
||||
|
||||
const JurisdictionMap& map = (jurisdictions)[nodeUUID];
|
||||
auto rootCode = map.getRootOctalCode();
|
||||
|
||||
if (!rootCode) {
|
||||
if (wantExtraDebugging) {
|
||||
qCDebug(octree) << "Jurisdiction without RootCode for node " << *node << ". That's unusual!";
|
||||
}
|
||||
return;
|
||||
}
|
||||
voxelDetailsForCode(rootCode.get(), rootDetails);
|
||||
foundRootDetails = true;
|
||||
});
|
||||
|
||||
if (foundRootDetails) {
|
||||
AACube serverBounds(glm::vec3(rootDetails.x, rootDetails.y, rootDetails.z), rootDetails.s);
|
||||
inView = (bool)(_viewFrustum.calculateCubeKeyholeIntersection(serverBounds));
|
||||
}
|
||||
|
||||
if (inView) {
|
||||
_octreeQuery.setMaxQueryPacketsPerSecond(perServerPPS);
|
||||
if (wantExtraDebugging) {
|
||||
qCDebug(octree) << "inView for node " << *node << ", give it budget of " << perServerPPS;
|
||||
}
|
||||
} else if (unknownView) {
|
||||
if (wantExtraDebugging) {
|
||||
qCDebug(octree) << "no known jurisdiction for node " << *node << ", give it budget of "
|
||||
<< perUnknownServer << " to send us jurisdiction.";
|
||||
}
|
||||
|
||||
// set the query's position/orientation to be degenerate in a manner that will get the scene quickly
|
||||
// If there's only one server, then don't do this, and just let the normal voxel query pass through
|
||||
// as expected... this way, we will actually get a valid scene if there is one to be seen
|
||||
if (totalServers > 1) {
|
||||
_octreeQuery.setCameraPosition(glm::vec3(-0.1,-0.1,-0.1));
|
||||
const glm::quat OFF_IN_NEGATIVE_SPACE = glm::quat(-0.5, 0, -0.5, 1.0);
|
||||
_octreeQuery.setCameraOrientation(OFF_IN_NEGATIVE_SPACE);
|
||||
_octreeQuery.setCameraNearClip(0.1f);
|
||||
_octreeQuery.setCameraFarClip(0.1f);
|
||||
if (wantExtraDebugging) {
|
||||
qCDebug(octree) << "Using 'minimal' camera position for node" << *node;
|
||||
}
|
||||
} else {
|
||||
if (wantExtraDebugging) {
|
||||
qCDebug(octree) << "Using regular camera position for node" << *node;
|
||||
}
|
||||
}
|
||||
_octreeQuery.setMaxQueryPacketsPerSecond(perUnknownServer);
|
||||
} else {
|
||||
_octreeQuery.setMaxQueryPacketsPerSecond(0);
|
||||
}
|
||||
|
||||
// setup the query packet
|
||||
auto queryPacket = NLPacket::create(packetType);
|
||||
|
||||
// read the data to our packet and set the payload size to fit the query
|
||||
int querySize = _octreeQuery.getBroadcastData(reinterpret_cast<unsigned char*>(queryPacket->getPayload()));
|
||||
queryPacket->setPayloadSize(querySize);
|
||||
|
||||
// ask the NodeList to send it
|
||||
nodeList->sendPacket(std::move(queryPacket), *node);
|
||||
}
|
||||
});
|
||||
// make sure we still have an active socket
|
||||
nodeList->sendUnreliablePacket(*queryPacket, *node);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
#define hifi_OctreeHeadlessViewer_h
|
||||
|
||||
#include <OctreeProcessor.h>
|
||||
#include <JurisdictionListener.h>
|
||||
#include <OctreeQuery.h>
|
||||
|
||||
|
||||
|
@ -23,8 +22,6 @@ class OctreeHeadlessViewer : public OctreeProcessor {
|
|||
public:
|
||||
OctreeHeadlessViewer();
|
||||
virtual ~OctreeHeadlessViewer() {};
|
||||
|
||||
void setJurisdictionListener(JurisdictionListener* jurisdictionListener) { _jurisdictionListener = jurisdictionListener; }
|
||||
|
||||
OctreeQuery& getOctreeQuery() { return _octreeQuery; }
|
||||
|
||||
|
@ -57,7 +54,6 @@ public slots:
|
|||
unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); }
|
||||
|
||||
private:
|
||||
JurisdictionListener* _jurisdictionListener = nullptr;
|
||||
OctreeQuery _octreeQuery;
|
||||
|
||||
ViewFrustum _viewFrustum;
|
||||
|
|
|
@ -391,8 +391,7 @@ int OctreeSendThread::packetDistributor(SharedNodePointer node, OctreeQueryNode*
|
|||
|
||||
nodeData->sceneStart(usecTimestampNow() - CHANGE_FUDGE);
|
||||
// start tracking our stats
|
||||
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged,
|
||||
_myServer->getOctree()->getRoot(), _myServer->getJurisdiction());
|
||||
nodeData->stats.sceneStarted(isFullScene, viewFrustumChanged, _myServer->getOctree()->getRoot());
|
||||
|
||||
preStartNewScene(nodeData, isFullScene);
|
||||
}
|
||||
|
@ -507,7 +506,7 @@ void OctreeSendThread::traverseTreeAndSendContents(SharedNodePointer node, Octre
|
|||
float octreeSizeScale = nodeData->getOctreeSizeScale();
|
||||
EncodeBitstreamParams params(INT_MAX, WANT_EXISTS_BITS, DONT_CHOP,
|
||||
viewFrustumChanged, boundaryLevelAdjust, octreeSizeScale,
|
||||
isFullScene, _myServer->getJurisdiction(), nodeData);
|
||||
isFullScene, nodeData);
|
||||
// Our trackSend() function is implemented by the server subclass, and will be called back as new entities/data elements are sent
|
||||
params.trackSend = [this](const QUuid& dataID, quint64 dataEdited) {
|
||||
_myServer->trackSend(dataID, dataEdited, _nodeUuid);
|
||||
|
|
|
@ -237,8 +237,6 @@ OctreeServer::OctreeServer(ReceivedMessage& message) :
|
|||
_debugSending(false),
|
||||
_debugReceiving(false),
|
||||
_verboseDebug(false),
|
||||
_jurisdiction(NULL),
|
||||
_jurisdictionSender(NULL),
|
||||
_octreeInboundPacketProcessor(NULL),
|
||||
_persistThread(NULL),
|
||||
_started(time(0)),
|
||||
|
@ -257,12 +255,6 @@ OctreeServer::~OctreeServer() {
|
|||
delete[] _parsedArgV;
|
||||
}
|
||||
|
||||
if (_jurisdictionSender) {
|
||||
_jurisdictionSender->terminating();
|
||||
_jurisdictionSender->terminate();
|
||||
_jurisdictionSender->deleteLater();
|
||||
}
|
||||
|
||||
if (_octreeInboundPacketProcessor) {
|
||||
_octreeInboundPacketProcessor->terminating();
|
||||
_octreeInboundPacketProcessor->terminate();
|
||||
|
@ -275,9 +267,6 @@ OctreeServer::~OctreeServer() {
|
|||
_persistThread->deleteLater();
|
||||
}
|
||||
|
||||
delete _jurisdiction;
|
||||
_jurisdiction = NULL;
|
||||
|
||||
// cleanup our tree here...
|
||||
qDebug() << qPrintable(_safeServerName) << "server START cleaning up octree... [" << this << "]";
|
||||
_tree.reset();
|
||||
|
@ -933,10 +922,6 @@ void OctreeServer::handleOctreeDataNackPacket(QSharedPointer<ReceivedMessage> me
|
|||
}
|
||||
}
|
||||
|
||||
void OctreeServer::handleJurisdictionRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
_jurisdictionSender->queueReceivedPacket(message, senderNode);
|
||||
}
|
||||
|
||||
void OctreeServer::handleOctreeFileReplacement(QSharedPointer<ReceivedMessage> message) {
|
||||
if (!_isFinished && !_isShuttingDown) {
|
||||
// these messages are only allowed to come from the domain server, so make sure that is the case
|
||||
|
@ -1111,23 +1096,6 @@ void OctreeServer::readConfiguration() {
|
|||
qDebug() << "statusPort= DISABLED";
|
||||
}
|
||||
|
||||
QString jurisdictionFile;
|
||||
if (readOptionString(QString("jurisdictionFile"), settingsSectionObject, jurisdictionFile)) {
|
||||
qDebug("jurisdictionFile=%s", qPrintable(jurisdictionFile));
|
||||
qDebug("about to readFromFile().... jurisdictionFile=%s", qPrintable(jurisdictionFile));
|
||||
_jurisdiction = new JurisdictionMap(qPrintable(jurisdictionFile));
|
||||
qDebug("after readFromFile().... jurisdictionFile=%s", qPrintable(jurisdictionFile));
|
||||
} else {
|
||||
QString jurisdictionRoot;
|
||||
bool hasRoot = readOptionString(QString("jurisdictionRoot"), settingsSectionObject, jurisdictionRoot);
|
||||
QString jurisdictionEndNodes;
|
||||
bool hasEndNodes = readOptionString(QString("jurisdictionEndNodes"), settingsSectionObject, jurisdictionEndNodes);
|
||||
|
||||
if (hasRoot || hasEndNodes) {
|
||||
_jurisdiction = new JurisdictionMap(qPrintable(jurisdictionRoot), qPrintable(jurisdictionEndNodes));
|
||||
}
|
||||
}
|
||||
|
||||
readOptionBool(QString("verboseDebug"), settingsSectionObject, _verboseDebug);
|
||||
qDebug("verboseDebug=%s", debug::valueOf(_verboseDebug));
|
||||
|
||||
|
@ -1241,7 +1209,6 @@ void OctreeServer::domainSettingsRequestComplete() {
|
|||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListener(getMyQueryMessageType(), this, "handleOctreeQueryPacket");
|
||||
packetReceiver.registerListener(PacketType::OctreeDataNack, this, "handleOctreeDataNackPacket");
|
||||
packetReceiver.registerListener(PacketType::JurisdictionRequest, this, "handleJurisdictionRequestPacket");
|
||||
packetReceiver.registerListener(PacketType::OctreeFileReplacement, this, "handleOctreeFileReplacement");
|
||||
packetReceiver.registerListener(PacketType::OctreeFileReplacementFromUrl, this, "handleOctreeFileReplacementFromURL");
|
||||
|
||||
|
@ -1365,13 +1332,6 @@ void OctreeServer::domainSettingsRequestComplete() {
|
|||
_persistThread->initialize(true);
|
||||
}
|
||||
|
||||
// set up our jurisdiction broadcaster...
|
||||
if (_jurisdiction) {
|
||||
_jurisdiction->setNodeType(getMyNodeType());
|
||||
}
|
||||
_jurisdictionSender = new JurisdictionSender(_jurisdiction, getMyNodeType());
|
||||
_jurisdictionSender->initialize(true);
|
||||
|
||||
// set up our OctreeServerPacketProcessor
|
||||
_octreeInboundPacketProcessor = new OctreeInboundPacketProcessor(this);
|
||||
_octreeInboundPacketProcessor->initialize(true);
|
||||
|
@ -1441,10 +1401,6 @@ void OctreeServer::aboutToFinish() {
|
|||
_octreeInboundPacketProcessor->terminating();
|
||||
}
|
||||
|
||||
if (_jurisdictionSender) {
|
||||
_jurisdictionSender->terminating();
|
||||
}
|
||||
|
||||
// Shut down all the send threads
|
||||
for (auto& it : _sendThreads) {
|
||||
auto& sendThread = *it.second;
|
||||
|
|
|
@ -44,7 +44,6 @@ public:
|
|||
bool wantsVerboseDebug() const { return _verboseDebug; }
|
||||
|
||||
OctreePointer getOctree() { return _tree; }
|
||||
JurisdictionMap* getJurisdiction() { return _jurisdiction; }
|
||||
|
||||
int getPacketsPerClientPerInterval() const { return std::min(_packetsPerClientPerInterval,
|
||||
std::max(1, getPacketsTotalPerInterval() / std::max(1, getCurrentClientCount()))); }
|
||||
|
@ -138,7 +137,6 @@ private slots:
|
|||
void domainSettingsRequestComplete();
|
||||
void handleOctreeQueryPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleOctreeDataNackPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleJurisdictionRequestPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleOctreeFileReplacement(QSharedPointer<ReceivedMessage> message);
|
||||
void handleOctreeFileReplacementFromURL(QSharedPointer<ReceivedMessage> message);
|
||||
void removeSendThread();
|
||||
|
@ -190,8 +188,6 @@ protected:
|
|||
bool _debugReceiving;
|
||||
bool _debugTimestampNow;
|
||||
bool _verboseDebug;
|
||||
JurisdictionMap* _jurisdiction;
|
||||
JurisdictionSender* _jurisdictionSender;
|
||||
OctreeInboundPacketProcessor* _octreeInboundPacketProcessor;
|
||||
OctreePersistThread* _persistThread;
|
||||
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
#include <SharedUtil.h>
|
||||
#include <NodeList.h> // for MAX_PACKET_SIZE
|
||||
#include <JurisdictionSender.h>
|
||||
|
||||
const int MAX_FILENAME_LENGTH = 1024;
|
||||
|
||||
|
|
|
@ -79,7 +79,6 @@ EntityScriptServer::EntityScriptServer(ReceivedMessage& message) : ThreadedAssig
|
|||
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
|
||||
packetReceiver.registerListenerForTypes({ PacketType::OctreeStats, PacketType::EntityData, PacketType::EntityErase },
|
||||
this, "handleOctreePacket");
|
||||
packetReceiver.registerListener(PacketType::Jurisdiction, this, "handleJurisdictionPacket");
|
||||
packetReceiver.registerListener(PacketType::SelectedAudioFormat, this, "handleSelectedAudioFormat");
|
||||
|
||||
auto avatarHashMap = DependencyManager::set<AvatarHashMap>();
|
||||
|
@ -283,11 +282,8 @@ void EntityScriptServer::run() {
|
|||
// Setup Script Engine
|
||||
resetEntitiesScriptEngine();
|
||||
|
||||
// we need to make sure that init has been called for our EntityScriptingInterface
|
||||
// so that it actually has a jurisdiction listener when we ask it for it next
|
||||
auto entityScriptingInterface = DependencyManager::get<EntityScriptingInterface>();
|
||||
entityScriptingInterface->init();
|
||||
_entityViewer.setJurisdictionListener(entityScriptingInterface->getJurisdictionListener());
|
||||
|
||||
_entityViewer.init();
|
||||
|
||||
|
@ -566,17 +562,6 @@ void EntityScriptServer::handleOctreePacket(QSharedPointer<ReceivedMessage> mess
|
|||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode) {
|
||||
NodeType_t nodeType;
|
||||
message->peekPrimitive(&nodeType);
|
||||
|
||||
// PacketType_JURISDICTION, first byte is the node type...
|
||||
if (nodeType == NodeType::EntityServer) {
|
||||
DependencyManager::get<EntityScriptingInterface>()->getJurisdictionListener()->
|
||||
queueReceivedPacket(message, senderNode);
|
||||
}
|
||||
}
|
||||
|
||||
void EntityScriptServer::aboutToFinish() {
|
||||
shutdownScriptEngine();
|
||||
|
||||
|
|
|
@ -41,7 +41,6 @@ public slots:
|
|||
|
||||
private slots:
|
||||
void handleOctreePacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleJurisdictionPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
void handleSelectedAudioFormat(QSharedPointer<ReceivedMessage> message);
|
||||
|
||||
void handleReloadEntityServerScriptPacket(QSharedPointer<ReceivedMessage> message, SharedNodePointer senderNode);
|
||||
|
|
35
cmake/externals/crashpad/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
include(ExternalProject)
|
||||
set(EXTERNAL_NAME crashpad)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
|
||||
if (WIN32)
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://backtrace.io/download/crashpad_062317.zip
|
||||
URL_MD5 65817e564b3628492abfc1dbd2a1e98b
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} SOURCE_DIR)
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${SOURCE_DIR}/include CACHE PATH "List of Crashpad include directories")
|
||||
|
||||
set(LIB_EXT "lib")
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_RELEASE ${SOURCE_DIR}/out/Release_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util release library")
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_client.${LIB_EXT} CACHE FILEPATH "Path to Crashpad debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_BASE_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}base.${LIB_EXT} CACHE FILEPATH "Path to Crashpad base debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_UTIL_LIBRARY_DEBUG ${SOURCE_DIR}/out/Debug_x64/lib_MD/${LIB_PREFIX}crashpad_util.${LIB_EXT} CACHE FILEPATH "Path to Crashpad util debug library")
|
||||
|
||||
set(CRASHPAD_HANDLER_EXE_PATH ${SOURCE_DIR}/out/Release_x64/crashpad_handler.exe CACHE FILEPATH "Path to the Crashpad handler executable")
|
||||
endif ()
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
|
@ -1,27 +1,22 @@
|
|||
set(EXTERNAL_NAME glew)
|
||||
|
||||
if (ANDROID)
|
||||
set(ANDROID_CMAKE_ARGS "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}" "-DANDROID_NATIVE_API_LEVEL=19")
|
||||
endif ()
|
||||
set(EXTERNAL_NAME glad32es)
|
||||
|
||||
include(ExternalProject)
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL http://hifi-public.s3.amazonaws.com/dependencies/glew_simple_1.13.0.zip
|
||||
URL_MD5 73f833649e904257b35bf4e84f8bdfb5
|
||||
CONFIGURE_COMMAND CMAKE_ARGS ${ANDROID_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
URL https://hifi-public.s3.amazonaws.com/austin/glad/glad32es.zip
|
||||
URL_MD5 6a641d8c49dee4c895fa59315f5682a6
|
||||
CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glew include directories")
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
if (UNIX)
|
||||
set(LIB_PREFIX "lib")
|
||||
|
@ -30,5 +25,9 @@ elseif (WIN32)
|
|||
set(LIB_EXT "lib")
|
||||
endif ()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glew_d.${LIB_EXT} CACHE FILEPATH "Path to glew debug library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glew.${LIB_EXT} CACHE FILEPATH "Path to glew release library")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glad_d.${LIB_EXT})
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glad.${LIB_EXT})
|
||||
select_library_configurations(${EXTERNAL_NAME_UPPER})
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glad include directories")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE INTERNAL "glad libraries")
|
33
cmake/externals/glad41/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
set(EXTERNAL_NAME glad41)
|
||||
|
||||
include(ExternalProject)
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://hifi-public.s3.amazonaws.com/austin/glad/glad41.zip
|
||||
URL_MD5 1324eeec33abe91e67d19ae551ba624d
|
||||
CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
if (UNIX)
|
||||
set(LIB_PREFIX "lib")
|
||||
set(LIB_EXT "a")
|
||||
elseif (WIN32)
|
||||
set(LIB_EXT "lib")
|
||||
endif ()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glad_d.${LIB_EXT})
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glad.${LIB_EXT})
|
||||
select_library_configurations(${EXTERNAL_NAME_UPPER})
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glad include directories")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE INTERNAL "glad libraries")
|
33
cmake/externals/glad45/CMakeLists.txt
vendored
Normal file
|
@ -0,0 +1,33 @@
|
|||
set(EXTERNAL_NAME glad45)
|
||||
|
||||
include(ExternalProject)
|
||||
include(SelectLibraryConfigurations)
|
||||
|
||||
ExternalProject_Add(
|
||||
${EXTERNAL_NAME}
|
||||
URL https://hifi-public.s3.amazonaws.com/austin/glad/glad45.zip
|
||||
URL_MD5 cfb19b3cb5b2f8f1d1669fb3150e5f05
|
||||
CONFIGURE_COMMAND CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DCMAKE_POSITION_INDEPENDENT_CODE=ON
|
||||
LOG_DOWNLOAD 1
|
||||
LOG_CONFIGURE 1
|
||||
LOG_BUILD 1
|
||||
)
|
||||
|
||||
# Hide this external target (for ide users)
|
||||
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
|
||||
set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
|
||||
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
|
||||
|
||||
if (UNIX)
|
||||
set(LIB_PREFIX "lib")
|
||||
set(LIB_EXT "a")
|
||||
elseif (WIN32)
|
||||
set(LIB_EXT "lib")
|
||||
endif ()
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_DEBUG ${INSTALL_DIR}/lib/${LIB_PREFIX}glad_d.${LIB_EXT})
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY_RELEASE ${INSTALL_DIR}/lib/${LIB_PREFIX}glad.${LIB_EXT})
|
||||
select_library_configurations(${EXTERNAL_NAME_UPPER})
|
||||
|
||||
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glad include directories")
|
||||
set(${EXTERNAL_NAME_UPPER}_LIBRARY ${${EXTERNAL_NAME_UPPER}_LIBRARY} CACHE INTERNAL "glad libraries")
|
|
@ -1,34 +0,0 @@
|
|||
#
|
||||
# AddBugSplat.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Ryan Huffman on 02/09/16.
|
||||
# Copyright 2016 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
macro(add_bugsplat)
|
||||
get_property(BUGSPLAT_CHECKED GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE)
|
||||
|
||||
if (NOT BUGSPLAT_CHECKED)
|
||||
find_package(BugSplat)
|
||||
set_property(GLOBAL PROPERTY CHECKED_FOR_BUGSPLAT_ONCE TRUE)
|
||||
endif ()
|
||||
|
||||
if (BUGSPLAT_FOUND)
|
||||
add_definitions(-DHAS_BUGSPLAT)
|
||||
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${BUGSPLAT_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${BUGSPLAT_LIBRARIES})
|
||||
add_paths_to_fixup_libs(${BUGSPLAT_DLL_PATH})
|
||||
|
||||
add_custom_command(TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_RC_DLL_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/")
|
||||
add_custom_command(TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${BUGSPLAT_EXE_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/")
|
||||
endif ()
|
||||
endmacro()
|
58
cmake/macros/AddCrashpad.cmake
Normal file
|
@ -0,0 +1,58 @@
|
|||
#
|
||||
# AddCrashpad.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Clement Brisset on 01/19/18.
|
||||
# Copyright 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
|
||||
#
|
||||
|
||||
macro(add_crashpad)
|
||||
set (USE_CRASHPAD TRUE)
|
||||
if ("$ENV{CMAKE_BACKTRACE_URL}" STREQUAL "")
|
||||
set(USE_CRASHPAD FALSE)
|
||||
else()
|
||||
set(CMAKE_BACKTRACE_URL $ENV{CMAKE_BACKTRACE_URL})
|
||||
endif()
|
||||
|
||||
if ("$ENV{CMAKE_BACKTRACE_TOKEN}" STREQUAL "")
|
||||
set(USE_CRASHPAD FALSE)
|
||||
else()
|
||||
set(CMAKE_BACKTRACE_TOKEN $ENV{CMAKE_BACKTRACE_TOKEN})
|
||||
endif()
|
||||
|
||||
if (WIN32 AND USE_CRASHPAD)
|
||||
get_property(CRASHPAD_CHECKED GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE)
|
||||
if (NOT CRASHPAD_CHECKED)
|
||||
|
||||
add_dependency_external_projects(crashpad)
|
||||
find_package(crashpad REQUIRED)
|
||||
|
||||
set_property(GLOBAL PROPERTY CHECKED_FOR_CRASHPAD_ONCE TRUE)
|
||||
endif()
|
||||
|
||||
add_definitions(-DHAS_CRASHPAD)
|
||||
add_definitions(-DCMAKE_BACKTRACE_URL=\"${CMAKE_BACKTRACE_URL}\")
|
||||
add_definitions(-DCMAKE_BACKTRACE_TOKEN=\"${CMAKE_BACKTRACE_TOKEN}\")
|
||||
|
||||
target_include_directories(${TARGET_NAME} PRIVATE ${CRASHPAD_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${CRASHPAD_LIBRARY} ${CRASHPAD_BASE_LIBRARY} ${CRASHPAD_UTIL_LIBRARY})
|
||||
|
||||
if (WIN32)
|
||||
set_target_properties(${TARGET_NAME} PROPERTIES LINK_FLAGS "/ignore:4099")
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
TARGET ${TARGET_NAME}
|
||||
POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy ${CRASHPAD_HANDLER_EXE_PATH} "$<TARGET_FILE_DIR:${TARGET_NAME}>/"
|
||||
)
|
||||
install(
|
||||
PROGRAMS ${CRASHPAD_HANDLER_EXE_PATH}
|
||||
DESTINATION ${INTERFACE_INSTALL_DIR}
|
||||
COMPONENT ${CLIENT_COMPONENT}
|
||||
)
|
||||
endif ()
|
||||
endmacro()
|
7
cmake/macros/AddCustomQrcPath.cmake
Normal file
|
@ -0,0 +1,7 @@
|
|||
# adds a custom path and local path to the inserted CUSTOM_PATHS_VAR list which
|
||||
# can be given to the GENERATE_QRC command
|
||||
|
||||
function(ADD_CUSTOM_QRC_PATH CUSTOM_PATHS_VAR IMPORT_PATH LOCAL_PATH)
|
||||
list(APPEND ${CUSTOM_PATHS_VAR} "${IMPORT_PATH}=${LOCAL_PATH}")
|
||||
set(${CUSTOM_PATHS_VAR} ${${CUSTOM_PATHS_VAR}} PARENT_SCOPE)
|
||||
endfunction()
|
|
@ -39,56 +39,41 @@ function(AUTOSCRIBE_SHADER SHADER_FILE)
|
|||
get_filename_component(SHADER_TARGET ${SHADER_FILE} NAME_WE)
|
||||
get_filename_component(SHADER_EXT ${SHADER_FILE} EXT)
|
||||
if(SHADER_EXT STREQUAL .slv)
|
||||
set(SHADER_TARGET ${SHADER_TARGET}_vert.h)
|
||||
set(SHADER_TYPE vert)
|
||||
elseif(${SHADER_EXT} STREQUAL .slf)
|
||||
set(SHADER_TARGET ${SHADER_TARGET}_frag.h)
|
||||
set(SHADER_TYPE frag)
|
||||
elseif(${SHADER_EXT} STREQUAL .slg)
|
||||
set(SHADER_TARGET ${SHADER_TARGET}_geom.h)
|
||||
set(SHADER_TYPE geom)
|
||||
endif()
|
||||
set(SHADER_TARGET ${SHADER_TARGET}_${SHADER_TYPE})
|
||||
|
||||
set(SHADER_TARGET "${SHADERS_DIR}/${SHADER_TARGET}")
|
||||
set(SHADER_TARGET_HEADER ${SHADER_TARGET}.h)
|
||||
set(SHADER_TARGET_SOURCE ${SHADER_TARGET}.cpp)
|
||||
set(SCRIBE_COMMAND scribe)
|
||||
|
||||
# Target dependant Custom rule on the SHADER_FILE
|
||||
if (APPLE)
|
||||
set(GLPROFILE MAC_GL)
|
||||
set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
|
||||
add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE})
|
||||
elseif (ANDROID)
|
||||
set(GLPROFILE LINUX_GL)
|
||||
set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
|
||||
# for an android build, we can't use the scribe that cmake would normally produce as a target,
|
||||
# since it's unrunnable by the cross-compiling build machine
|
||||
|
||||
# so, we require the compiling user to point us at a compiled executable version for their native toolchain
|
||||
if (NOT NATIVE_SCRIBE)
|
||||
find_program(NATIVE_SCRIBE scribe PATHS ${SCRIBE_PATH} ENV SCRIBE_PATH)
|
||||
endif()
|
||||
|
||||
if (NOT NATIVE_SCRIBE)
|
||||
message(FATAL_ERROR "The High Fidelity scribe tool is required for shader pre-processing. \
|
||||
Please compile scribe using your native toolchain and set SCRIBE_PATH to the path containing the scribe executable in your ENV.\
|
||||
")
|
||||
endif ()
|
||||
|
||||
add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND ${NATIVE_SCRIBE} ${SCRIBE_ARGS} DEPENDS ${SHADER_INCLUDE_FILES} ${SHADER_FILE})
|
||||
set(SCRIBE_COMMAND ${NATIVE_SCRIBE})
|
||||
elseif (UNIX)
|
||||
set(GLPROFILE LINUX_GL)
|
||||
set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
|
||||
add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE})
|
||||
else ()
|
||||
set(GLPROFILE PC_GL)
|
||||
set(SCRIBE_ARGS -c++ -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
|
||||
add_custom_command(OUTPUT ${SHADER_TARGET} COMMAND scribe ${SCRIBE_ARGS} DEPENDS scribe ${SHADER_INCLUDE_FILES} ${SHADER_FILE})
|
||||
endif()
|
||||
set(SCRIBE_ARGS -c++ -T ${SHADER_TYPE} -D GLPROFILE ${GLPROFILE} ${SCRIBE_INCLUDES} -o ${SHADER_TARGET} ${SHADER_FILE})
|
||||
add_custom_command(
|
||||
OUTPUT ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE}
|
||||
COMMAND ${SCRIBE_COMMAND} ${SCRIBE_ARGS}
|
||||
DEPENDS ${SCRIBE_COMMAND} ${SHADER_INCLUDE_FILES} ${SHADER_FILE}
|
||||
)
|
||||
|
||||
#output the generated file name
|
||||
set(AUTOSCRIBE_SHADER_RETURN ${SHADER_TARGET} PARENT_SCOPE)
|
||||
set(AUTOSCRIBE_SHADER_RETURN ${SHADER_TARGET_HEADER} ${SHADER_TARGET_SOURCE} PARENT_SCOPE)
|
||||
|
||||
file(GLOB INCLUDE_FILES ${SHADER_TARGET})
|
||||
file(GLOB INCLUDE_FILES ${SHADER_TARGET_HEADER})
|
||||
|
||||
endfunction()
|
||||
|
||||
|
@ -126,7 +111,7 @@ macro(AUTOSCRIBE_SHADER_LIB)
|
|||
if (WIN32)
|
||||
source_group("Shaders" FILES ${SHADER_INCLUDE_FILES})
|
||||
source_group("Shaders" FILES ${SHADER_SOURCE_FILES})
|
||||
source_group("Shaders" FILES ${AUTOSCRIBE_SHADER_SRC})
|
||||
source_group("Shaders\\generated" FILES ${AUTOSCRIBE_SHADER_SRC})
|
||||
endif()
|
||||
|
||||
list(APPEND AUTOSCRIBE_SHADER_LIB_SRC ${SHADER_INCLUDE_FILES})
|
||||
|
@ -136,4 +121,7 @@ macro(AUTOSCRIBE_SHADER_LIB)
|
|||
# Link library shaders, if they exist
|
||||
include_directories("${SHADERS_DIR}")
|
||||
|
||||
# Add search directory to find gpu/Shader.h
|
||||
include_directories("${HIFI_LIBRARY_DIR}/gpu/src")
|
||||
|
||||
endmacro()
|
||||
|
|
14
cmake/macros/FindNPM.cmake
Normal file
|
@ -0,0 +1,14 @@
|
|||
#
|
||||
# FindNPM.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Thijs Wenker on 01/23/18.
|
||||
# Copyright 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
|
||||
#
|
||||
|
||||
macro(find_npm)
|
||||
find_program(NPM_EXECUTABLE "npm")
|
||||
endmacro()
|
|
@ -1,7 +1,7 @@
|
|||
|
||||
function(GENERATE_QRC)
|
||||
set(oneValueArgs OUTPUT PREFIX PATH)
|
||||
set(multiValueArgs GLOBS)
|
||||
set(multiValueArgs CUSTOM_PATHS GLOBS)
|
||||
cmake_parse_arguments(GENERATE_QRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
|
||||
if ("${GENERATE_QRC_PREFIX}" STREQUAL "")
|
||||
set(QRC_PREFIX_PATH /)
|
||||
|
@ -12,9 +12,21 @@ function(GENERATE_QRC)
|
|||
foreach(GLOB ${GENERATE_QRC_GLOBS})
|
||||
file(GLOB_RECURSE FOUND_FILES RELATIVE ${GENERATE_QRC_PATH} ${GLOB})
|
||||
foreach(FILENAME ${FOUND_FILES})
|
||||
if (${FILENAME} MATCHES "^\\.\\.")
|
||||
continue()
|
||||
endif()
|
||||
list(APPEND ALL_FILES "${GENERATE_QRC_PATH}/${FILENAME}")
|
||||
set(QRC_CONTENTS "${QRC_CONTENTS}<file alias=\"${FILENAME}\">${GENERATE_QRC_PATH}/${FILENAME}</file>\n")
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
|
||||
foreach(CUSTOM_PATH ${GENERATE_QRC_CUSTOM_PATHS})
|
||||
string(REPLACE "=" ";" CUSTOM_PATH ${CUSTOM_PATH})
|
||||
list(GET CUSTOM_PATH 0 IMPORT_PATH)
|
||||
list(GET CUSTOM_PATH 1 LOCAL_PATH)
|
||||
set(QRC_CONTENTS "${QRC_CONTENTS}<file alias=\"${LOCAL_PATH}\">${IMPORT_PATH}</file>\n")
|
||||
endforeach()
|
||||
|
||||
set(GENERATE_QRC_DEPENDS ${ALL_FILES} PARENT_SCOPE)
|
||||
configure_file("${HF_CMAKE_DIR}/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT})
|
||||
endfunction()
|
||||
|
|
38
cmake/macros/TargetGlad.cmake
Normal file
|
@ -0,0 +1,38 @@
|
|||
#
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
# Created by Bradley Austin Davis on 2015/10/10
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_GLAD)
|
||||
if (ANDROID)
|
||||
set(INSTALL_DIR ${HIFI_ANDROID_PRECOMPILED}/glad)
|
||||
set(GLAD_INCLUDE_DIRS "${INSTALL_DIR}/include")
|
||||
set(GLAD_LIBRARY_DEBUG ${INSTALL_DIR}/lib/libglad_d.a)
|
||||
set(GLAD_LIBRARY_RELEASE ${INSTALL_DIR}/lib/libglad.a)
|
||||
select_library_configurations(GLAD)
|
||||
find_library(EGL EGL)
|
||||
target_link_libraries(${TARGET_NAME} ${EGL})
|
||||
else()
|
||||
if (USE_GLES)
|
||||
set(GLAD_VER "32es")
|
||||
else()
|
||||
set(GLAD_VER "45")
|
||||
endif()
|
||||
find_package(OpenGL REQUIRED)
|
||||
list(APPEND GLAD_EXTRA_LIBRARIES ${OPENGL_LIBRARY})
|
||||
if (NOT WIN32)
|
||||
list(APPEND GLAD_EXTRA_LIBRARIES dl)
|
||||
endif()
|
||||
set(GLAD "glad${GLAD_VER}")
|
||||
string(TOUPPER ${GLAD} GLAD_UPPER)
|
||||
add_dependency_external_projects(${GLAD})
|
||||
set(GLAD_INCLUDE_DIRS ${${GLAD_UPPER}_INCLUDE_DIRS})
|
||||
set(GLAD_LIBRARY ${${GLAD_UPPER}_LIBRARY})
|
||||
endif()
|
||||
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLAD_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${GLAD_LIBRARY})
|
||||
target_link_libraries(${TARGET_NAME} ${GLAD_EXTRA_LIBRARIES})
|
||||
endmacro()
|
|
@ -1,16 +0,0 @@
|
|||
#
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
# Created by Bradley Austin Davis on 2015/10/10
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_GLEW)
|
||||
if (NOT ANDROID)
|
||||
add_definitions(-DGLEW_STATIC)
|
||||
add_dependency_external_projects(glew)
|
||||
find_package(GLEW REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLEW_INCLUDE_DIRS})
|
||||
target_link_libraries(${TARGET_NAME} ${GLEW_LIBRARY})
|
||||
endif()
|
||||
endmacro()
|
|
@ -6,7 +6,11 @@
|
|||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_GLM)
|
||||
add_dependency_external_projects(glm)
|
||||
find_package(GLM REQUIRED)
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
|
||||
if (ANDROID)
|
||||
set(GLM_INCLUDE_DIRS "${HIFI_ANDROID_PRECOMPILED}/glm/include")
|
||||
else()
|
||||
add_dependency_external_projects(glm)
|
||||
find_package(GLM REQUIRED)
|
||||
endif()
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${GLM_INCLUDE_DIRS})
|
||||
endmacro()
|
|
@ -6,20 +6,6 @@
|
|||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
macro(TARGET_OPENGL)
|
||||
if (APPLE)
|
||||
# link in required OS X frameworks and include the right GL headers
|
||||
find_library(OpenGL OpenGL)
|
||||
target_link_libraries(${TARGET_NAME} ${OpenGL})
|
||||
elseif(ANDROID)
|
||||
target_link_libraries(${TARGET_NAME} GLESv3 EGL)
|
||||
else()
|
||||
find_package(OpenGL REQUIRED)
|
||||
if (${OPENGL_INCLUDE_DIR})
|
||||
include_directories(SYSTEM "${OPENGL_INCLUDE_DIR}")
|
||||
endif()
|
||||
target_link_libraries(${TARGET_NAME} "${OPENGL_LIBRARY}")
|
||||
target_include_directories(${TARGET_NAME} PUBLIC ${OPENGL_INCLUDE_DIR})
|
||||
endif()
|
||||
target_glad()
|
||||
target_nsight()
|
||||
target_glew()
|
||||
endmacro()
|
||||
|
|
|
@ -1,30 +0,0 @@
|
|||
#
|
||||
# FindBugSplat.cmake
|
||||
# cmake/modules
|
||||
#
|
||||
# Created by Ryan Huffman on 02/09/16.
|
||||
# Copyright 2016 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http:#www.apache.org/licenses/LICENSE-2.0.html
|
||||
#
|
||||
|
||||
if (WIN32)
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("BugSplat")
|
||||
|
||||
find_path(BUGSPLAT_INCLUDE_DIRS NAMES BugSplat.h PATH_SUFFIXES inc HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
|
||||
find_library(BUGSPLAT_LIBRARY_RELEASE "BugSplat64.lib" PATH_SUFFIXES "lib64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
find_path(BUGSPLAT_DLL_PATH NAMES "BugSplat64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
find_file(BUGSPLAT_RC_DLL_PATH NAMES "BugSplatRc64.dll" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
find_file(BUGSPLAT_EXE_PATH NAMES "BsSndRpt64.exe" PATH_SUFFIXES "bin64" HINTS ${BUGSPLAT_SEARCH_DIRS})
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(BUGSPLAT)
|
||||
|
||||
set(BUGSPLAT_LIBRARIES ${BUGSPLAT_LIBRARY_RELEASE})
|
||||
endif ()
|
||||
|
||||
set(BUGSPLAT_REQUIREMENTS BUGSPLAT_INCLUDE_DIRS BUGSPLAT_LIBRARIES BUGSPLAT_DLL_PATH BUGSPLAT_RC_DLL_PATH BUGSPLAT_EXE_PATH)
|
||||
find_package_handle_standard_args(BugSplat DEFAULT_MSG ${BUGSPLAT_REQUIREMENTS})
|
41
cmake/modules/FindCrashpad.cmake
Normal file
|
@ -0,0 +1,41 @@
|
|||
#
|
||||
# FindCrashpad.cmake
|
||||
#
|
||||
# Try to find Crashpad libraries and include path.
|
||||
# Once done this will define
|
||||
#
|
||||
# CRASHPAD_FOUND
|
||||
# CRASHPAD_INCLUDE_DIRS
|
||||
# CRASHPAD_LIBRARY
|
||||
# CRASHPAD_BASE_LIBRARY
|
||||
# CRASHPAD_UTIL_LIBRARY
|
||||
#
|
||||
# Created on 01/19/2018 by Clement Brisset
|
||||
# Copyright 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
|
||||
#
|
||||
|
||||
include("${MACRO_DIR}/HifiLibrarySearchHints.cmake")
|
||||
hifi_library_search_hints("crashpad")
|
||||
|
||||
find_path(CRASHPAD_INCLUDE_DIRS base/macros.h PATH_SUFFIXES include HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
find_library(CRASHPAD_LIBRARY_RELEASE crashpad PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_BASE_LIBRARY_RELEASE base PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_UTIL_LIBRARY_RELEASE util PATH_SUFFIXES "Release_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
find_library(CRASHPAD_LIBRARY_DEBUG crashpad PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_BASE_LIBRARY_DEBUG base PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
find_library(CRASHPAD_UTIL_LIBRARY_DEBUG util PATH_SUFFIXES "Debug_x64/lib_MD" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
find_file(CRASHPAD_HANDLER_EXE_PATH NAME "crashpad_handler.exe" PATH_SUFFIXES "Release_x64" HINTS ${CRASHPAD_SEARCH_DIRS})
|
||||
|
||||
include(SelectLibraryConfigurations)
|
||||
select_library_configurations(CRASHPAD)
|
||||
select_library_configurations(CRASHPAD_BASE)
|
||||
select_library_configurations(CRASHPAD_UTIL)
|
||||
|
||||
include(FindPackageHandleStandardArgs)
|
||||
find_package_handle_standard_args(CRASHPAD DEFAULT_MSG CRASHPAD_INCLUDE_DIRS CRASHPAD_LIBRARY CRASHPAD_BASE_LIBRARY CRASHPAD_UTIL_LIBRARY)
|
|
@ -823,6 +823,7 @@ Section "-Core installation"
|
|||
Delete "$INSTDIR\server-console.exe"
|
||||
RMDir /r "$INSTDIR\locales"
|
||||
RMDir /r "$INSTDIR\resources\app"
|
||||
RMDir /r "$INSTDIR\client"
|
||||
Delete "$INSTDIR\resources\atom.asar"
|
||||
Delete "$INSTDIR\build-info.json"
|
||||
Delete "$INSTDIR\content_resources_200_percent.pak"
|
||||
|
|
|
@ -1,12 +1,6 @@
|
|||
{
|
||||
"version": 2.0,
|
||||
"version": 2.1,
|
||||
"settings": [
|
||||
{
|
||||
"name": "label",
|
||||
"label": "Label",
|
||||
"settings": [
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "metaverse",
|
||||
"label": "Metaverse / Networking",
|
||||
|
@ -15,7 +9,8 @@
|
|||
"name": "access_token",
|
||||
"label": "Access Token",
|
||||
"help": "This is your OAuth access token to connect this domain-server with your High Fidelity account. <br/>It can be generated by clicking the 'Connect Account' button above.<br/>You can also go to the <a href='https://metaverse.highfidelity.com/user/security' target='_blank'>My Security</a> page of your account and generate a token with the 'domains' scope and paste it here.",
|
||||
"advanced": true
|
||||
"advanced": true,
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "id",
|
||||
|
@ -55,8 +50,8 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"label": "Places / Paths",
|
||||
"html_id": "places_paths",
|
||||
"label": "Paths",
|
||||
"html_id": "paths",
|
||||
"restart": false,
|
||||
"settings": [
|
||||
{
|
||||
|
@ -64,6 +59,7 @@
|
|||
"label": "Paths",
|
||||
"help": "Clients can enter a path to reach an exact viewpoint in your domain.<br/>Add rows to the table below to map a path to a viewpoint.<br/>The index path ( / ) is where clients will enter if they do not enter an explicit path.",
|
||||
"type": "table",
|
||||
"content_setting": true,
|
||||
"can_add_new_rows": true,
|
||||
"key": {
|
||||
"name": "path",
|
||||
|
@ -164,7 +160,8 @@
|
|||
{
|
||||
"name": "http_username",
|
||||
"label": "HTTP Username",
|
||||
"help": "Username used for basic HTTP authentication."
|
||||
"help": "Username used for basic HTTP authentication.",
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "http_password",
|
||||
|
@ -172,7 +169,8 @@
|
|||
"type": "password",
|
||||
"help": "Password used for basic HTTP authentication. Leave this alone if you do not want to change it.",
|
||||
"password_placeholder": "******",
|
||||
"value-hidden": true
|
||||
"value-hidden": true,
|
||||
"backup": false
|
||||
},
|
||||
{
|
||||
"name": "verify_http_password",
|
||||
|
@ -935,6 +933,7 @@
|
|||
"name": "persistent_scripts",
|
||||
"type": "table",
|
||||
"label": "Persistent Scripts",
|
||||
"content_setting": true,
|
||||
"help": "Add the URLs for scripts that you would like to ensure are always running in your domain.",
|
||||
"can_add_new_rows": true,
|
||||
"columns": [
|
||||
|
@ -955,99 +954,6 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "asset_server",
|
||||
"label": "Asset Server (ATP)",
|
||||
"assignment-types": [ 3 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "enabled",
|
||||
"type": "checkbox",
|
||||
"label": "Enabled",
|
||||
"help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)",
|
||||
"default": true,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "assets_path",
|
||||
"type": "string",
|
||||
"label": "Assets Path",
|
||||
"help": "The path to the directory assets are stored in.<br/>If this path is relative, it will be relative to the application data directory.<br/>If you change this path you will need to manually copy any existing assets from the previous directory.",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "assets_filesize_limit",
|
||||
"type": "int",
|
||||
"label": "File Size Limit",
|
||||
"help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.",
|
||||
"default": 0,
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entity_script_server",
|
||||
"label": "Entity Script Server (ESS)",
|
||||
"assignment-types": [ 5 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "entity_pps_per_script",
|
||||
"label": "Entity PPS per script",
|
||||
"help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.<br/>Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.",
|
||||
"default": 900,
|
||||
"type": "int",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "max_total_entity_pps",
|
||||
"label": "Maximum Total Entity PPS",
|
||||
"help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.<br/>Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.",
|
||||
"default": 9000,
|
||||
"type": "int",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "avatars",
|
||||
"label": "Avatars",
|
||||
"assignment-types": [ 1, 2 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "min_avatar_scale",
|
||||
"type": "double",
|
||||
"label": "Minimum Avatar Scale",
|
||||
"help": "Limits the scale of avatars in your domain. Must be at least 0.005.",
|
||||
"placeholder": 0.25,
|
||||
"default": 0.25
|
||||
},
|
||||
{
|
||||
"name": "max_avatar_scale",
|
||||
"type": "double",
|
||||
"label": "Maximum Avatar Scale",
|
||||
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1000.",
|
||||
"placeholder": 3.0,
|
||||
"default": 3.0
|
||||
},
|
||||
{
|
||||
"name": "avatar_whitelist",
|
||||
"label": "Avatars Allowed from:",
|
||||
"help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "replacement_avatar",
|
||||
"label": "Replacement Avatar for disallowed avatars",
|
||||
"help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "audio_threading",
|
||||
"label": "Audio Threading",
|
||||
|
@ -1080,6 +986,7 @@
|
|||
"name": "attenuation_per_doubling_in_distance",
|
||||
"label": "Default Domain Attenuation",
|
||||
"help": "Factor between 0 and 1.0 (0: No attenuation, 1.0: extreme attenuation)",
|
||||
"content_setting": true,
|
||||
"placeholder": "0.5",
|
||||
"default": "0.5",
|
||||
"advanced": false
|
||||
|
@ -1105,6 +1012,7 @@
|
|||
"label": "Zones",
|
||||
"help": "In this table you can define a set of zones in which you can specify various audio properties.",
|
||||
"numbered": false,
|
||||
"content_setting": true,
|
||||
"can_add_new_rows": true,
|
||||
"key": {
|
||||
"name": "name",
|
||||
|
@ -1155,6 +1063,7 @@
|
|||
"type": "table",
|
||||
"label": "Attenuation Coefficients",
|
||||
"help": "In this table you can set custom attenuation coefficients between audio zones",
|
||||
"content_setting": true,
|
||||
"numbered": true,
|
||||
"can_order": true,
|
||||
"can_add_new_rows": true,
|
||||
|
@ -1185,6 +1094,7 @@
|
|||
"label": "Reverb Settings",
|
||||
"help": "In this table you can set reverb levels for audio zones. For a medium-sized (e.g., 100 square meter) meeting room, try a decay time of around 1.5 seconds and a wet/dry mix of 25%. For an airplane hangar or cathedral, try a decay time of 4 seconds and a wet/dry mix of 50%.",
|
||||
"numbered": true,
|
||||
"content_setting": true,
|
||||
"can_add_new_rows": true,
|
||||
"columns": [
|
||||
{
|
||||
|
@ -1266,9 +1176,82 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "avatars",
|
||||
"label": "Avatars",
|
||||
"assignment-types": [ 1, 2 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "min_avatar_height",
|
||||
"type": "double",
|
||||
"label": "Minimum Avatar Height (meters)",
|
||||
"help": "Limits the height of avatars in your domain. Must be at least 0.009.",
|
||||
"placeholder": 0.4,
|
||||
"default": 0.4
|
||||
},
|
||||
{
|
||||
"name": "max_avatar_height",
|
||||
"type": "double",
|
||||
"label": "Maximum Avatar Height (meters)",
|
||||
"help": "Limits the scale of avatars in your domain. Cannot be greater than 1755.",
|
||||
"placeholder": 5.2,
|
||||
"default": 5.2
|
||||
},
|
||||
{
|
||||
"name": "avatar_whitelist",
|
||||
"label": "Avatars Allowed from:",
|
||||
"help": "Comma separated list of URLs (with optional paths) that avatar .fst files are allowed from. If someone attempts to use an avatar with a different domain, it will be rejected and the replacement avatar will be used. If left blank, any domain is allowed.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "replacement_avatar",
|
||||
"label": "Replacement Avatar for disallowed avatars",
|
||||
"help": "A URL for an avatar .fst to be used when someone tries to use an avatar that is not allowed. If left blank, the generic default avatar is used.",
|
||||
"placeholder": "",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "avatar_mixer",
|
||||
"label": "Avatar Mixer",
|
||||
"assignment-types": [
|
||||
1
|
||||
],
|
||||
"settings": [
|
||||
{
|
||||
"name": "max_node_send_bandwidth",
|
||||
"type": "double",
|
||||
"label": "Per-Node Bandwidth",
|
||||
"help": "Desired maximum send bandwidth (in Megabits per second) to each node",
|
||||
"placeholder": 5.0,
|
||||
"default": 5.0,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "auto_threads",
|
||||
"label": "Automatically determine thread count",
|
||||
"type": "checkbox",
|
||||
"help": "Allow system to determine number of threads (recommended)",
|
||||
"default": false,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "num_threads",
|
||||
"label": "Number of Threads",
|
||||
"help": "Threads to spin up for avatar mixing (if not automatically set)",
|
||||
"placeholder": "1",
|
||||
"default": "1",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entity_server_settings",
|
||||
"label": "Entity Server Settings",
|
||||
"label": "Entities",
|
||||
"assignment-types": [
|
||||
6
|
||||
],
|
||||
|
@ -1309,6 +1292,7 @@
|
|||
"name": "entityEditFilter",
|
||||
"label": "Filter Entity Edits",
|
||||
"help": "Check all entity edits against this filter function.",
|
||||
"content_setting": true,
|
||||
"placeholder": "url whose content is like: function filter(properties) { return properties; }",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
|
@ -1503,35 +1487,55 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"name": "avatar_mixer",
|
||||
"label": "Avatar Mixer",
|
||||
"assignment-types": [
|
||||
1
|
||||
],
|
||||
"name": "asset_server",
|
||||
"label": "Asset Server (ATP)",
|
||||
"assignment-types": [ 3 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "max_node_send_bandwidth",
|
||||
"type": "double",
|
||||
"label": "Per-Node Bandwidth",
|
||||
"help": "Desired maximum send bandwidth (in Megabits per second) to each node",
|
||||
"placeholder": 5.0,
|
||||
"default": 5.0,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "auto_threads",
|
||||
"label": "Automatically determine thread count",
|
||||
"name": "enabled",
|
||||
"type": "checkbox",
|
||||
"help": "Allow system to determine number of threads (recommended)",
|
||||
"default": false,
|
||||
"label": "Enabled",
|
||||
"help": "Assigns an asset-server in your domain to serve files to clients via the ATP protocol (over UDP)",
|
||||
"default": true,
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "num_threads",
|
||||
"label": "Number of Threads",
|
||||
"help": "Threads to spin up for avatar mixing (if not automatically set)",
|
||||
"placeholder": "1",
|
||||
"default": "1",
|
||||
"name": "assets_path",
|
||||
"type": "string",
|
||||
"label": "Assets Path",
|
||||
"help": "The path to the directory assets are stored in.<br/>If this path is relative, it will be relative to the application data directory.<br/>If you change this path you will need to manually copy any existing assets from the previous directory.",
|
||||
"default": "",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "assets_filesize_limit",
|
||||
"type": "int",
|
||||
"label": "File Size Limit",
|
||||
"help": "The file size limit of an asset that can be imported into the asset server in MBytes. 0 (default) means no limit on file size.",
|
||||
"default": 0,
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "entity_script_server",
|
||||
"label": "Entity Script Server (ESS)",
|
||||
"assignment-types": [ 5 ],
|
||||
"settings": [
|
||||
{
|
||||
"name": "entity_pps_per_script",
|
||||
"label": "Entity PPS per script",
|
||||
"help": "The number of packets per second (PPS) that can be sent to the entity server for each server entity script. This contributes to a total overall amount.<br/>Example: 1000 PPS with 5 entites gives a total PPS of 5000 that is shared among the entity scripts. A single could use 4000 PPS, leaving 1000 for the rest, for example.",
|
||||
"default": 900,
|
||||
"type": "int",
|
||||
"advanced": true
|
||||
},
|
||||
{
|
||||
"name": "max_total_entity_pps",
|
||||
"label": "Maximum Total Entity PPS",
|
||||
"help": "The maximum total packets per seconds (PPS) that can be sent to the entity server.<br/>Example: 5 scripts @ 1000 PPS per script = 5000 total PPS. A maximum total PPS of 4000 would cap this 5000 total PPS to 4000.",
|
||||
"default": 9000,
|
||||
"type": "int",
|
||||
"advanced": true
|
||||
}
|
||||
]
|
||||
|
|
7
domain-server/resources/web/base-settings-scripts.html
Normal file
|
@ -0,0 +1,7 @@
|
|||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
<script src='/js/form2js.min.js'></script>
|
||||
<script src='/js/bootstrap-switch.min.js'></script>
|
||||
<script src='/js/shared.js'></script>
|
||||
<script src='/js/base-settings.js'></script>
|
59
domain-server/resources/web/base-settings.html
Normal file
|
@ -0,0 +1,59 @@
|
|||
<div class="col-md-10 col-md-offset-1 col-xs-12">
|
||||
<div class="row">
|
||||
|
||||
<div class="col-xs-12">
|
||||
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<span class="alert-link">
|
||||
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form id="settings-form" role="form">
|
||||
<script id="panels-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% if (!group.hidden) { %>
|
||||
|
||||
<% var settings = _.partition(group.settings, function(value, index) { %>
|
||||
<% return !value.deprecated %>
|
||||
<% })[0] %>
|
||||
|
||||
<% var split_settings = _.partition(group.settings, function(value, index) { %>
|
||||
<% return !value.advanced %>
|
||||
<% }) %>
|
||||
|
||||
<% isGrouped = !!group.name %>
|
||||
<% panelID = isGrouped ? group.name : group.html_id %>
|
||||
|
||||
<div id="<%- panelID %>_group" class="anchor"></div>
|
||||
<div class="panel panel-default<%- (isGrouped) ? ' grouped' : '' %>"
|
||||
id="<%- panelID %>">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><%- group.label %></h3>
|
||||
<span class="badge"></span>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, false) %>
|
||||
<% }); %>
|
||||
|
||||
<% if (split_settings[1].length > 0) { %>
|
||||
<button type="button" class="btn btn-default" data-toggle="collapse" data-target="#<%- panelID %>-advanced">Advanced Settings <span class="caret"></span></button>
|
||||
<div id="<%- panelID %>-advanced" class="collapse advanced-settings-section">
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, true) %>
|
||||
<% }); %>
|
||||
</div>
|
||||
<% } %>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</script>
|
||||
<div id="panels"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -1,45 +1,19 @@
|
|||
<!--#include virtual="header.html"-->
|
||||
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="alert" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var Settings = {
|
||||
content_settings: true,
|
||||
endpoint: "/content-settings.json",
|
||||
path: "/content/"
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title">Upload Entities File</h3>
|
||||
</div>
|
||||
<form id="upload-form" action="upload" enctype="multipart/form-data" method="post">
|
||||
<div class="panel-body">
|
||||
<p>
|
||||
Upload an entities file (e.g.: models.json.gz) to replace the content of this domain.<br>
|
||||
Note: <strong>Your domain's content will be replaced by the content you upload</strong>, but the backup files of your domain's content will not immediately be changed.
|
||||
</p>
|
||||
<p>If your domain has any content that you would like to re-use at a later date, save a manual backup of your models.json.gz file, which is usually stored at the following paths:</p>
|
||||
<label class="control-label">Windows</label>
|
||||
<pre>C:/Users/[username]/AppData/Roaming/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<label class="control-label">OSX</label>
|
||||
<pre>/Users/[username]/Library/Application Support/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<label class="control-label">Linux</label>
|
||||
<pre>/home/[username]/.local/share/High Fidelity/assignment-client/entities/models.json.gz</pre>
|
||||
<br>
|
||||
<input type="file" name="entities-file" class="form-control-file" accept=".json, .gz">
|
||||
<br>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<input type="submit" class="btn btn-info" value="Upload">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--#include virtual="base-settings.html"-->
|
||||
|
||||
<!--#include virtual="footer.html"-->
|
||||
<script src='js/content.js'></script>
|
||||
<script src='/js/sweetalert.min.js'></script>
|
||||
|
||||
<!--#include virtual="base-settings-scripts.html"-->
|
||||
|
||||
<script src="js/content.js"></script>
|
||||
|
||||
<!--#include virtual="page-end.html"-->
|
||||
|
|
|
@ -1,14 +1,6 @@
|
|||
$(document).ready(function(){
|
||||
|
||||
function showSpinnerAlert(title) {
|
||||
swal({
|
||||
title: title,
|
||||
text: '<div class="spinner" style="color:black;"><div class="bounce1"></div><div class="bounce2"></div><div class="bounce3"></div></div>',
|
||||
html: true,
|
||||
showConfirmButton: false,
|
||||
allowEscapeKey: false
|
||||
});
|
||||
}
|
||||
Settings.afterReloadActions = function() {};
|
||||
|
||||
var frm = $('#upload-form');
|
||||
frm.submit(function (ev) {
|
||||
|
|
|
@ -18,6 +18,14 @@ body {
|
|||
margin-top: 70px;
|
||||
}
|
||||
|
||||
/* unfortunate hack so that anchors go to the right place with fixed navbar */
|
||||
:target:before {
|
||||
content: " ";
|
||||
display: block;
|
||||
height: 70px;
|
||||
margin-top: -70px;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
|
@ -118,11 +126,6 @@ span.port {
|
|||
margin-top: 10px;
|
||||
}
|
||||
|
||||
#small-save-button {
|
||||
width: 100%;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
td.buttons {
|
||||
width: 30px;
|
||||
}
|
||||
|
@ -345,3 +348,89 @@ table .headers + .headers td {
|
|||
text-align: center;
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
@media (min-width: 768px) {
|
||||
ul.nav li.dropdown-on-hover:hover ul.dropdown-menu {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
ul.nav li.dropdown ul.dropdown-menu {
|
||||
padding: 0px 0px;
|
||||
}
|
||||
|
||||
ul.nav li.dropdown li a {
|
||||
padding-top: 7px;
|
||||
padding-bottom: 7px;
|
||||
}
|
||||
|
||||
ul.nav li.dropdown li a:hover {
|
||||
color: white;
|
||||
background-color: #337ab7;
|
||||
}
|
||||
|
||||
ul.nav li.dropdown ul.dropdown-menu .divider {
|
||||
margin: 0px 0;
|
||||
}
|
||||
|
||||
#visit-domain-link {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.navbar-btn {
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#save-settings-xs-button {
|
||||
float: right;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
#button-bars {
|
||||
display: inline-block;
|
||||
float: left;
|
||||
}
|
||||
|
||||
#hamburger-badge {
|
||||
position: relative;
|
||||
top: -2px;
|
||||
float: left;
|
||||
margin-right: 10px;
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
#restart-server {
|
||||
margin-left: 0px;
|
||||
}
|
||||
|
||||
#restart-server:hover {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.badge {
|
||||
margin-left: 5px;
|
||||
background-color: #00B4EF !important;
|
||||
}
|
||||
|
||||
.panel-title {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#visit-hmd-icon {
|
||||
width: 25px;
|
||||
position: relative;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
.advanced-settings-section {
|
||||
margin-top: 20px;
|
||||
}
|
||||
|
||||
#restore-settings-button {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
/* fix for https://bugs.webkit.org/show_bug.cgi?id=39620 */
|
||||
.save-button-text {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
|
|
@ -17,30 +17,47 @@
|
|||
<div class="container-fluid">
|
||||
<!-- Brand and toggle get grouped for better mobile display -->
|
||||
<div class="navbar-header">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#bs-example-navbar-collapse-1">
|
||||
<button type="button" class="navbar-toggle" data-toggle="collapse" data-target="#collapsed-navbar">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span id="hamburger-badge" class="badge"></span>
|
||||
|
||||
<div id="button-bars">
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</div>
|
||||
</button>
|
||||
|
||||
<button id="save-settings-xs-button" class="save-button btn btn-success navbar-btn hidden-sm hidden-md hidden-lg" disabled="true"><span class="save-button-text">Save</span></button>
|
||||
</div>
|
||||
|
||||
<!-- Collect the nav links, forms, and other content for toggling -->
|
||||
<div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1">
|
||||
<div class="collapse navbar-collapse" id="collapsed-navbar">
|
||||
<ul class="nav navbar-nav">
|
||||
<li><a href="/">Nodes</a></li>
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown">Assignments <span class="caret"></span></a>
|
||||
<ul class="dropdown-menu" role="menu">
|
||||
<li><a href="/assignment">New Assignment</a></li>
|
||||
</ul>
|
||||
<li><a href="/assignment">Assignment</a></li>
|
||||
|
||||
<li class="dropdown dropdown-on-hover">
|
||||
<a href="/content/" class="hidden-xs">Content <span class="content-settings-badge badge"></span> <span class="caret"></span></a>
|
||||
<a href="#" class="dropdown-toggle hidden-sm hidden-md hidden-lg" data-toggle="dropdown">Content <span class="content-badge badge"></span> <span class="caret"></span></a>
|
||||
<ul id="content-settings-nav-dropdown" class="dropdown-menu" role="menu">
|
||||
</ul>
|
||||
</li>
|
||||
|
||||
<li class="dropdown dropdown-on-hover">
|
||||
<a href="/settings/" class="hidden-xs">Settings <span class="domain-settings-badge badge"></span> <span class="caret"></span></a>
|
||||
<a href="#" class="dropdown-toggle hidden-sm hidden-md hidden-lg" data-toggle="dropdown">Settings <span class="domain-settings-badge badge"></span> <span class="caret"></span></a>
|
||||
<ul id="domain-settings-nav-dropdown" class="dropdown-menu" role="menu">
|
||||
</ul>
|
||||
</li>
|
||||
<li><a href="/content/">Content</a></li>
|
||||
<li><a href="/settings/">Settings</a></li>
|
||||
</ul>
|
||||
<ul class="nav navbar-right navbar-nav">
|
||||
<li><a id="visit-domain-link" class="blue-link" target="_blank" style="display: none;">Visit domain in VR</a></li>
|
||||
<li><a href="#" id="restart-server"><span class="glyphicon glyphicon-refresh"></span> Restart</a></li>
|
||||
<a id="visit-domain-link" class="btn btn-default navbar-btn" role="button" target="_blank" style="display: none;">
|
||||
<img id="visit-hmd-icon" src="/images/hmd-w-eyes.svg" alt="Head-mounted display" />
|
||||
Visit in VR
|
||||
</a>
|
||||
<button id="save-settings-button" class="save-button btn btn-success navbar-btn hidden-xs" disabled="true"><span class="save-button-text">Save</span></button>
|
||||
<a href="#" id="restart-server" class="navbar-btn btn btn-link"><span class="glyphicon glyphicon-refresh"></span> Restart</a>
|
||||
</ul>
|
||||
</div>
|
||||
</div><!-- /.container-fluid -->
|
||||
|
@ -50,7 +67,7 @@
|
|||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">domain-server is restarting</h4>
|
||||
<h4 class="modal-title">Domain Server is restarting</h4>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<h5>This page will automatically refresh in <span id="refresh-time">3 seconds</span>.</h5>
|
||||
|
|
13
domain-server/resources/web/images/hmd-w-eyes.svg
Normal file
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 456 244" style="enable-background:new 0 0 456 244;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#666666;}
|
||||
</style>
|
||||
<path class="st0" d="M352.8,3.4h-250C49.6,3.4,6.3,46.2,6.3,98.9v44.9c0,52.7,43.3,95.5,96.5,95.5h67.6c19.4,0,31.9-17.9,42.9-33.6
|
||||
c4.2-6.1,11.1-15.9,14.8-17.9c3.6,2.1,10.4,12.2,14.5,18.4c10.4,15.5,22.1,33.1,40.3,33.1h69.9c53.3,0,96.5-42.9,96.5-95.5V98.9
|
||||
C449.3,46.2,406,3.4,352.8,3.4z M129.6,157.6c-22.4,0-40.6-18.2-40.6-40.6s18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6
|
||||
S151.9,157.6,129.6,157.6z M328.4,157.6c-22.4,0-40.6-18.2-40.6-40.6s18.2-40.6,40.6-40.6c22.4,0,40.6,18.2,40.6,40.6
|
||||
S350.8,157.6,328.4,157.6z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 928 B |
1152
domain-server/resources/web/js/base-settings.js
Normal file
|
@ -23,16 +23,22 @@ function showRestartModal() {
|
|||
}, 1000);
|
||||
}
|
||||
|
||||
function settingsGroupAnchor(base, html_id) {
|
||||
return base + "#" + html_id + "_group"
|
||||
}
|
||||
|
||||
$(document).ready(function(){
|
||||
var url = window.location;
|
||||
|
||||
// Will only work if string in href matches with location
|
||||
$('ul.nav a[href="'+ url +'"]').parent().addClass('active');
|
||||
|
||||
// Will also work for relative and absolute hrefs
|
||||
$('ul.nav a').filter(function() {
|
||||
return this.href == url;
|
||||
}).parent().addClass('active');
|
||||
$('body').on('click', '#restart-server', function(e) {
|
||||
}).parent().addClass('active');
|
||||
|
||||
$('body').on('click', '#restart-server', function(e) {
|
||||
swal( {
|
||||
title: "Are you sure?",
|
||||
text: "This will restart your domain server, causing your domain to be briefly offline.",
|
||||
|
@ -45,4 +51,47 @@ $(document).ready(function(){
|
|||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
var $contentDropdown = $('#content-settings-nav-dropdown');
|
||||
var $settingsDropdown = $('#domain-settings-nav-dropdown');
|
||||
|
||||
// for pages that have the settings dropdowns
|
||||
if ($contentDropdown.length && $settingsDropdown.length) {
|
||||
// make a JSON request to get the dropdown menus for content and settings
|
||||
// we don't error handle here because the top level menu is still clickable and usables if this fails
|
||||
$.getJSON('/settings-menu-groups.json', function(data){
|
||||
function makeGroupDropdownElement(group, base) {
|
||||
var html_id = group.html_id ? group.html_id : group.name;
|
||||
return "<li class='setting-group'><a href='" + settingsGroupAnchor(base, html_id) + "'>" + group.label + "<span class='badge'></span></a></li>";
|
||||
}
|
||||
|
||||
$.each(data.content_settings, function(index, group){
|
||||
if (index > 0) {
|
||||
$contentDropdown.append("<li role='separator' class='divider'></li>");
|
||||
}
|
||||
|
||||
$contentDropdown.append(makeGroupDropdownElement(group, "/content/"));
|
||||
});
|
||||
|
||||
$.each(data.domain_settings, function(index, group){
|
||||
if (index > 0) {
|
||||
$settingsDropdown.append("<li role='separator' class='divider'></li>");
|
||||
}
|
||||
|
||||
$settingsDropdown.append(makeGroupDropdownElement(group, "/settings/"));
|
||||
|
||||
// for domain settings, we add a dummy "Places" group that we fill
|
||||
// via the API - add it to the dropdown menu in the right spot
|
||||
// which is after "Metaverse / Networking"
|
||||
if (group.name == "metaverse") {
|
||||
$settingsDropdown.append("<li role='separator' class='divider'></li>");
|
||||
$settingsDropdown.append(makeGroupDropdownElement({ html_id: 'places', label: 'Places' }, "/settings/"));
|
||||
}
|
||||
});
|
||||
|
||||
// append a link for the "Settings Backup" panel
|
||||
$settingsDropdown.append("<li role='separator' class='divider'></li>");
|
||||
$settingsDropdown.append(makeGroupDropdownElement({ html_id: 'settings_backup', label: 'Settings Backup'}, "/settings"));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
var Settings = {
|
||||
showAdvanced: false,
|
||||
ADVANCED_CLASS: 'advanced-setting',
|
||||
if (typeof Settings === "undefined") {
|
||||
Settings = {};
|
||||
}
|
||||
|
||||
Object.assign(Settings, {
|
||||
DEPRECATED_CLASS: 'deprecated-setting',
|
||||
TRIGGER_CHANGE_CLASS: 'trigger-change',
|
||||
DATA_ROW_CLASS: 'value-row',
|
||||
|
@ -41,7 +43,7 @@ var Settings = {
|
|||
FORM_ID: 'settings-form',
|
||||
INVALID_ROW_CLASS: 'invalid-input',
|
||||
DATA_ROW_INDEX: 'data-row-index'
|
||||
};
|
||||
});
|
||||
|
||||
var URLs = {
|
||||
// STABLE METAVERSE_URL: https://metaverse.highfidelity.com
|
||||
|
@ -95,7 +97,13 @@ var DOMAIN_ID_TYPE_FULL = 2;
|
|||
var DOMAIN_ID_TYPE_UNKNOWN = 3;
|
||||
|
||||
function domainIDIsSet() {
|
||||
return Settings.data.values.metaverse.id.length > 0;
|
||||
if (typeof Settings.data.values.metaverse !== 'undefined' &&
|
||||
typeof Settings.data.values.metaverse.id !== 'undefined') {
|
||||
|
||||
return Settings.data.values.metaverse.id.length > 0;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentDomainIDType() {
|
||||
|
@ -156,11 +164,12 @@ function getDomainFromAPI(callback) {
|
|||
if (callback === undefined) {
|
||||
callback = function() {};
|
||||
}
|
||||
|
||||
var domainID = Settings.data.values.metaverse.id;
|
||||
if (domainID === null || domainID === undefined || domainID === '') {
|
||||
|
||||
if (!domainIDIsSet()) {
|
||||
callback({ status: 'fail' });
|
||||
return null;
|
||||
} else {
|
||||
var domainID = Settings.data.values.metaverse.id;
|
||||
}
|
||||
|
||||
pendingDomainRequest = $.ajax({
|
||||
|
|
|
@ -1,104 +1,29 @@
|
|||
<!--#include virtual="header.html"-->
|
||||
|
||||
<div class="col-md-10 col-md-offset-1">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="alert" style="display:none;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<script type="text/javascript">
|
||||
var Settings = {
|
||||
content_settings: false,
|
||||
endpoint: "/settings.json",
|
||||
path: "/settings/"
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-3 col-sm-3" id="setup-sidebar-col">
|
||||
<div id="setup-sidebar" data-clampedwidth="#setup-sidebar-col">
|
||||
<script id="list-group-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% if (!group.hidden) { %>
|
||||
<% panelID = group.name ? group.name : group.html_id %>
|
||||
<li>
|
||||
<a href="#<%- panelID %>" class="list-group-item">
|
||||
<span class="badge"></span>
|
||||
<%- group.label %>
|
||||
</a>
|
||||
</li>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</script>
|
||||
|
||||
<ul class="nav nav-pills nav-stacked">
|
||||
</ul>
|
||||
|
||||
<button id="advanced-toggle-button" class="btn btn-info advanced-toggle">Show advanced</button>
|
||||
<button class="btn btn-success save-button" disabled>Save</button>
|
||||
<div id="manage-cloud-domains-link" style="display: none;">
|
||||
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Manage Cloud Hosted Domains</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-md-9 col-sm-9 col-xs-12">
|
||||
<div class="col-md-10 col-md-offset-1 col-xs-12">
|
||||
<div class="row">
|
||||
<div class="col-xs-12">
|
||||
|
||||
<div id="cloud-domains-alert" class="alert alert-info alert-dismissible" role="alert" style="display: none;">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">×</span></button>
|
||||
<span class="alert-link">
|
||||
<a href="https://highfidelity.com/user/cloud_domains" target="_blank" class="blue-link">Visit Cloud Hosted Domains</a> to manage all your cloud domains
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<form id="settings-form" role="form">
|
||||
|
||||
<script id="panels-template" type="text/template">
|
||||
<% _.each(descriptions, function(group){ %>
|
||||
<% if (!group.hidden) { %>
|
||||
<% var settings = _.partition(group.settings, function(value, index) { return !value.deprecated })[0] %>
|
||||
<% split_settings = _.partition(settings, function(value, index) { return !value.advanced }) %>
|
||||
<% isAdvanced = _.isEmpty(split_settings[0]) && !_.isEmpty(split_settings[1]) %>
|
||||
<% if (isAdvanced) { %>
|
||||
<% $("a[href=#" + group.name + "]").addClass('advanced-setting').hide() %>
|
||||
<% } %>
|
||||
|
||||
<% isGrouped = !!group.name %>
|
||||
<% panelID = isGrouped ? group.name : group.html_id %>
|
||||
|
||||
<div class="panel panel-default<%- (isAdvanced) ? ' advanced-setting' : '' %><%- (isGrouped) ? ' grouped' : '' %>"
|
||||
id="<%- panelID %>">
|
||||
<div class="panel-heading">
|
||||
<h3 class="panel-title"><%- group.label %></h3>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<% _.each(split_settings[0], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, false) %>
|
||||
<% }); %>
|
||||
<% if (!_.isEmpty(split_settings[1])) { %>
|
||||
<% $("#advanced-toggle-button").show() %>
|
||||
<% _.each(split_settings[1], function(setting) { %>
|
||||
<% keypath = isGrouped ? group.name + "." + setting.name : setting.name %>
|
||||
<%= getFormGroup(keypath, setting, values, true) %>
|
||||
<% }); %>
|
||||
<% }%>
|
||||
</div>
|
||||
</div>
|
||||
<% } %>
|
||||
<% }); %>
|
||||
</script>
|
||||
<div id="panels"></div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="col-xs-12 hidden-sm hidden-md hidden-lg">
|
||||
<button class="btn btn-success save-button" id="small-save-button">Save</button>
|
||||
</div>
|
||||
<div class="alert alert-info">Your domain content settings are now available in <a href='/content/'>Content</a></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!--#include virtual="base-settings.html"-->
|
||||
|
||||
<!--#include virtual="footer.html"-->
|
||||
<script src='/js/underscore-min.js'></script>
|
||||
<script src='/js/underscore-keypath.min.js'></script>
|
||||
<script src='/js/bootbox.min.js'></script>
|
||||
|
||||
<script src='/js/sha256.js'></script>
|
||||
<script src='js/form2js.min.js'></script>
|
||||
<script src='js/bootstrap-switch.min.js'></script>
|
||||
<script src='/js/shared.js'></script>
|
||||
<script src='js/settings.js'></script>
|
||||
|
||||
<!--#include virtual="base-settings-scripts.html"-->
|
||||
|
||||
<script src="js/settings.js"></script>
|
||||
|
||||
<!--#include virtual="page-end.html"-->
|
||||
|
|