1
.gitignore
vendored
|
@ -92,6 +92,7 @@ npm-debug.log
|
|||
|
||||
# Resource binary file
|
||||
interface/compiledResources
|
||||
*.rcc
|
||||
|
||||
# GPUCache
|
||||
interface/resources/GPUCache/*
|
||||
|
|
|
@ -12,7 +12,23 @@
|
|||
}
|
||||
@end
|
||||
|
||||
|
||||
void redirectLogToDocuments()
|
||||
{
|
||||
NSString* filePath = [[NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0]
|
||||
stringByAppendingString:@"/Launcher/"];
|
||||
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
|
||||
NSError * error = nil;
|
||||
[[NSFileManager defaultManager] createDirectoryAtPath:filePath withIntermediateDirectories:TRUE attributes:nil error:&error];
|
||||
}
|
||||
NSString *pathForLog = [filePath stringByAppendingPathComponent:@"log.txt"];
|
||||
|
||||
freopen([pathForLog cStringUsingEncoding:NSASCIIStringEncoding],"a+",stderr);
|
||||
}
|
||||
|
||||
int main(int argc, char const* argv[]) {
|
||||
redirectLogToDocuments();
|
||||
if (argc < 3) {
|
||||
NSLog(@"Error: wrong number of arguments");
|
||||
return 0;
|
||||
|
@ -50,6 +66,7 @@ int main(int argc, char const* argv[]) {
|
|||
options:NSWorkspaceLaunchNewInstance
|
||||
configuration:configuration
|
||||
error:&error];
|
||||
|
||||
if (error != nil) {
|
||||
NSLog(@"couldn't start launcher: %@", error);
|
||||
return 1;
|
||||
|
|
290
launchers/qt/CMakeLists.txt
Normal file
|
@ -0,0 +1,290 @@
|
|||
cmake_minimum_required(VERSION 3.0)
|
||||
if (APPLE)
|
||||
set(ENV{MACOSX_DEPLOYMENT_TARGET} 10.10)
|
||||
endif()
|
||||
project(HQLauncher)
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/Modules")
|
||||
include("cmake/macros/SetPackagingParameters.cmake")
|
||||
set(CMAKE_CXX_STANDARD 14)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
include("cmake/init.cmake")
|
||||
include("cmake/macros/SetPackagingParameters.cmake")
|
||||
|
||||
if(MSVC)
|
||||
add_definitions(-D_CRT_SECURE_NO_WARNINGS)
|
||||
|
||||
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
|
||||
set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} /MT")
|
||||
set(CMAKE_CXX_FLAGS_MINSIZEREL "${CMAKE_CXX_FLAGS_MINSIZEREL} /MT")
|
||||
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
|
||||
endif()
|
||||
|
||||
|
||||
function(set_from_env _RESULT_NAME _ENV_VAR_NAME _DEFAULT_VALUE)
|
||||
if (NOT DEFINED ${_RESULT_NAME})
|
||||
if ("$ENV{${_ENV_VAR_NAME}}" STREQUAL "")
|
||||
set (${_RESULT_NAME} ${_DEFAULT_VALUE} PARENT_SCOPE)
|
||||
else()
|
||||
set (${_RESULT_NAME} $ENV{${_ENV_VAR_NAME}} PARENT_SCOPE)
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
include(ExternalProject)
|
||||
|
||||
if (APPLE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS "-framework Cocoa -framework CoreServices -framework Carbon -framework IOKit -framework Security -framework SystemConfiguration")
|
||||
add_compile_options(-W -Wall -Wextra -Wpedantic)
|
||||
endif()
|
||||
if (WIN32)
|
||||
|
||||
ExternalProject_Add(
|
||||
qtlite
|
||||
URL "https://public.highfidelity.com/dependencies/qtlite/qt-lite-5.9.9-win-oct-15-2019.zip"
|
||||
URL_HASH MD5=0176277bca37d219e83738caf3a698eb
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
|
||||
ExternalProject_Get_Property(qtlite SOURCE_DIR)
|
||||
ExternalProject_Get_Property(qtlite STAMP_DIR)
|
||||
|
||||
include("${STAMP_DIR}/download-qtlite.cmake")
|
||||
include("${STAMP_DIR}/extract-qtlite.cmake")
|
||||
include("${STAMP_DIR}/verify-qtlite.cmake")
|
||||
|
||||
message("${SOURCE_DIR}/lib/cmake")
|
||||
|
||||
list(APPEND CMAKE_PREFIX_PATH ${SOURCE_DIR}/lib/cmake)
|
||||
|
||||
set(SSL_DIR ${SOURCE_DIR}/ssl)
|
||||
set(OPENSSL_ROOT_DIR ${SSL_DIR})
|
||||
message("SSL dir is ${SSL_DIR}")
|
||||
set(OPENSSL_USE_STATIC_LIBS TRUE)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
|
||||
message("-- Found OpenSSL Libs ${OPENSSL_LIBRARIES}")
|
||||
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
ExternalProject_Add(
|
||||
qtlite
|
||||
URL "https://public.highfidelity.com/dependencies/qtlite/qt-lite-5.9.9-mac.zip"
|
||||
URL_HASH MD5=0cd78d40e5f539a7e314cf99b6cae0d0
|
||||
CONFIGURE_COMMAND ""
|
||||
BUILD_COMMAND ""
|
||||
INSTALL_COMMAND ""
|
||||
LOG_DOWNLOAD 1
|
||||
)
|
||||
|
||||
|
||||
ExternalProject_Get_Property(qtlite SOURCE_DIR)
|
||||
ExternalProject_Get_Property(qtlite STAMP_DIR)
|
||||
|
||||
include("${STAMP_DIR}/download-qtlite.cmake")
|
||||
include("${STAMP_DIR}/extract-qtlite.cmake")
|
||||
include("${STAMP_DIR}/verify-qtlite.cmake")
|
||||
|
||||
message("${SOURCE_DIR}/lib/cmake")
|
||||
|
||||
list(APPEND CMAKE_PREFIX_PATH ${SOURCE_DIR}/lib/cmake)
|
||||
|
||||
set(SSL_DIR ${SOURCE_DIR}/ssl)
|
||||
set(OPENSSL_ROOT_DIR ${SSL_DIR})
|
||||
message("SSL dir is ${SSL_DIR}")
|
||||
endif()
|
||||
|
||||
if (APPLE)
|
||||
set(OPENSSL_USE_STATIC_LIBS TRUE)
|
||||
find_package(OpenSSL REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(Qt5 COMPONENTS Core Gui Qml Quick QuickControls2 Network REQUIRED)
|
||||
find_package(OpenGL REQUIRED)
|
||||
find_package(QtStaticDeps REQUIRED)
|
||||
|
||||
set(CUSTOM_LAUNCHER_QRC_PATHS "")
|
||||
set(RESOURCES_QRC ${CMAKE_CURRENT_BINARY_DIR}/resources.qrc)
|
||||
set(RESOURCES_RCC ${CMAKE_CURRENT_SOURCE_DIR}/resources.rcc)
|
||||
generate_qrc(OUTPUT ${RESOURCES_QRC} PATH ${CMAKE_CURRENT_SOURCE_DIR}/resources CUSTOM_PATHS ${CUSTOM_LAUNCHER_QRC_PATHS} GLOBS *)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT ${RESOURCES_RCC}
|
||||
DEPENDS ${RESOURCES_QRC} ${GENERATE_QRC_DEPENDS}
|
||||
COMMAND "${_qt5Core_install_prefix}/bin/rcc"
|
||||
ARGS ${RESOURCES_QRC} -binary -o ${RESOURCES_RCC})
|
||||
|
||||
QT5_ADD_RESOURCES(RES_SOURCES ${RESOURCES_QRC})
|
||||
|
||||
list(APPEND GENERATE_QRC_DEPENDS ${RESOURCES_RCC})
|
||||
add_custom_target(resources ALL DEPENDS ${GENERATE_QRC_DEPENDS})
|
||||
|
||||
foreach(plugin ${Qt5Gui_PLUGINS})
|
||||
get_target_property(_loc ${plugin} LOCATION)
|
||||
set(plugin_libs ${plugin_libs} ${_loc})
|
||||
endforeach()
|
||||
|
||||
set(src_files
|
||||
src/main.cpp
|
||||
src/Launcher.h
|
||||
src/Launcher.cpp
|
||||
src/LauncherState.h
|
||||
src/LauncherState.cpp
|
||||
src/LauncherWindow.h
|
||||
src/LauncherWindow.cpp
|
||||
src/LoginRequest.h
|
||||
src/LoginRequest.cpp
|
||||
src/SignupRequest.h
|
||||
src/SignupRequest.cpp
|
||||
src/BuildsRequest.h
|
||||
src/BuildsRequest.cpp
|
||||
src/UserSettingsRequest.h
|
||||
src/UserSettingsRequest.cpp
|
||||
src/PathUtils.h
|
||||
src/PathUtils.cpp
|
||||
src/Unzipper.h
|
||||
src/Unzipper.cpp
|
||||
src/Helper.h
|
||||
src/Helper.cpp
|
||||
src/CommandlineOptions.h
|
||||
src/CommandlineOptions.cpp
|
||||
deps/miniz/miniz.h
|
||||
deps/miniz/miniz.cpp
|
||||
)
|
||||
|
||||
|
||||
if (APPLE)
|
||||
set(src_files ${src_files}
|
||||
src/Helper_darwin.mm
|
||||
src/NSTask+NSTaskExecveAdditions.h
|
||||
src/NSTask+NSTaskExecveAdditions.m
|
||||
)
|
||||
endif()
|
||||
|
||||
if (WIN32)
|
||||
set(src_files ${src_files}
|
||||
src/Helper_windows.cpp
|
||||
src/LauncherInstaller_windows.h
|
||||
src/LauncherInstaller_windows.cpp
|
||||
)
|
||||
endif()
|
||||
set(TARGET_NAME ${PROJECT_NAME})
|
||||
|
||||
|
||||
set_packaging_parameters()
|
||||
if (WIN32)
|
||||
set(CONFIGURE_ICON_PATH "${CMAKE_CURRENT_SOURCE_DIR}/resources/images/interface.ico")
|
||||
message(${CONFIGURE_ICON_PATH})
|
||||
set(CONFIGURE_ICON_RC_OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/Icon.rc")
|
||||
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/Icon.rc.in" ${CONFIGURE_ICON_RC_OUTPUT})
|
||||
add_executable(${PROJECT_NAME} WIN32 ${src_files} ${RES_SOURCES} ${CONFIGURE_ICON_RC_OUTPUT})
|
||||
elseif (APPLE)
|
||||
set(APP_NAME "HQ Launcher")
|
||||
set_target_properties(${this_target} PROPERTIES
|
||||
MACOSX_BUNDLE_INFO_PLIST MacOSXBundleInfo.plist.in)
|
||||
|
||||
set(MACOSX_BUNDLE_ICON_FILE "interface.icns")
|
||||
add_executable(${PROJECT_NAME} MACOSX_BUNDLE ${src_files} ${RES_SOURCES})
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES OUTPUT_NAME ${APP_NAME}
|
||||
MACOSX_BUNDLE_BUNDLE_NAME ${APP_NAME})
|
||||
|
||||
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources"
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory
|
||||
${CMAKE_SOURCE_DIR}/resources/images "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources/"
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/MacOS/" ln -sf ./HQ\ Launcher updater
|
||||
# Older versions of Launcher put updater in `/Contents/Resources/updater`.
|
||||
COMMAND ${CMAKE_COMMAND} -E chdir "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${APP_NAME}.app/Contents/Resources" ln -sf ../MacOS/HQ\ Launcher updater
|
||||
)
|
||||
endif()
|
||||
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
Qt5::Core
|
||||
Qt5::Quick
|
||||
Qt5::QuickControls2
|
||||
Qt5::Qml
|
||||
Qt5::Gui
|
||||
Qt5::Network
|
||||
${Qt_LIBRARIES}
|
||||
${OPENGL_LIBRARIES}
|
||||
${plugin_libs}
|
||||
${QT_STATIC_LIBS}
|
||||
)
|
||||
|
||||
if (WIN32)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
wsock32 ws2_32 Winmm version imm32 dwmapi
|
||||
Crypt32 Iphlpapi
|
||||
#"${SSL_DIR}/lib/libeay32.lib"
|
||||
#"${SSL_DIR}/lib/ssleay32.lib"
|
||||
${OPENSSL_LIBRARIES}
|
||||
"${_qt5Core_install_prefix}/qml/QtQuick.2/qtquick2plugin.lib"
|
||||
"${_qt5Core_install_prefix}/qml/QtQuick/Controls.2/qtquickcontrols2plugin.lib"
|
||||
"${_qt5Core_install_prefix}/qml/QtQuick/Templates.2/qtquicktemplates2plugin.lib")
|
||||
elseif (APPLE)
|
||||
target_link_libraries(${PROJECT_NAME}
|
||||
${OPENSSL_LIBRARIES}
|
||||
"${_qt5Core_install_prefix}/qml/QtQuick.2/libqtquick2plugin.a"
|
||||
"${_qt5Core_install_prefix}/qml/QtQuick/Controls.2/libqtquickcontrols2plugin.a"
|
||||
"${_qt5Core_install_prefix}/qml/QtQuick/Templates.2/libqtquicktemplates2plugin.a"
|
||||
"${_qt5Core_install_prefix}/plugins/platforms/libqcocoa.a")
|
||||
endif()
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/deps/
|
||||
${Qt5Core_INCLUDE_DIRS}
|
||||
${Qt5Quick_INCLUDE_DIRS}
|
||||
${Qt5Gui_INCLUDE_DIRS}
|
||||
${Qt5Qml_INCLUDE_DIRS})
|
||||
|
||||
add_dependencies(${PROJECT_NAME} resources)
|
||||
|
||||
if (APPLE)
|
||||
target_include_directories(${PROJECT_NAME} PUBLIC
|
||||
${OPENSSL_INCLUDE_DIR})
|
||||
endif()
|
||||
|
||||
if (LAUNCHER_SOURCE_TREE_RESOURCES)
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="${CMAKE_CURRENT_SOURCE_DIR}/resources/")
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE HIFI_USE_LOCAL_FILE)
|
||||
message("Use source tree resources path: file://${CMAKE_CURRENT_SOURCE_DIR}/resources/")
|
||||
else()
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE RESOURCE_PREFIX_URL="qrc:/")
|
||||
message("Use resource.rcc path: qrc:/")
|
||||
endif()
|
||||
|
||||
target_compile_definitions(${PROJECT_NAME} PRIVATE LAUNCHER_BUILD_VERSION="${BUILD_VERSION}")
|
||||
|
||||
if (APPLE)
|
||||
install(
|
||||
TARGETS HQLauncher
|
||||
BUNDLE DESTINATION "."
|
||||
COMPONENT applications)
|
||||
|
||||
set(CMAKE_INSTALL_PREFIX ${CMAKE_BINARY_DIR})
|
||||
|
||||
include(CPackComponent)
|
||||
|
||||
set(CPACK_PACKAGE_NAME "HQ Launcher")
|
||||
set(CPACK_PACKAGE_VENDOR "High Fidelity")
|
||||
set(CPACK_PACKAGE_FILE_NAME "HQ Launcher")
|
||||
|
||||
set(CPACK_NSIS_DISPLAY_NAME ${_DISPLAY_NAME})
|
||||
|
||||
set(DMG_SUBFOLDER_NAME "High Fidelity")
|
||||
set(ESCAPED_DMG_SUBFOLDER_NAME "")
|
||||
set(DMG_SUBFOLDER_ICON "${CMAKE_SOURCE_DIR}/cmake/installer/install-folder.rsrc")
|
||||
|
||||
set(CPACK_GENERATOR "DragNDrop")
|
||||
include(CPack)
|
||||
endif()
|
20
launchers/qt/HQ Launcher.entitlements
Normal file
|
@ -0,0 +1,20 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>com.apple.security.application-groups</key>
|
||||
<array>
|
||||
<string>high-fidelity.hifi</string>
|
||||
</array>
|
||||
<key>com.apple.security.device.audio-input</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-jit</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-executable-page-protection</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
14
launchers/qt/cmake/init.cmake
Normal file
|
@ -0,0 +1,14 @@
|
|||
|
||||
# Hide automoc folders (for IDEs)
|
||||
set(AUTOGEN_TARGETS_FOLDER "hidden/generated")
|
||||
# Find includes in corresponding build directories
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
# CMAKE_CURRENT_SOURCE_DIR is the parent folder here
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules/")
|
||||
|
||||
file(GLOB LAUNCHER_CUSTOM_MACROS "cmake/macros/*.cmake")
|
||||
foreach(CUSTOM_MACRO ${LAUNCHER_CUSTOM_MACROS})
|
||||
include(${CUSTOM_MACRO})
|
||||
endforeach()
|
||||
unset(LAUNCHER_CUSTOM_MACROS)
|
1634
launchers/qt/cmake/installer/install-folder.rsrc
Normal file
BIN
launchers/qt/cmake/installer/installer-header.bmp
Normal file
After Width: | Height: | Size: 100 KiB |
BIN
launchers/qt/cmake/installer/installer.ico
Normal file
After Width: | Height: | Size: 299 KiB |
BIN
launchers/qt/cmake/installer/uninstaller-header.bmp
Normal file
After Width: | Height: | Size: 100 KiB |
31
launchers/qt/cmake/macros/GenerateQrc.cmake
Normal file
|
@ -0,0 +1,31 @@
|
|||
function(GENERATE_QRC)
|
||||
set(oneValueArgs OUTPUT PREFIX PATH)
|
||||
set(multiValueArgs CUSTOM_PATHS GLOBS)
|
||||
cmake_parse_arguments(GENERATE_QRC "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
|
||||
if ("${GENERATE_QRC_PREFIX}" STREQUAL "")
|
||||
set(QRC_PREFIX_PATH /)
|
||||
else()
|
||||
set(QRC_PREFIX_PATH ${GENERATE_QRC_PREFIX})
|
||||
endif()
|
||||
|
||||
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("${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/resources.qrc.in" ${GENERATE_QRC_OUTPUT})
|
||||
endfunction()
|
45
launchers/qt/cmake/macros/SetPackagingParameters.cmake
Normal file
|
@ -0,0 +1,45 @@
|
|||
#
|
||||
# SetPackagingParameters.cmake
|
||||
# cmake/macros
|
||||
#
|
||||
# Created by Leonardo Murillo on 07/14/2015.
|
||||
# Copyright 2015 High Fidelity, Inc.
|
||||
#
|
||||
# Distributed under the Apache License, Version 2.0.
|
||||
# See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
|
||||
|
||||
# This macro checks some Jenkins defined environment variables to determine the origin of this build
|
||||
# and decides how targets should be packaged.
|
||||
|
||||
macro(SET_PACKAGING_PARAMETERS)
|
||||
set(PR_BUILD 0)
|
||||
set(PRODUCTION_BUILD 0)
|
||||
set(DEV_BUILD 0)
|
||||
set(BUILD_NUMBER 0)
|
||||
|
||||
set_from_env(RELEASE_TYPE RELEASE_TYPE "DEV")
|
||||
set_from_env(RELEASE_NUMBER RELEASE_NUMBER "")
|
||||
set_from_env(STABLE_BUILD STABLE_BUILD 0)
|
||||
|
||||
message(STATUS "The RELEASE_TYPE variable is: ${RELEASE_TYPE}")
|
||||
set(BUILD_NUMBER ${RELEASE_NUMBER})
|
||||
|
||||
if (RELEASE_TYPE STREQUAL "PRODUCTION")
|
||||
set(PRODUCTION_BUILD 1)
|
||||
set(BUILD_VERSION ${RELEASE_NUMBER})
|
||||
|
||||
# add definition for this release type
|
||||
add_definitions(-DPRODUCTION_BUILD)
|
||||
|
||||
elseif (RELEASE_TYPE STREQUAL "PR")
|
||||
set(PR_BUILD 1)
|
||||
set(BUILD_VERSION "PR${RELEASE_NUMBER}")
|
||||
|
||||
# add definition for this release type
|
||||
add_definitions(-DPR_BUILD)
|
||||
else ()
|
||||
set(DEV_BUILD 1)
|
||||
set(BUILD_VERSION "dev")
|
||||
endif ()
|
||||
|
||||
endmacro(SET_PACKAGING_PARAMETERS)
|
33
launchers/qt/cmake/modules/FindQtStaticDeps.cmake
Normal file
|
@ -0,0 +1,33 @@
|
|||
|
||||
set(qt_static_lib_dependices
|
||||
"qtpcre2"
|
||||
"qtlibpng"
|
||||
"qtfreetype"
|
||||
"Qt5AccessibilitySupport"
|
||||
"Qt5FbSupport"
|
||||
"Qt5OpenGLExtensions"
|
||||
"Qt5QuickTemplates2"
|
||||
"Qt5FontDatabaseSupport"
|
||||
"Qt5ThemeSupport"
|
||||
"Qt5EventDispatcherSupport")
|
||||
|
||||
if (WIN32)
|
||||
elseif(APPLE)
|
||||
set(qt_static_lib_dependices
|
||||
${qt_static_lib_dependices}
|
||||
"Qt5GraphicsSupport"
|
||||
"Qt5CglSupport"
|
||||
"Qt5ClipboardSupport")
|
||||
endif()
|
||||
|
||||
set(LIBS_PREFIX "${_qt5Core_install_prefix}/lib/")
|
||||
foreach (_qt_static_dep ${qt_static_lib_dependices})
|
||||
if (WIN32)
|
||||
set(lib_path "${LIBS_PREFIX}${_qt_static_dep}.lib")
|
||||
else()
|
||||
set(lib_path "${LIBS_PREFIX}lib${_qt_static_dep}.a")
|
||||
endif()
|
||||
set(QT_STATIC_LIBS ${QT_STATIC_LIBS} ${lib_path})
|
||||
endforeach()
|
||||
|
||||
unset(qt_static_lib_dependices)
|
37
launchers/qt/cmake/modules/MacOSXBundleInfo.plist.in
Normal file
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>English</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>${APP_NAME}</string>
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>${MACOSX_BUNDLE_ICON_FILE}</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.highfidelity.launcher</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${PRODUCT_NAME}</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<dict>
|
||||
<key>NSAllowsArbitraryLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1.0</string>
|
||||
<key>NSMainNibFile</key>
|
||||
<string>Window</string>
|
||||
<key>NSPrincipalClass</key>
|
||||
<string>NSApplication</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
|
||||
</dict>
|
||||
</plist>
|
1
launchers/qt/cmake/templates/Icon.rc.in
Normal file
|
@ -0,0 +1 @@
|
|||
IDI_ICON1 ICON DISCARDABLE "@CONFIGURE_ICON_PATH@"
|
5
launchers/qt/cmake/templates/resources.qrc.in
Normal file
|
@ -0,0 +1,5 @@
|
|||
<!DOCTYPE RCC><RCC version="1.0">
|
||||
<qresource prefix="@QRC_PREFIX_PATH@">
|
||||
@QRC_CONTENTS@
|
||||
</qresource>
|
||||
</RCC>
|
7658
launchers/qt/deps/miniz/miniz.cpp
Normal file
1338
launchers/qt/deps/miniz/miniz.h
Normal file
0
launchers/qt/deps/miniz/stdafx.h
Normal file
BIN
launchers/qt/resources/fonts/Graphik-Medium.ttf
Normal file
BIN
launchers/qt/resources/fonts/Graphik-Regular.ttf
Normal file
BIN
launchers/qt/resources/fonts/Graphik-Semibold.ttf
Normal file
BIN
launchers/qt/resources/images/HiFi_Voxel.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
BIN
launchers/qt/resources/images/HiFi_Window.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
13
launchers/qt/resources/images/Launcher.rc2
Normal file
|
@ -0,0 +1,13 @@
|
|||
//
|
||||
// Launcher.rc2 - resources Microsoft Visual C++ does not edit directly
|
||||
//
|
||||
|
||||
#ifdef APSTUDIO_INVOKED
|
||||
#error this file is not editable by Microsoft Visual C++
|
||||
#endif //APSTUDIO_INVOKED
|
||||
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
||||
// Add manually edited resources here...
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////
|
BIN
launchers/qt/resources/images/hidePass.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
launchers/qt/resources/images/hifi_logo_large@2x.png
Normal file
After Width: | Height: | Size: 16 KiB |
BIN
launchers/qt/resources/images/hifi_logo_small@2x.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
launchers/qt/resources/images/hifi_window@2x.png
Normal file
After Width: | Height: | Size: 7.6 KiB |
BIN
launchers/qt/resources/images/interface.icns
Normal file
BIN
launchers/qt/resources/images/interface.ico
Normal file
After Width: | Height: | Size: 26 KiB |
BIN
launchers/qt/resources/images/showPass.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
102
launchers/qt/resources/qml/Download.qml
Normal file
|
@ -0,0 +1,102 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.1
|
||||
import "HFControls"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
mirror: true
|
||||
source: PathUtils.resourcePath("images/hifi_window@2x.png");
|
||||
transformOrigin: Item.Center
|
||||
rotation: 180
|
||||
}
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
width: 150
|
||||
height: 150
|
||||
source: PathUtils.resourcePath("images/HiFi_Voxel.png");
|
||||
|
||||
anchors {
|
||||
top: root.top
|
||||
topMargin: 98
|
||||
horizontalCenter: root.horizontalCenter
|
||||
}
|
||||
|
||||
RotationAnimator {
|
||||
target: logo;
|
||||
loops: Animation.Infinite
|
||||
from: 0;
|
||||
to: 360;
|
||||
duration: 5000
|
||||
running: true
|
||||
}
|
||||
}
|
||||
|
||||
HFTextHeader {
|
||||
id: firstText
|
||||
|
||||
text: "Setup will take a moment"
|
||||
|
||||
anchors {
|
||||
top: logo.bottom
|
||||
topMargin: 46
|
||||
horizontalCenter: logo.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
HFTextRegular {
|
||||
id: secondText
|
||||
|
||||
text: "We're getting everything set up for you."
|
||||
|
||||
anchors {
|
||||
top: firstText.bottom
|
||||
topMargin: 8
|
||||
horizontalCenter: logo.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
ProgressBar {
|
||||
id: progressBar
|
||||
width: 394
|
||||
height: 8
|
||||
|
||||
value: LauncherState.downloadProgress;
|
||||
|
||||
anchors {
|
||||
top: secondText.bottom
|
||||
topMargin: 30
|
||||
horizontalCenter: logo.horizontalCenter
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: progressBar.width
|
||||
implicitHeight: progressBar.height
|
||||
radius: 8
|
||||
color: "#252525"
|
||||
}
|
||||
|
||||
contentItem: Item {
|
||||
implicitWidth: progressBar.width
|
||||
implicitHeight: progressBar.height * 0.85
|
||||
|
||||
Rectangle {
|
||||
width: progressBar.visualPosition * parent.width
|
||||
height: parent.height
|
||||
radius: 6
|
||||
color: "#01B2ED"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.parent.setBuildInfoState("left");
|
||||
}
|
||||
}
|
61
launchers/qt/resources/qml/DownloadFinished.qml
Normal file
|
@ -0,0 +1,61 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.1
|
||||
import "HFControls"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
anchors.centerIn: parent
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
mirror: true
|
||||
source: PathUtils.resourcePath("images/hifi_window@2x.png");
|
||||
transformOrigin: Item.Center
|
||||
rotation: 0
|
||||
}
|
||||
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
width: 132
|
||||
height: 134
|
||||
source: PathUtils.resourcePath("images/HiFi_Voxel.png");
|
||||
|
||||
anchors {
|
||||
top: root.top
|
||||
topMargin: 144
|
||||
horizontalCenter: root.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
HFTextHeader {
|
||||
id: header
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
text: "You're all set!"
|
||||
anchors {
|
||||
top: logo.bottom
|
||||
topMargin: 26
|
||||
horizontalCenter: logo.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
HFTextRegular {
|
||||
id: description
|
||||
text: "We will see you in world."
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
anchors {
|
||||
top: header.bottom
|
||||
topMargin: 8
|
||||
horizontalCenter: header.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.parent.setBuildInfoState("left");
|
||||
}
|
||||
}
|
227
launchers/qt/resources/qml/HFBase/CreateAccountBase.qml
Normal file
|
@ -0,0 +1,227 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick 2.1
|
||||
|
||||
import "../HFControls"
|
||||
import HQLauncher 1.0
|
||||
|
||||
|
||||
Item {
|
||||
id: root
|
||||
anchors.centerIn: parent
|
||||
property string titleText: "Sign in and pick a password"
|
||||
property string usernamePlaceholder: "Username"
|
||||
property string passwordPlaceholder: "Set a password (must be at least 6 characters)"
|
||||
property int marginLeft: root.width * 0.15
|
||||
|
||||
property bool enabled: LauncherState.applicationState == ApplicationState.WaitingForSignup
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
mirror: true
|
||||
source: PathUtils.resourcePath("images/hifi_window@2x.png");
|
||||
transformOrigin: Item.Center
|
||||
rotation: 180
|
||||
}
|
||||
|
||||
HFTextHeader {
|
||||
id: title
|
||||
width: 481
|
||||
lineHeight: 35
|
||||
lineHeightMode: Text.FixedHeight
|
||||
text: LauncherState.lastSignupErrorMessage.length == 0 ? root.titleText : "Uh oh"
|
||||
anchors {
|
||||
top: root.top
|
||||
topMargin: 29
|
||||
left: root.left
|
||||
leftMargin: root.marginLeft
|
||||
}
|
||||
}
|
||||
|
||||
HFTextRegular {
|
||||
id: instruction
|
||||
width: 425
|
||||
|
||||
text: "Use the email address you applied for access with"
|
||||
visible: LauncherState.lastSignupErrorMessage.length == 0
|
||||
|
||||
anchors {
|
||||
left: root.left
|
||||
leftMargin: root.marginLeft
|
||||
top: title.bottom
|
||||
topMargin: 18
|
||||
}
|
||||
}
|
||||
|
||||
HFTextError {
|
||||
id: error
|
||||
|
||||
width: 425
|
||||
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
visible: LauncherState.lastSignupErrorMessage.length > 0
|
||||
text: LauncherState.lastSignupErrorMessage
|
||||
|
||||
textFormat: Text.StyledText
|
||||
|
||||
onLinkActivated: {
|
||||
if (link == "login") {
|
||||
LauncherState.gotoLogin();
|
||||
} else {
|
||||
LauncherState.openURLInBrowser(link)
|
||||
}
|
||||
}
|
||||
|
||||
anchors {
|
||||
left: root.left
|
||||
leftMargin: root.marginLeft
|
||||
top: title.bottom
|
||||
topMargin: 18
|
||||
}
|
||||
}
|
||||
|
||||
HFTextField {
|
||||
id: email
|
||||
width: 430
|
||||
|
||||
enabled: root.enabled
|
||||
|
||||
placeholderText: "Email Address"
|
||||
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||
anchors {
|
||||
top: instruction.bottom
|
||||
left: root.left
|
||||
leftMargin: root.marginLeft
|
||||
topMargin: 18
|
||||
}
|
||||
}
|
||||
|
||||
HFTextField {
|
||||
id: username
|
||||
width: 430
|
||||
|
||||
enabled: root.enabled
|
||||
|
||||
placeholderText: root.usernamePlaceholder
|
||||
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||
anchors {
|
||||
top: email.bottom
|
||||
left: root.left
|
||||
leftMargin: root.marginLeft
|
||||
topMargin: 18
|
||||
}
|
||||
}
|
||||
|
||||
HFTextField {
|
||||
id: password
|
||||
width: 430
|
||||
|
||||
enabled: root.enabled
|
||||
|
||||
placeholderText: root.passwordPlaceholder
|
||||
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||
togglePasswordField: true
|
||||
echoMode: TextInput.Password
|
||||
anchors {
|
||||
top: username.bottom
|
||||
left: root.left
|
||||
leftMargin: root.marginLeft
|
||||
topMargin: 18
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HFTextRegular {
|
||||
id: displayNameText
|
||||
|
||||
text: "This is the display name other people see in High Fidelity. It can be changed at any time from your profile."
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
width: 430
|
||||
|
||||
anchors {
|
||||
top: password.bottom
|
||||
left: root.left
|
||||
leftMargin: root.marginLeft
|
||||
topMargin: 22
|
||||
}
|
||||
}
|
||||
|
||||
HFTextField {
|
||||
id: displayName
|
||||
width: 430
|
||||
|
||||
enabled: root.enabled
|
||||
|
||||
placeholderText: "Display Name"
|
||||
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||
anchors {
|
||||
top: displayNameText.bottom
|
||||
left: root.left
|
||||
leftMargin: root.marginLeft
|
||||
topMargin: 4
|
||||
}
|
||||
|
||||
onAccepted: {
|
||||
if (root.enabled && email.text.length > 0 && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0) {
|
||||
LauncherState.signup(email.text, username.text, password.text, displayName.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HFButton {
|
||||
id: button
|
||||
width: 134
|
||||
|
||||
enabled: root.enabled && email.text.length > 0 && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0
|
||||
|
||||
text: "NEXT"
|
||||
|
||||
anchors {
|
||||
top: displayName.bottom
|
||||
left: root.left
|
||||
leftMargin: root.marginLeft
|
||||
topMargin: 21
|
||||
}
|
||||
|
||||
onClicked: LauncherState.signup(email.text, username.text, password.text, displayName.text)
|
||||
}
|
||||
|
||||
|
||||
Text {
|
||||
text: "Already have an account?"
|
||||
font.family: "Graphik"
|
||||
font.pixelSize: 14
|
||||
color: "#009EE0"
|
||||
|
||||
anchors {
|
||||
top: button.bottom
|
||||
topMargin: 16
|
||||
left: button.left
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
LauncherState.gotoLogin();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HFTextLogo {
|
||||
anchors {
|
||||
bottom: root.bottom
|
||||
bottomMargin: 46
|
||||
right: displayName.right
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.parent.setBuildInfoState("left");
|
||||
}
|
||||
}
|
79
launchers/qt/resources/qml/HFBase/Error.qml
Normal file
|
@ -0,0 +1,79 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick 2.1
|
||||
import "../HFControls"
|
||||
|
||||
|
||||
Item {
|
||||
id: root
|
||||
anchors.centerIn: parent
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
mirror: false
|
||||
source: "qrc:/images/hifi_window@2x.png"
|
||||
//fillMode: Image.PreserveAspectFit
|
||||
transformOrigin: Item.Center
|
||||
//rotation: 90
|
||||
}
|
||||
|
||||
Image {
|
||||
id: logo
|
||||
width: 132
|
||||
height: 134
|
||||
source: "qrc:/images/HiFi_Voxel.png"
|
||||
|
||||
anchors {
|
||||
top: root.top
|
||||
topMargin: 98
|
||||
horizontalCenter: root.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
HFTextHeader {
|
||||
id: header
|
||||
text: "Uh oh."
|
||||
|
||||
anchors {
|
||||
top: logo.bottom
|
||||
topMargin: 26
|
||||
horizontalCenter: logo.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
HFTextRegular {
|
||||
id: description
|
||||
|
||||
text: "We seem to have a problem.\n Please restart Launcher."
|
||||
|
||||
anchors {
|
||||
top: header.bottom
|
||||
topMargin: 8
|
||||
horizontalCenter: header.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HFButton {
|
||||
id: button
|
||||
width: 166
|
||||
|
||||
text: "RESTART"
|
||||
|
||||
anchors {
|
||||
top: description.bottom
|
||||
topMargin: 60
|
||||
horizontalCenter: description.horizontalCenter
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
LauncherState.restart();
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.parent.setBuildInfoState("right");
|
||||
}
|
||||
|
||||
}
|
203
launchers/qt/resources/qml/HFBase/LoginBase.qml
Normal file
|
@ -0,0 +1,203 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick 2.1
|
||||
|
||||
import "../HFControls"
|
||||
import HQLauncher 1.0
|
||||
|
||||
Item {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
|
||||
property bool enabled: LauncherState.applicationState == ApplicationState.WaitingForLogin
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
mirror: false
|
||||
source: PathUtils.resourcePath("images/hifi_window@2x.png");
|
||||
transformOrigin: Item.Center
|
||||
rotation: 0
|
||||
}
|
||||
|
||||
Item {
|
||||
width: 430
|
||||
height: root.height
|
||||
|
||||
|
||||
anchors {
|
||||
top: root.top
|
||||
horizontalCenter: root.horizontalCenter
|
||||
}
|
||||
|
||||
HFTextHeader {
|
||||
id: title
|
||||
lineHeight: 35
|
||||
lineHeightMode: Text.FixedHeight
|
||||
text: "Please Log in"
|
||||
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: 40
|
||||
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
HFTextRegular {
|
||||
id: instruction
|
||||
|
||||
visible: LauncherState.lastLoginErrorMessage.length == 0
|
||||
text: "Use the account credentials you created at sign-up"
|
||||
anchors {
|
||||
top: title.bottom
|
||||
topMargin: 18
|
||||
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
HFTextError {
|
||||
id: error
|
||||
|
||||
visible: LauncherState.lastLoginErrorMessage.length > 0
|
||||
text: LauncherState.lastLoginErrorMessage
|
||||
anchors {
|
||||
top: title.bottom
|
||||
topMargin: 18
|
||||
|
||||
horizontalCenter: parent.horizontalCenter
|
||||
}
|
||||
}
|
||||
|
||||
HFTextField {
|
||||
id: username
|
||||
|
||||
enabled: root.enabled
|
||||
width: 430
|
||||
|
||||
text: LauncherState.lastUsedUsername
|
||||
placeholderText: "Username"
|
||||
|
||||
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||
anchors {
|
||||
top: error.bottom
|
||||
topMargin: 24
|
||||
|
||||
left: parent.left
|
||||
right: parent.right;
|
||||
}
|
||||
}
|
||||
|
||||
HFTextField {
|
||||
id: password
|
||||
width: 430
|
||||
enabled: root.enabled
|
||||
|
||||
placeholderText: "Password"
|
||||
togglePasswordField: true
|
||||
echoMode: TextInput.Password
|
||||
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||
anchors {
|
||||
top: username.bottom
|
||||
topMargin: 25
|
||||
|
||||
left: parent.left
|
||||
right: parent.right;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
HFTextRegular {
|
||||
id: displayText
|
||||
|
||||
text: "This is the display name other people see in High Fidelity. It can be changed at any time from your profile."
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
anchors {
|
||||
top: password.bottom
|
||||
topMargin: 50
|
||||
|
||||
left: parent.left
|
||||
right: parent.right;
|
||||
}
|
||||
}
|
||||
|
||||
HFTextField {
|
||||
id: displayName
|
||||
width: 430
|
||||
enabled: root.enabled
|
||||
|
||||
placeholderText: "Display name"
|
||||
seperatorColor: Qt.rgba(1, 1, 1, 0.3)
|
||||
anchors {
|
||||
top: displayText.bottom
|
||||
topMargin: 4
|
||||
|
||||
left: parent.left
|
||||
right: parent.right;
|
||||
}
|
||||
onAccepted: {
|
||||
if (root.enabled && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0) {
|
||||
LauncherState.login(username.text, password.text, displayName.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HFButton {
|
||||
id: button
|
||||
width: 134
|
||||
|
||||
enabled: root.enabled && username.text.length > 0 && password.text.length > 0 && displayName.text.length > 0
|
||||
|
||||
text: "NEXT"
|
||||
|
||||
anchors {
|
||||
top: displayName.bottom
|
||||
topMargin: 25
|
||||
|
||||
left: parent.left
|
||||
}
|
||||
|
||||
onClicked: LauncherState.login(username.text, password.text, displayName.text)
|
||||
}
|
||||
|
||||
Text {
|
||||
id: createAccountLink
|
||||
|
||||
text: "Sign up"
|
||||
font.family: "Graphik"
|
||||
font.pixelSize: 14
|
||||
color: "#009EE0"
|
||||
|
||||
anchors {
|
||||
top: button.bottom
|
||||
topMargin: 16
|
||||
left: parent.left
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
|
||||
onClicked: {
|
||||
console.log("clicked");
|
||||
LauncherState.gotoSignup();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
HFTextLogo {
|
||||
anchors {
|
||||
bottom: createAccountLink.bottom
|
||||
|
||||
right: parent.right
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Component.onCompleted: {
|
||||
root.parent.setBuildInfoState("right");
|
||||
}
|
||||
}
|
44
launchers/qt/resources/qml/HFControls/HFButton.qml
Normal file
|
@ -0,0 +1,44 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.1
|
||||
|
||||
Button {
|
||||
id: control
|
||||
|
||||
height: 50
|
||||
|
||||
property string backgroundColor: "#00000000"
|
||||
property string borderColor: enabled ? "#FFFFFF" : "#7e8c81"
|
||||
property string textColor: borderColor
|
||||
property int backgroundOpacity: 1
|
||||
property int backgroundRadius: 1
|
||||
property int backgroundWidth: 2
|
||||
|
||||
font.family: "Graphik Semibold"
|
||||
font.pixelSize: 15
|
||||
|
||||
background: Rectangle {
|
||||
implicitWidth: 100
|
||||
implicitHeight: 40
|
||||
color: control.backgroundColor
|
||||
opacity: control.backgroundOpacity
|
||||
border.color: control.borderColor
|
||||
border.width: control.backgroundWidth
|
||||
radius: control.backgroundRadius
|
||||
}
|
||||
|
||||
contentItem: Text {
|
||||
text: control.text
|
||||
font: control.font
|
||||
color: control.textColor
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: mouseArea
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
anchors.fill: parent
|
||||
onPressed: mouse.accepted = false
|
||||
}
|
||||
}
|
7
launchers/qt/resources/qml/HFControls/HFTextError.qml
Normal file
|
@ -0,0 +1,7 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick 2.1
|
||||
|
||||
HFTextRegular {
|
||||
color: "#FF0014"
|
||||
}
|
||||
|
90
launchers/qt/resources/qml/HFControls/HFTextField.qml
Normal file
|
@ -0,0 +1,90 @@
|
|||
import QtQuick 2.5
|
||||
import QtQuick.Controls 2.1
|
||||
|
||||
TextField {
|
||||
id: control
|
||||
|
||||
height: 50
|
||||
|
||||
font.family: "Graphik Regular"
|
||||
font.pixelSize: 14
|
||||
color: (text.length == 0 || !enabled) ? "#7e8c81" : "#000000"
|
||||
|
||||
property bool togglePasswordField: false
|
||||
verticalAlignment: TextInput.AlignVCenter
|
||||
horizontalAlignment: TextInput.AlignLeft
|
||||
placeholderText: "PlaceHolder"
|
||||
property string seperatorColor: "#FFFFFF"
|
||||
selectByMouse: true
|
||||
|
||||
background: Item {
|
||||
anchors.fill: parent
|
||||
Rectangle {
|
||||
id: background
|
||||
color: "#FFFFFF"
|
||||
anchors.fill: parent
|
||||
|
||||
Image {
|
||||
id: hide
|
||||
visible: control.togglePasswordField
|
||||
source: (control.echoMode == TextInput.Password) ? PathUtils.resourcePath("images/showPass.png") :
|
||||
PathUtils.resourcePath("images/hidePass.png");
|
||||
fillMode: Image.PreserveAspectFit
|
||||
width: 24
|
||||
smooth: true
|
||||
anchors {
|
||||
top: parent.top
|
||||
topMargin: 18
|
||||
bottom: parent.bottom
|
||||
bottomMargin: 18
|
||||
right: parent.right
|
||||
rightMargin: 13
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: {
|
||||
if (control.echoMode === TextInput.Password) {
|
||||
control.echoMode = TextInput.Normal;
|
||||
} else {
|
||||
control.echoMode = TextInput.Password;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Keys.onPressed: {
|
||||
event.accepted = false;
|
||||
|
||||
if (Platform === "MacOS") {
|
||||
if (event.key == Qt.Key_Left) {
|
||||
if (control.cursorPosition > 0) {
|
||||
var index = control.cursorPosition - 1;
|
||||
control.select(index, index);
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
if (event.key == Qt.Key_Right) {
|
||||
if (control.cursorPosition < control.text.length) {
|
||||
var index = control.cursorPosition + 1;
|
||||
control.select(index, index);
|
||||
}
|
||||
event.accepted = true;
|
||||
}
|
||||
|
||||
if (event.modifiers & Qt.ControlModifier) {
|
||||
if (event.key == Qt.Key_C) {
|
||||
control.copy();
|
||||
event.accepted = true;
|
||||
} else if (event.key == Qt.Key_V) {
|
||||
control.paste();
|
||||
event.accepted = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
8
launchers/qt/resources/qml/HFControls/HFTextHeader.qml
Normal file
|
@ -0,0 +1,8 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick 2.1
|
||||
|
||||
Text {
|
||||
font.family: "Graphik Semibold"
|
||||
font.pixelSize: 32
|
||||
color: "#ffffff"
|
||||
}
|
11
launchers/qt/resources/qml/HFControls/HFTextLogo.qml
Normal file
|
@ -0,0 +1,11 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick 2.1
|
||||
|
||||
Text {
|
||||
text: "High Fidelity"
|
||||
font.bold: true
|
||||
font.family: "Graphik Semibold"
|
||||
font.pixelSize: 17
|
||||
font.letterSpacing: -1
|
||||
color: "#FFFFFF"
|
||||
}
|
18
launchers/qt/resources/qml/HFControls/HFTextRegular.qml
Normal file
|
@ -0,0 +1,18 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick 2.1
|
||||
|
||||
Text {
|
||||
id: root
|
||||
|
||||
font.family: "Graphik Regular"
|
||||
font.pixelSize: 14
|
||||
|
||||
color: "#C4C4C4"
|
||||
linkColor: color
|
||||
|
||||
MouseArea {
|
||||
anchors.fill: root
|
||||
cursorShape: root.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
acceptedButtons: Qt.NoButton
|
||||
}
|
||||
}
|
7
launchers/qt/resources/qml/Login.qml
Normal file
|
@ -0,0 +1,7 @@
|
|||
// login
|
||||
|
||||
import "HFBase"
|
||||
|
||||
LoginBase {
|
||||
anchors.fill: parent
|
||||
}
|
28
launchers/qt/resources/qml/SplashScreen.qml
Normal file
|
@ -0,0 +1,28 @@
|
|||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.1
|
||||
|
||||
Item {
|
||||
id: root
|
||||
anchors.fill: parent
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: parent.width
|
||||
height: parent.height
|
||||
mirror: false
|
||||
source: PathUtils.resourcePath("images/hifi_window@2x.png");
|
||||
transformOrigin: Item.Center
|
||||
rotation: 0
|
||||
}
|
||||
|
||||
Image {
|
||||
anchors.centerIn: parent
|
||||
width: 240
|
||||
height: 180
|
||||
source: PathUtils.resourcePath("images/hifi_logo_large@2x.png");
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
root.parent.setBuildInfoState("right");
|
||||
}
|
||||
}
|
67
launchers/qt/resources/qml/root.qml
Normal file
|
@ -0,0 +1,67 @@
|
|||
// root.qml
|
||||
|
||||
import QtQuick 2.3
|
||||
import QtQuick.Controls 2.1
|
||||
import HQLauncher 1.0
|
||||
import "HFControls"
|
||||
|
||||
Item {
|
||||
id: root
|
||||
Loader {
|
||||
anchors.fill: parent
|
||||
id: loader
|
||||
|
||||
|
||||
function setBuildInfoState(state) {
|
||||
buildInfo.state = state;
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
loader.source = "./SplashScreen.qml";
|
||||
LauncherState.updateSourceUrl.connect(function(url) {
|
||||
loader.source = url;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function loadPage(url) {
|
||||
loader.source = url;
|
||||
}
|
||||
|
||||
HFTextRegular {
|
||||
id: buildInfo
|
||||
|
||||
anchors {
|
||||
leftMargin: 10
|
||||
rightMargin: 10
|
||||
bottomMargin: 10
|
||||
|
||||
right: root.right
|
||||
bottom: root.bottom
|
||||
}
|
||||
|
||||
color: "#666"
|
||||
text: "V." + LauncherState.buildVersion;
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "left"
|
||||
AnchorChanges {
|
||||
target: buildInfo
|
||||
anchors.left: root.left
|
||||
anchors.right: undefined
|
||||
}
|
||||
},
|
||||
|
||||
State {
|
||||
name: "right"
|
||||
AnchorChanges {
|
||||
target: buildInfo
|
||||
anchors.right: root.right
|
||||
anchors.left: undefined
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
115
launchers/qt/src/BuildsRequest.cpp
Normal file
|
@ -0,0 +1,115 @@
|
|||
#include "BuildsRequest.h"
|
||||
|
||||
#include "Helper.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QProcessEnvironment>
|
||||
|
||||
bool Builds::getBuild(QString tag, Build* outBuild) {
|
||||
if (tag.isNull()) {
|
||||
tag = defaultTag;
|
||||
}
|
||||
|
||||
for (auto& build : builds) {
|
||||
if (build.tag == tag) {
|
||||
*outBuild = build;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void BuildsRequest::send(QNetworkAccessManager& nam) {
|
||||
QString latestBuildRequestUrl { "https://thunder.highfidelity.com/builds/api/tags/latest/?format=json" };
|
||||
QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment();
|
||||
|
||||
if (processEnvironment.contains("HQ_LAUNCHER_BUILDS_URL")) {
|
||||
latestBuildRequestUrl = processEnvironment.value("HQ_LAUNCHER_BUILDS_URL");
|
||||
}
|
||||
|
||||
qDebug() << latestBuildRequestUrl;
|
||||
|
||||
QNetworkRequest request{ QUrl(latestBuildRequestUrl) };
|
||||
auto reply = nam.get(request);
|
||||
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, &BuildsRequest::receivedResponse);
|
||||
}
|
||||
|
||||
void BuildsRequest::receivedResponse() {
|
||||
_state = State::Finished;
|
||||
|
||||
auto reply = static_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (reply->error()) {
|
||||
qDebug() << "Error getting builds from thunder: " << reply->errorString();
|
||||
_error = Error::Unknown;
|
||||
emit finished();
|
||||
return;
|
||||
} else {
|
||||
qDebug() << "Builds reply has been received";
|
||||
|
||||
auto data = reply->readAll();
|
||||
|
||||
QJsonParseError parseError;
|
||||
auto doc = QJsonDocument::fromJson(data, &parseError);
|
||||
|
||||
if (parseError.error) {
|
||||
qDebug() << "Error parsing response from thunder: " << data;
|
||||
_error = Error::Unknown;
|
||||
} else {
|
||||
auto root = doc.object();
|
||||
if (!root.contains("default_tag")) {
|
||||
_error = Error::MissingDefaultTag;
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
_latestBuilds.defaultTag = root["default_tag"].toString();
|
||||
|
||||
auto results = root["results"];
|
||||
if (!results.isArray()) {
|
||||
_error = Error::MalformedResponse;
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
for (auto result : results.toArray()) {
|
||||
auto entry = result.toObject();
|
||||
Build build;
|
||||
build.tag = entry["name"].toString();
|
||||
build.latestVersion = entry["latest_version"].toInt();
|
||||
build.buildNumber = entry["build_number"].toInt();
|
||||
#ifdef Q_OS_WIN
|
||||
build.installerZipURL = entry["installers"].toObject()["windows"].toObject()["zip_url"].toString();
|
||||
#elif defined(Q_OS_MACOS)
|
||||
build.installerZipURL = entry["installers"].toObject()["mac"].toObject()["zip_url"].toString();
|
||||
#else
|
||||
#error "Launcher is only supported on Windows and Mac OS"
|
||||
#endif
|
||||
_latestBuilds.builds.push_back(build);
|
||||
}
|
||||
|
||||
auto launcherResults = root["launcher"].toObject();
|
||||
|
||||
Build launcherBuild;
|
||||
launcherBuild.latestVersion = launcherResults["version"].toInt();
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
launcherBuild.installerZipURL = launcherResults["windows"].toObject()["url"].toString();
|
||||
#elif defined(Q_OS_MACOS)
|
||||
launcherBuild.installerZipURL = launcherResults["mac"].toObject()["url"].toString();
|
||||
#else
|
||||
#error "Launcher is only supported on Windows and Mac OS"
|
||||
#endif
|
||||
_latestBuilds.launcherBuild = launcherBuild;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
emit finished();
|
||||
}
|
54
launchers/qt/src/BuildsRequest.h
Normal file
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
struct Build {
|
||||
QString tag{ QString::null };
|
||||
int latestVersion{ 0 };
|
||||
int buildNumber{ 0 };
|
||||
QString installerZipURL{ QString::null };
|
||||
};
|
||||
|
||||
struct Builds {
|
||||
bool getBuild(QString tag, Build* outBuild);
|
||||
|
||||
QString defaultTag;
|
||||
std::vector<Build> builds;
|
||||
Build launcherBuild;
|
||||
};
|
||||
|
||||
class BuildsRequest : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class State {
|
||||
Unsent,
|
||||
Sending,
|
||||
Finished
|
||||
};
|
||||
|
||||
enum class Error {
|
||||
None = 0,
|
||||
Unknown,
|
||||
MalformedResponse,
|
||||
MissingDefaultTag,
|
||||
};
|
||||
Q_ENUM(Error)
|
||||
|
||||
void send(QNetworkAccessManager& nam);
|
||||
Error getError() const { return _error; }
|
||||
|
||||
const Builds& getLatestBuilds() const { return _latestBuilds; }
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
|
||||
private slots:
|
||||
void receivedResponse();
|
||||
|
||||
private:
|
||||
State _state { State::Unsent };
|
||||
Error _error { Error::None };
|
||||
|
||||
Builds _latestBuilds;
|
||||
};
|
35
launchers/qt/src/CommandlineOptions.cpp
Normal file
|
@ -0,0 +1,35 @@
|
|||
#include "CommandlineOptions.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <QDebug>
|
||||
#include <QString>
|
||||
|
||||
bool isCommandlineOption(const std::string& option) {
|
||||
if (option.rfind("--", 0) == 0 && option.at(2) != '-') {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool CommandlineOptions::contains(const std::string& option) {
|
||||
auto iter = std::find(_commandlineOptions.begin(), _commandlineOptions.end(), option);
|
||||
return (iter != _commandlineOptions.end());
|
||||
}
|
||||
|
||||
void CommandlineOptions::parse(const int argc, char** argv) {
|
||||
for (int index = 1; index < argc; index++) {
|
||||
std::string option = argv[index];
|
||||
if (isCommandlineOption(option)) {
|
||||
_commandlineOptions.push_back(option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CommandlineOptions::append(const std::string& command) {
|
||||
_commandlineOptions.push_back(command);
|
||||
}
|
||||
|
||||
CommandlineOptions* CommandlineOptions::getInstance() {
|
||||
static CommandlineOptions commandlineOptions;
|
||||
return &commandlineOptions;
|
||||
}
|
17
launchers/qt/src/CommandlineOptions.h
Normal file
|
@ -0,0 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
class CommandlineOptions {
|
||||
public:
|
||||
CommandlineOptions() = default;
|
||||
~CommandlineOptions() = default;
|
||||
|
||||
void parse(const int argc, char** argv);
|
||||
bool contains(const std::string& option);
|
||||
void append(const std::string& command);
|
||||
static CommandlineOptions* getInstance();
|
||||
private:
|
||||
std::vector<std::string> _commandlineOptions;
|
||||
};
|
105
launchers/qt/src/Helper.cpp
Normal file
|
@ -0,0 +1,105 @@
|
|||
#include "Helper.h"
|
||||
|
||||
#include "PathUtils.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDebug>
|
||||
#include <QFileInfo>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QProcess>
|
||||
#include <QProcessEnvironment>
|
||||
#include <QDateTime>
|
||||
#include <QTextStream>
|
||||
#include <QStandardPaths>
|
||||
#include <iostream>
|
||||
|
||||
|
||||
QString getMetaverseAPIDomain() {
|
||||
QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment();
|
||||
if (processEnvironment.contains("HIFI_METAVERSE_URL")) {
|
||||
return processEnvironment.value("HIFI_METAVERSE_URL");
|
||||
}
|
||||
return "https://metaverse.highfidelity.com";
|
||||
}
|
||||
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
|
||||
Q_UNUSED(context);
|
||||
|
||||
QString date = QDateTime::currentDateTime().toString("dd/MM/yyyy hh:mm:ss");
|
||||
QString txt = QString("[%1] ").arg(date);
|
||||
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
txt += QString("{Debug} \t\t %1").arg(message);
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
txt += QString("{Warning} \t %1").arg(message);
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
txt += QString("{Critical} \t %1").arg(message);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
txt += QString("{Fatal} \t\t %1").arg(message);
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
txt += QString("{Info} \t %1").arg(message);
|
||||
break;
|
||||
}
|
||||
|
||||
QDir logsDir = PathUtils::getLogsDirectory();
|
||||
logsDir.mkpath(logsDir.absolutePath());
|
||||
QString filename = logsDir.absoluteFilePath("Log.txt");
|
||||
|
||||
QFile outFile(filename);
|
||||
outFile.open(QIODevice::WriteOnly | QIODevice::Append);
|
||||
|
||||
QTextStream textStream(&outFile);
|
||||
std::cout << txt.toStdString() << "\n";
|
||||
textStream << txt << "\n";
|
||||
outFile.close();
|
||||
}
|
||||
|
||||
bool swapLaunchers(const QString& oldLauncherPath, const QString& newLauncherPath) {
|
||||
if (!(QFileInfo::exists(oldLauncherPath) && QFileInfo::exists(newLauncherPath))) {
|
||||
qDebug() << "old launcher: " << oldLauncherPath << "new launcher: " << newLauncherPath << " file does not exist";
|
||||
}
|
||||
|
||||
bool success = false;
|
||||
#ifdef Q_OS_MAC
|
||||
qDebug() << "replacing launchers -> old launcher: " << oldLauncherPath << " new launcher: " << newLauncherPath;
|
||||
success = replaceDirectory(oldLauncherPath, newLauncherPath);
|
||||
#endif
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
void cleanLogFile() {
|
||||
QDir launcherDirectory = PathUtils::getLogsDirectory();
|
||||
launcherDirectory.mkpath(launcherDirectory.absolutePath());
|
||||
QString filename = launcherDirectory.absoluteFilePath("Log.txt");
|
||||
QString tmpFilename = launcherDirectory.absoluteFilePath("Log-last.txt");
|
||||
if (QFile::exists(filename)) {
|
||||
if (QFile::exists(tmpFilename)) {
|
||||
QFile::remove(tmpFilename);
|
||||
}
|
||||
QFile::rename(filename, tmpFilename);
|
||||
QFile::remove(filename);
|
||||
}
|
||||
}
|
||||
|
||||
QString getHTTPUserAgent() {
|
||||
#if defined(Q_OS_WIN)
|
||||
return "HQLauncher/fixme (Windows)";
|
||||
#elif defined(Q_OS_MACOS)
|
||||
return "HQLauncher/fixme (MacOS)";
|
||||
#else
|
||||
#error Unsupported platform
|
||||
#endif
|
||||
}
|
||||
|
||||
const QString& getInterfaceSharedMemoryName() {
|
||||
static const QString applicationName = "High Fidelity Interface - " + qgetenv("USERNAME");
|
||||
return applicationName;
|
||||
}
|
39
launchers/qt/src/Helper.h
Normal file
|
@ -0,0 +1,39 @@
|
|||
#include <QString>
|
||||
#include <string>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "Windows.h"
|
||||
#endif
|
||||
|
||||
QString getMetaverseAPIDomain();
|
||||
|
||||
void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride,
|
||||
const QString& displayName, const QString& contentCachePath, QString loginResponseToken = QString());
|
||||
|
||||
|
||||
void launchAutoUpdater(const QString& autoUpdaterPath);
|
||||
bool swapLaunchers(const QString& oldLauncherPath = QString(), const QString& newLauncherPath = QString());
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message);
|
||||
void cleanLogFile();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory);
|
||||
void closeInterfaceIfRunning();
|
||||
void waitForInterfaceToClose();
|
||||
bool isLauncherAlreadyRunning();
|
||||
QString getBundlePath();
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
HRESULT createSymbolicLink(LPCSTR lpszPathObj, LPCSTR lpszPathLink, LPCSTR lpszDesc, LPCSTR lpszArgs = (LPCSTR)"");
|
||||
bool insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value);
|
||||
bool insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value);
|
||||
bool deleteRegistryKey(const std::string& regPath);
|
||||
|
||||
BOOL isProcessRunning(const char* processName, int& processID);
|
||||
BOOL shutdownProcess(DWORD dwProcessId, UINT uExitCode);
|
||||
#endif
|
||||
|
||||
QString getHTTPUserAgent();
|
||||
|
||||
const QString& getInterfaceSharedMemoryName();
|
150
launchers/qt/src/Helper_darwin.mm
Normal file
|
@ -0,0 +1,150 @@
|
|||
#include "Helper.h"
|
||||
|
||||
#import "NSTask+NSTaskExecveAdditions.h"
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#include <QString>
|
||||
#include <QDebug>
|
||||
#include <QTimer>
|
||||
#include <QCoreApplication>
|
||||
|
||||
void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptOverride,
|
||||
const QString& displayName, const QString& contentCachePath, QString loginTokenResponse) {
|
||||
|
||||
NSString* homeBookmark = [[NSString stringWithFormat:@"hqhome="] stringByAppendingString:homePath.toNSString()];
|
||||
NSArray* arguments;
|
||||
if (!loginTokenResponse.isEmpty()) {
|
||||
arguments = [NSArray arrayWithObjects:
|
||||
@"--url" , homePath.toNSString(),
|
||||
@"--tokens", loginTokenResponse.toNSString(),
|
||||
@"--cache", contentCachePath.toNSString(),
|
||||
@"--displayName", displayName.toNSString(),
|
||||
@"--defaultScriptsOverride", defaultScriptOverride.toNSString(),
|
||||
@"--setBookmark", homeBookmark,
|
||||
@"--no-updater",
|
||||
@"--no-launcher",
|
||||
@"--suppress-settings-reset", nil];
|
||||
} else {
|
||||
arguments = [NSArray arrayWithObjects:
|
||||
@"--url" , homePath.toNSString(),
|
||||
@"--cache", contentCachePath.toNSString(),
|
||||
@"--defaultScriptsOverride", defaultScriptOverride.toNSString(),
|
||||
@"--setBookmark", homeBookmark,
|
||||
@"--no-updater",
|
||||
@"--no-launcher",
|
||||
@"--suppress-settings-reset", nil];
|
||||
}
|
||||
|
||||
NSWorkspace *workspace = [NSWorkspace sharedWorkspace];
|
||||
NSURL *url = [NSURL fileURLWithPath:[workspace fullPathForApplication:clientPath.toNSString()]];
|
||||
NSTask *task = [[NSTask alloc] init];
|
||||
task.launchPath = [url path];
|
||||
task.arguments = arguments;
|
||||
[task replaceThisProcess];
|
||||
|
||||
}
|
||||
|
||||
QString getBundlePath() {
|
||||
return QString::fromNSString([[NSBundle mainBundle] bundlePath]);
|
||||
}
|
||||
|
||||
|
||||
void launchAutoUpdater(const QString& autoUpdaterPath) {
|
||||
NSException *exception;
|
||||
bool launched = false;
|
||||
// Older versions of Launcher put updater in `/Contents/Resources/updater`.
|
||||
NSString* newLauncher = autoUpdaterPath.toNSString();
|
||||
for (NSString *bundlePath in @[@"/Contents/MacOS/updater",
|
||||
@"/Contents/Resources/updater",
|
||||
]) {
|
||||
NSTask* task = [[NSTask alloc] init];
|
||||
task.launchPath = [newLauncher stringByAppendingString: bundlePath];
|
||||
task.arguments = @[[[NSBundle mainBundle] bundlePath], newLauncher];
|
||||
|
||||
qDebug() << "launching updater: " << task.launchPath << task.arguments;
|
||||
|
||||
@try {
|
||||
[task launch];
|
||||
}
|
||||
@catch (NSException *e) {
|
||||
qDebug() << "couldn't launch updater: " << QString::fromNSString(e.name) << QString::fromNSString(e.reason);
|
||||
exception = e;
|
||||
continue;
|
||||
}
|
||||
|
||||
launched = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (!launched) {
|
||||
@throw exception;
|
||||
}
|
||||
|
||||
QCoreApplication::instance()->quit();
|
||||
}
|
||||
|
||||
|
||||
@interface UpdaterHelper : NSObject
|
||||
+(NSURL*) NSStringToNSURL: (NSString*) path;
|
||||
@end
|
||||
|
||||
@implementation UpdaterHelper
|
||||
+(NSURL*) NSStringToNSURL: (NSString*) path
|
||||
{
|
||||
return [NSURL URLWithString: [path stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLFragmentAllowedCharacterSet]] relativeToURL: [NSURL URLWithString:@"file://"]];
|
||||
}
|
||||
@end
|
||||
|
||||
|
||||
bool replaceDirectory(const QString& orginalDirectory, const QString& newDirectory) {
|
||||
NSError *error = nil;
|
||||
NSFileManager* fileManager = [NSFileManager defaultManager];
|
||||
NSURL* destinationUrl = [UpdaterHelper NSStringToNSURL:newDirectory.toNSString()];
|
||||
bool success = (bool) [fileManager replaceItemAtURL:[UpdaterHelper NSStringToNSURL:orginalDirectory.toNSString()] withItemAtURL:[UpdaterHelper NSStringToNSURL:newDirectory.toNSString()]
|
||||
backupItemName:nil options:NSFileManagerItemReplacementUsingNewMetadataOnly resultingItemURL:&destinationUrl error:&error];
|
||||
|
||||
if (error != nil) {
|
||||
qDebug() << "NSFileManager::replaceItemAtURL -> error: " << error;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
|
||||
void waitForInterfaceToClose() {
|
||||
bool interfaceRunning = true;
|
||||
|
||||
while (interfaceRunning) {
|
||||
interfaceRunning = false;
|
||||
NSWorkspace* workspace = [NSWorkspace sharedWorkspace];
|
||||
NSArray* apps = [workspace runningApplications];
|
||||
for (NSRunningApplication* app in apps) {
|
||||
if ([[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface"] ||
|
||||
[[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface-pr"]) {
|
||||
interfaceRunning = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool isLauncherAlreadyRunning() {
|
||||
NSArray* apps = [NSRunningApplication runningApplicationsWithBundleIdentifier:@"com.highfidelity.launcher"];
|
||||
if ([apps count] > 1) {
|
||||
qDebug() << "launcher is already running";
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void closeInterfaceIfRunning() {
|
||||
NSWorkspace* workspace = [NSWorkspace sharedWorkspace];
|
||||
NSArray* apps = [workspace runningApplications];
|
||||
for (NSRunningApplication* app in apps) {
|
||||
if ([[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface"] ||
|
||||
[[app bundleIdentifier] isEqualToString:@"com.highfidelity.interface-pr"]) {
|
||||
[app terminate];
|
||||
}
|
||||
}
|
||||
}
|
188
launchers/qt/src/Helper_windows.cpp
Normal file
|
@ -0,0 +1,188 @@
|
|||
#include "Helper.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
|
||||
#include "windows.h"
|
||||
#include "winnls.h"
|
||||
#include "shobjidl.h"
|
||||
#include "objbase.h"
|
||||
#include "objidl.h"
|
||||
#include "shlguid.h"
|
||||
#include <atlstr.h>
|
||||
#include <tlhelp32.h>
|
||||
#include <strsafe.h>
|
||||
|
||||
void launchClient(const QString& clientPath, const QString& homePath, const QString& defaultScriptsPath,
|
||||
const QString& displayName, const QString& contentCachePath, QString loginResponseToken) {
|
||||
|
||||
// TODO Fix parameters
|
||||
QString params = "\"" + clientPath + "\"" + " --url \"" + homePath + "\""
|
||||
+ " --setBookmark \"hqhome=" + homePath + "\""
|
||||
+ " --defaultScriptsOverride \"file:///" + defaultScriptsPath + "\""
|
||||
+ " --cache \"" + contentCachePath + "\""
|
||||
+ " --suppress-settings-reset --no-launcher --no-updater";
|
||||
|
||||
if (!displayName.isEmpty()) {
|
||||
params += " --displayName \"" + displayName + "\"";
|
||||
}
|
||||
|
||||
if (!loginResponseToken.isEmpty()) {
|
||||
params += " --tokens \"" + loginResponseToken.replace("\"", "\\\"") + "\"";
|
||||
}
|
||||
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
// set the size of the structures
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
// start the program up
|
||||
BOOL success = CreateProcess(
|
||||
clientPath.toLatin1().data(),
|
||||
params.toLatin1().data(),
|
||||
nullptr, // Process handle not inheritable
|
||||
nullptr, // Thread handle not inheritable
|
||||
FALSE, // Set handle inheritance to FALSE
|
||||
CREATE_NEW_CONSOLE, // Opens file in a separate console
|
||||
nullptr, // Use parent's environment block
|
||||
nullptr, // Use parent's starting directory
|
||||
&si, // Pointer to STARTUPINFO structure
|
||||
&pi // Pointer to PROCESS_INFORMATION structure
|
||||
);
|
||||
|
||||
// Close process and thread handles.
|
||||
CloseHandle(pi.hProcess);
|
||||
CloseHandle(pi.hThread);
|
||||
}
|
||||
|
||||
void launchAutoUpdater(const QString& autoUpdaterPath) {
|
||||
QString params = "\"" + QCoreApplication::applicationFilePath() + "\"" + " --restart --noUpdate";
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
BOOL success = CreateProcess(
|
||||
autoUpdaterPath.toUtf8().data(),
|
||||
params.toUtf8().data(),
|
||||
nullptr, // Process handle not inheritable
|
||||
nullptr, // Thread handle not inheritable
|
||||
FALSE, // Set handle inheritance to FALSE
|
||||
CREATE_NEW_CONSOLE, // Opens file in a separate console
|
||||
nullptr, // Use parent's environment block
|
||||
nullptr, // Use parent's starting directory
|
||||
&si, // Pointer to STARTUPINFO structure
|
||||
&pi // Pointer to PROCESS_INFORMATION structure
|
||||
);
|
||||
|
||||
QCoreApplication::instance()->quit();
|
||||
}
|
||||
|
||||
|
||||
HRESULT createSymbolicLink(LPCSTR lpszPathObj, LPCSTR lpszPathLink, LPCSTR lpszDesc, LPCSTR lpszArgs) {
|
||||
IShellLink* psl;
|
||||
|
||||
// Get a pointer to the IShellLink interface. It is assumed that CoInitialize
|
||||
// has already been called.
|
||||
CoInitialize(NULL);
|
||||
HRESULT hres = E_INVALIDARG;
|
||||
hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&psl);
|
||||
if (SUCCEEDED(hres)) {
|
||||
IPersistFile* ppf;
|
||||
|
||||
// Set the path to the shortcut target and add the description.
|
||||
psl->SetPath(lpszPathObj);
|
||||
psl->SetDescription(lpszDesc);
|
||||
psl->SetArguments(lpszArgs);
|
||||
|
||||
// Query IShellLink for the IPersistFile interface, used for saving the
|
||||
// shortcut in persistent storage.
|
||||
hres = psl->QueryInterface(IID_IPersistFile, (LPVOID*)&ppf);
|
||||
|
||||
if (SUCCEEDED(hres)) {
|
||||
WCHAR wsz[MAX_PATH];
|
||||
|
||||
// Ensure that the string is Unicode.
|
||||
MultiByteToWideChar(CP_ACP, 0, lpszPathLink, -1, wsz, MAX_PATH);
|
||||
|
||||
// Add code here to check return value from MultiByteWideChar
|
||||
// for success.
|
||||
|
||||
// Save the link by calling IPersistFile::Save.
|
||||
hres = ppf->Save(wsz, TRUE);
|
||||
ppf->Release();
|
||||
}
|
||||
psl->Release();
|
||||
}
|
||||
CoUninitialize();
|
||||
return SUCCEEDED(hres);
|
||||
}
|
||||
|
||||
bool insertRegistryKey(const std::string& regPath, const std::string& name, const std::string& value) {
|
||||
HKEY key;
|
||||
auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL);
|
||||
if (status == ERROR_SUCCESS) {
|
||||
status = RegSetValueExA(key, name.c_str(), 0, REG_SZ, (const BYTE*)value.c_str(), (DWORD)(value.size() + 1));
|
||||
return (bool) (status == ERROR_SUCCESS);
|
||||
}
|
||||
RegCloseKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool insertRegistryKey(const std::string& regPath, const std::string& name, DWORD value) {
|
||||
HKEY key;
|
||||
auto status = RegCreateKeyExA(HKEY_CURRENT_USER, regPath.c_str(), 0, NULL, REG_OPTION_NON_VOLATILE, KEY_WRITE | KEY_QUERY_VALUE, NULL, &key, NULL);
|
||||
if (status == ERROR_SUCCESS) {
|
||||
status = RegSetValueExA(key, name.c_str(), 0, REG_DWORD, (const BYTE*)&value, sizeof(value));
|
||||
return (bool) TRUE;
|
||||
}
|
||||
RegCloseKey(key);
|
||||
return false;
|
||||
}
|
||||
|
||||
bool deleteRegistryKey(const std::string& regPath) {
|
||||
TCHAR szDelKey[MAX_PATH * 2];
|
||||
StringCchCopy(szDelKey, MAX_PATH * 2, regPath.c_str());
|
||||
auto status = RegDeleteKey(HKEY_CURRENT_USER, szDelKey);
|
||||
if (status == ERROR_SUCCESS) {
|
||||
return (bool) TRUE;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
BOOL isProcessRunning(const char* processName, int& processID) {
|
||||
bool exists = false;
|
||||
PROCESSENTRY32 entry;
|
||||
entry.dwSize = sizeof(PROCESSENTRY32);
|
||||
|
||||
HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, NULL);
|
||||
|
||||
if (Process32First(snapshot, &entry)) {
|
||||
while (Process32Next(snapshot, &entry)) {
|
||||
if (!_stricmp(entry.szExeFile, processName)) {
|
||||
exists = true;
|
||||
processID = entry.th32ProcessID;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
CloseHandle(snapshot);
|
||||
return exists;
|
||||
}
|
||||
|
||||
BOOL shutdownProcess(DWORD dwProcessId, UINT uExitCode) {
|
||||
DWORD dwDesiredAccess = PROCESS_TERMINATE;
|
||||
BOOL bInheritHandle = FALSE;
|
||||
HANDLE hProcess = OpenProcess(dwDesiredAccess, bInheritHandle, dwProcessId);
|
||||
if (hProcess == NULL) {
|
||||
return FALSE;
|
||||
}
|
||||
BOOL result = TerminateProcess(hProcess, uExitCode);
|
||||
CloseHandle(hProcess);
|
||||
return result;
|
||||
}
|
42
launchers/qt/src/Launcher.cpp
Normal file
|
@ -0,0 +1,42 @@
|
|||
#include "Launcher.h"
|
||||
|
||||
#include <QResource>
|
||||
#include <QFileInfo>
|
||||
#include <QQmlContext>
|
||||
#include <QFontDatabase>
|
||||
|
||||
#include "LauncherWindow.h"
|
||||
#include "LauncherState.h"
|
||||
#include "PathUtils.h"
|
||||
|
||||
Launcher::Launcher(int& argc, char**argv) : QGuiApplication(argc, argv) {
|
||||
_launcherState = std::make_shared<LauncherState>();
|
||||
QString platform;
|
||||
#ifdef Q_OS_WIN
|
||||
platform = "Windows";
|
||||
#elif defined(Q_OS_MACOS)
|
||||
platform = "MacOS";
|
||||
#endif
|
||||
_launcherWindow = std::make_unique<LauncherWindow>();
|
||||
_launcherWindow->rootContext()->setContextProperty("LauncherState", _launcherState.get());
|
||||
_launcherWindow->rootContext()->setContextProperty("PathUtils", new PathUtils());
|
||||
_launcherWindow->rootContext()->setContextProperty("Platform", platform);
|
||||
_launcherWindow->setTitle("High Fidelity");
|
||||
_launcherWindow->setFlags(Qt::FramelessWindowHint | Qt::Window);
|
||||
_launcherWindow->setLauncherStatePtr(_launcherState);
|
||||
|
||||
LauncherState::declareQML();
|
||||
|
||||
QFontDatabase::addApplicationFont(PathUtils::fontPath("Graphik-Regular.ttf"));
|
||||
QFontDatabase::addApplicationFont(PathUtils::fontPath("Graphik-Medium.ttf"));
|
||||
QFontDatabase::addApplicationFont(PathUtils::fontPath("Graphik-Semibold.ttf"));
|
||||
|
||||
_launcherWindow->setSource(QUrl(PathUtils::resourcePath("qml/root.qml")));
|
||||
_launcherWindow->setHeight(540);
|
||||
_launcherWindow->setWidth(627);
|
||||
_launcherWindow->setResizeMode(QQuickView::SizeRootObjectToView);
|
||||
_launcherWindow->show();
|
||||
}
|
||||
|
||||
Launcher::~Launcher() {
|
||||
}
|
16
launchers/qt/src/Launcher.h
Normal file
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
#include <memory>
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
class LauncherWindow;
|
||||
class LauncherState;
|
||||
class Launcher : public QGuiApplication {
|
||||
public:
|
||||
Launcher(int& argc, char** argv);
|
||||
~Launcher();
|
||||
|
||||
private:
|
||||
std::unique_ptr<LauncherWindow> _launcherWindow;
|
||||
std::shared_ptr<LauncherState> _launcherState;
|
||||
};
|
243
launchers/qt/src/LauncherInstaller_windows.cpp
Normal file
|
@ -0,0 +1,243 @@
|
|||
#include "LauncherInstaller_windows.h"
|
||||
|
||||
#include "CommandlineOptions.h"
|
||||
#include "Helper.h"
|
||||
#include "PathUtils.h"
|
||||
|
||||
#include <string>
|
||||
#include <chrono>
|
||||
#include <iomanip>
|
||||
#include <ctime>
|
||||
#include <sstream>
|
||||
|
||||
#include <QStandardPaths>
|
||||
#include <QFileInfo>
|
||||
#include <QFile>
|
||||
#include <QDebug>
|
||||
|
||||
LauncherInstaller::LauncherInstaller() {
|
||||
_launcherInstallDir = PathUtils::getLauncherDirectory();
|
||||
_launcherApplicationsDir = PathUtils::getApplicationsDirectory();
|
||||
qDebug() << "Launcher install dir: " << _launcherInstallDir.absolutePath();
|
||||
qDebug() << "Launcher Application dir: " << _launcherApplicationsDir.absolutePath();
|
||||
|
||||
_launcherInstallDir.mkpath(_launcherInstallDir.absolutePath());
|
||||
_launcherApplicationsDir.mkpath(_launcherApplicationsDir.absolutePath());
|
||||
char appPath[MAX_PATH];
|
||||
GetModuleFileNameA(NULL, appPath, MAX_PATH);
|
||||
QString applicationRunningPath = appPath;
|
||||
QFileInfo fileInfo(applicationRunningPath);
|
||||
_launcherRunningFilePath = fileInfo.absoluteFilePath();
|
||||
_launcherRunningDirPath = fileInfo.absoluteDir().absolutePath();
|
||||
qDebug() << "Launcher running file path: " << _launcherRunningFilePath;
|
||||
qDebug() << "Launcher running dir path: " << _launcherRunningDirPath;
|
||||
}
|
||||
|
||||
bool LauncherInstaller::runningOutsideOfInstallDir() {
|
||||
return (QString::compare(_launcherInstallDir.absolutePath(), _launcherRunningDirPath) != 0);
|
||||
}
|
||||
|
||||
void LauncherInstaller::install() {
|
||||
if (runningOutsideOfInstallDir()) {
|
||||
qDebug() << "Installing HQ Launcher....";
|
||||
uninstallOldLauncher();
|
||||
QString oldLauncherPath = PathUtils::getLauncherFilePath();
|
||||
|
||||
if (QFile::exists(oldLauncherPath)) {
|
||||
bool didRemove = QFile::remove(oldLauncherPath);
|
||||
qDebug() << "did remove file: " << didRemove;
|
||||
}
|
||||
qDebug() << "Current launcher location: " << _launcherRunningFilePath;
|
||||
bool success = QFile::copy(_launcherRunningFilePath, oldLauncherPath);
|
||||
if (success) {
|
||||
qDebug() << "Launcher installed: " << oldLauncherPath;
|
||||
} else {
|
||||
qDebug() << "Failed to install: " << oldLauncherPath;
|
||||
}
|
||||
|
||||
deleteShortcuts();
|
||||
createShortcuts();
|
||||
deleteApplicationRegistryKeys();
|
||||
createApplicationRegistryKeys();
|
||||
} else {
|
||||
qDebug() << "Failed to install HQ Launcher";
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherInstaller::createShortcuts() {
|
||||
QString launcherPath = PathUtils::getLauncherFilePath();
|
||||
|
||||
QString uninstallLinkPath = _launcherInstallDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||
|
||||
QDir desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
|
||||
QString appStartLinkPath = _launcherApplicationsDir.absoluteFilePath("HQ Launcher.lnk");
|
||||
QString uninstallAppStartLinkPath = _launcherApplicationsDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||
QString desktopAppLinkPath = desktopDir.absoluteFilePath("HQ Launcher.lnk");
|
||||
|
||||
|
||||
createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)uninstallLinkPath.toStdString().c_str(),
|
||||
(LPCSTR)("Click to Uninstall HQ"), (LPCSTR)("--uninstall"));
|
||||
|
||||
createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)uninstallAppStartLinkPath.toStdString().c_str(),
|
||||
(LPCSTR)("Click to Uninstall HQ"), (LPCSTR)("--uninstall"));
|
||||
|
||||
createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)desktopAppLinkPath.toStdString().c_str(),
|
||||
(LPCSTR)("Click to Setup and Launch HQ"));
|
||||
|
||||
createSymbolicLink((LPCSTR)launcherPath.toStdString().c_str(), (LPCSTR)appStartLinkPath.toStdString().c_str(),
|
||||
(LPCSTR)("Click to Setup and Launch HQ"));
|
||||
}
|
||||
|
||||
QString randomQtString() {
|
||||
const QString possibleCharacters("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789");
|
||||
const int randomStringLength = 5;
|
||||
auto now = std::chrono::system_clock::now();
|
||||
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch());
|
||||
qsrand(duration.count());
|
||||
|
||||
QString randomString;
|
||||
for(int i = 0; i < randomStringLength; i++)
|
||||
{
|
||||
int index = qrand() % possibleCharacters.length();
|
||||
QChar nextChar = possibleCharacters.at(index);
|
||||
randomString.append(nextChar);
|
||||
}
|
||||
return randomString;
|
||||
}
|
||||
|
||||
void LauncherInstaller::uninstall() {
|
||||
qDebug() << "Uninstall Launcher";
|
||||
deleteShortcuts();
|
||||
CommandlineOptions* options = CommandlineOptions::getInstance();
|
||||
if (!options->contains("--resumeUninstall")) {
|
||||
QDir tmpDirectory = QStandardPaths::writableLocation(QStandardPaths::TempLocation);
|
||||
QString destination = tmpDirectory.absolutePath() + "/" + randomQtString() + ".exe";
|
||||
qDebug() << "temp file destination: " << destination;
|
||||
bool copied = QFile::copy(_launcherRunningFilePath, destination);
|
||||
|
||||
if (copied) {
|
||||
QString params = "\"" + _launcherRunningFilePath + "\"" + " --resumeUninstall";
|
||||
STARTUPINFO si;
|
||||
PROCESS_INFORMATION pi;
|
||||
|
||||
ZeroMemory(&si, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
ZeroMemory(&pi, sizeof(pi));
|
||||
|
||||
BOOL success = CreateProcess(
|
||||
destination.toUtf8().data(),
|
||||
params.toUtf8().data(),
|
||||
nullptr, // Process handle not inheritable
|
||||
nullptr, // Thread handle not inheritable
|
||||
FALSE, // Set handle inheritance to FALSE
|
||||
CREATE_NEW_CONSOLE, // Opens file in a separate console
|
||||
nullptr, // Use parent's environment block
|
||||
nullptr, // Use parent's starting directory
|
||||
&si, // Pointer to STARTUPINFO structure
|
||||
&pi // Pointer to PROCESS_INFORMATION structure
|
||||
);
|
||||
} else {
|
||||
qDebug() << "Failed to complete uninstall launcher";
|
||||
}
|
||||
return;
|
||||
}
|
||||
QString launcherPath = _launcherInstallDir.absoluteFilePath("HQ Launcher.exe");
|
||||
if (QFile::exists(launcherPath)) {
|
||||
bool removed = QFile::remove(launcherPath);
|
||||
qDebug() << "Successfully removed " << launcherPath << ": " << removed;
|
||||
}
|
||||
deleteApplicationRegistryKeys();
|
||||
}
|
||||
|
||||
void LauncherInstaller::deleteShortcuts() {
|
||||
QDir desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
QString applicationPath = _launcherApplicationsDir.absolutePath();
|
||||
|
||||
QString uninstallLinkPath = _launcherInstallDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||
if (QFile::exists(uninstallLinkPath)) {
|
||||
QFile::remove(uninstallLinkPath);
|
||||
}
|
||||
|
||||
QString appStartLinkPath = _launcherApplicationsDir.absoluteFilePath("HQ Launcher.lnk");
|
||||
if (QFile::exists(appStartLinkPath)) {
|
||||
QFile::remove(appStartLinkPath);
|
||||
}
|
||||
|
||||
QString uninstallAppStartLinkPath = _launcherApplicationsDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||
if (QFile::exists(uninstallAppStartLinkPath)) {
|
||||
QFile::remove(uninstallAppStartLinkPath);
|
||||
}
|
||||
|
||||
QString desktopAppLinkPath = desktopDir.absoluteFilePath("HQ Launcher.lnk");
|
||||
if (QFile::exists(desktopAppLinkPath)) {
|
||||
QFile::remove(desktopAppLinkPath);
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherInstaller::uninstallOldLauncher() {
|
||||
QDir localAppDir = QStandardPaths::standardLocations(QStandardPaths::AppLocalDataLocation).value(0) + "/../../HQ";
|
||||
QDir startAppDir = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation).value(0) + "/HQ";
|
||||
QDir desktopDir = QStandardPaths::writableLocation(QStandardPaths::DesktopLocation);
|
||||
|
||||
qDebug() << localAppDir.absolutePath();
|
||||
qDebug() << startAppDir.absolutePath();
|
||||
QString desktopAppLinkPath = desktopDir.absoluteFilePath("HQ Launcher.lnk");
|
||||
if (QFile::exists(desktopAppLinkPath)) {
|
||||
QFile::remove(desktopAppLinkPath);
|
||||
}
|
||||
|
||||
QString uninstallLinkPath = localAppDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||
if (QFile::exists(uninstallLinkPath)) {
|
||||
QFile::remove(uninstallLinkPath);
|
||||
}
|
||||
|
||||
QString applicationPath = localAppDir.absoluteFilePath("HQ Launcher.exe");
|
||||
if (QFile::exists(applicationPath)) {
|
||||
QFile::remove(applicationPath);
|
||||
}
|
||||
|
||||
QString appStartLinkPath = startAppDir.absoluteFilePath("HQ Launcher.lnk");
|
||||
if (QFile::exists(appStartLinkPath)) {
|
||||
QFile::remove(appStartLinkPath);
|
||||
}
|
||||
|
||||
QString uninstallAppStartLinkPath = startAppDir.absoluteFilePath("Uninstall HQ.lnk");
|
||||
if (QFile::exists(uninstallAppStartLinkPath)) {
|
||||
QFile::remove(uninstallAppStartLinkPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void LauncherInstaller::createApplicationRegistryKeys() {
|
||||
const std::string REGISTRY_PATH = "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ";
|
||||
bool success = insertRegistryKey(REGISTRY_PATH, "DisplayName", "HQ");
|
||||
std::string installPath = _launcherInstallDir.absolutePath().replace("/", "\\").toStdString();
|
||||
success = insertRegistryKey(REGISTRY_PATH, "InstallLocation", installPath);
|
||||
std::string applicationExe = installPath + "\\HQ Launcher.exe";
|
||||
std::string uninstallPath = applicationExe + " --uninstall";
|
||||
qDebug() << QString::fromStdString(applicationExe);
|
||||
qDebug() << QString::fromStdString(uninstallPath);
|
||||
success = insertRegistryKey(REGISTRY_PATH, "UninstallString", uninstallPath);
|
||||
success = insertRegistryKey(REGISTRY_PATH, "DisplayVersion", std::string(LAUNCHER_BUILD_VERSION));
|
||||
success = insertRegistryKey(REGISTRY_PATH, "DisplayIcon", applicationExe);
|
||||
success = insertRegistryKey(REGISTRY_PATH, "Publisher", "High Fidelity");
|
||||
|
||||
auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
||||
|
||||
std::stringstream date;
|
||||
date << std::put_time(std::localtime(&now), "%Y-%m-%d") ;
|
||||
success = insertRegistryKey(REGISTRY_PATH, "InstallDate", date.str());
|
||||
success = insertRegistryKey(REGISTRY_PATH, "EstimatedSize", (DWORD)14181);
|
||||
success = insertRegistryKey(REGISTRY_PATH, "NoModify", (DWORD)1);
|
||||
success = insertRegistryKey(REGISTRY_PATH, "NoRepair", (DWORD)1);
|
||||
|
||||
qDebug() << "Did succcessfully insertRegistyKeys: " << success;
|
||||
}
|
||||
|
||||
void LauncherInstaller::deleteApplicationRegistryKeys() {
|
||||
const std::string regPath= "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\HQ";
|
||||
bool success = deleteRegistryKey(regPath.c_str());
|
||||
qDebug() << "Did delete Application Registry Keys: " << success;
|
||||
}
|
23
launchers/qt/src/LauncherInstaller_windows.h
Normal file
|
@ -0,0 +1,23 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
class LauncherInstaller {
|
||||
public:
|
||||
LauncherInstaller();
|
||||
~LauncherInstaller() = default;
|
||||
|
||||
void install();
|
||||
void uninstall();
|
||||
bool runningOutsideOfInstallDir();
|
||||
private:
|
||||
void createShortcuts();
|
||||
void uninstallOldLauncher();
|
||||
void createApplicationRegistryKeys();
|
||||
void deleteShortcuts();
|
||||
void deleteApplicationRegistryKeys();
|
||||
|
||||
QDir _launcherInstallDir;
|
||||
QDir _launcherApplicationsDir;
|
||||
QString _launcherRunningFilePath;
|
||||
QString _launcherRunningDirPath;
|
||||
};
|
797
launchers/qt/src/LauncherState.cpp
Normal file
|
@ -0,0 +1,797 @@
|
|||
#include "LauncherState.h"
|
||||
|
||||
#include "CommandlineOptions.h"
|
||||
#include "PathUtils.h"
|
||||
#include "Unzipper.h"
|
||||
#include "Helper.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdlib>
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
#include <QNetworkRequest>
|
||||
#include <QNetworkReply>
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QUrlQuery>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <QThreadPool>
|
||||
|
||||
#include <QEventLoop>
|
||||
|
||||
#include <qregularexpression.h>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
//#define BREAK_ON_ERROR
|
||||
//#define DEBUG_UI
|
||||
|
||||
const QString configHomeLocationKey { "homeLocation" };
|
||||
const QString configLastLoginKey { "lastLogin" };
|
||||
const QString configLoggedInKey{ "loggedIn" };
|
||||
const QString configLauncherPathKey{ "launcherPath" };
|
||||
|
||||
Q_INVOKABLE void LauncherState::openURLInBrowser(QString url) {
|
||||
#ifdef Q_OS_WIN
|
||||
ShellExecute(0, 0, url.toLatin1(), 0, 0 , SW_SHOW);
|
||||
#elif defined(Q_OS_DARWIN)
|
||||
system("open \"" + url.toLatin1() + "\"");
|
||||
#endif
|
||||
}
|
||||
|
||||
void LauncherState::toggleDebugState() {
|
||||
#ifdef DEBUG_UI
|
||||
_isDebuggingScreens = !_isDebuggingScreens;
|
||||
|
||||
UIState updatedUIState = getUIState();
|
||||
if (_uiState != updatedUIState) {
|
||||
emit uiStateChanged();
|
||||
emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource()));
|
||||
_uiState = getUIState();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void LauncherState::gotoNextDebugScreen() {
|
||||
#ifdef DEBUG_UI
|
||||
if (_currentDebugScreen < (UIState::UI_STATE_NUM - 1)) {
|
||||
_currentDebugScreen = (UIState)(_currentDebugScreen + 1);
|
||||
emit uiStateChanged();
|
||||
emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource()));
|
||||
_uiState = getUIState();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
void LauncherState::gotoPreviousDebugScreen() {
|
||||
#ifdef DEBUG_UI
|
||||
if (_currentDebugScreen > 0) {
|
||||
_currentDebugScreen = (UIState)(_currentDebugScreen - 1);
|
||||
emit uiStateChanged();
|
||||
emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource()));
|
||||
_uiState = getUIState();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LauncherState::shouldDownloadContentCache() const {
|
||||
return !_contentCacheURL.isEmpty() && !QFile::exists(PathUtils::getContentCachePath());
|
||||
}
|
||||
|
||||
void LauncherState::setLastSignupErrorMessage(const QString& msg) {
|
||||
_lastSignupErrorMessage = msg;
|
||||
emit lastSignupErrorMessageChanged();
|
||||
}
|
||||
|
||||
void LauncherState::setLastLoginErrorMessage(const QString& msg) {
|
||||
_lastLoginErrorMessage = msg;
|
||||
emit lastLoginErrorMessageChanged();
|
||||
}
|
||||
|
||||
static const std::array<QString, LauncherState::UIState::UI_STATE_NUM> QML_FILE_FOR_UI_STATE =
|
||||
{ { "qml/SplashScreen.qml", "qml/HFBase/CreateAccountBase.qml", "qml/HFBase/LoginBase.qml",
|
||||
"qml/Download.qml", "qml/DownloadFinished.qml", "qml/HFBase/Error.qml" } };
|
||||
|
||||
void LauncherState::ASSERT_STATE(ApplicationState state) {
|
||||
if (_applicationState != state) {
|
||||
qDebug() << "Unexpected state, current: " << _applicationState << ", expected: " << state;
|
||||
#ifdef BREAK_ON_ERROR
|
||||
__debugbreak();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherState::ASSERT_STATE(const std::vector<ApplicationState>& states) {
|
||||
for (auto state : states) {
|
||||
if (_applicationState == state) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Unexpected state, current: " << _applicationState << ", expected: " << states;
|
||||
#ifdef BREAK_ON_ERROR
|
||||
__debugbreak();
|
||||
#endif
|
||||
}
|
||||
|
||||
LauncherState::LauncherState() {
|
||||
_launcherDirectory = PathUtils::getLauncherDirectory();
|
||||
qDebug() << "Launcher directory: " << _launcherDirectory.absolutePath();
|
||||
_launcherDirectory.mkpath(_launcherDirectory.absolutePath());
|
||||
_launcherDirectory.mkpath(PathUtils::getDownloadDirectory().absolutePath());
|
||||
requestBuilds();
|
||||
}
|
||||
|
||||
QString LauncherState::getCurrentUISource() const {
|
||||
return QML_FILE_FOR_UI_STATE[getUIState()];
|
||||
}
|
||||
|
||||
void LauncherState::declareQML() {
|
||||
qmlRegisterType<LauncherState>("HQLauncher", 1, 0, "ApplicationState");
|
||||
}
|
||||
|
||||
LauncherState::UIState LauncherState::getUIState() const {
|
||||
if (_isDebuggingScreens) {
|
||||
return _currentDebugScreen;
|
||||
}
|
||||
|
||||
switch (_applicationState) {
|
||||
case ApplicationState::Init:
|
||||
case ApplicationState::RequestingBuilds:
|
||||
case ApplicationState::GettingCurrentClientVersion:
|
||||
return UIState::SplashScreen;
|
||||
case ApplicationState::WaitingForLogin:
|
||||
case ApplicationState::RequestingLogin:
|
||||
return UIState::LoginScreen;
|
||||
case ApplicationState::WaitingForSignup:
|
||||
case ApplicationState::RequestingSignup:
|
||||
case ApplicationState::RequestingLoginAfterSignup:
|
||||
return UIState::SignupScreen;
|
||||
case ApplicationState::DownloadingClient:
|
||||
case ApplicationState::InstallingClient:
|
||||
case ApplicationState::DownloadingContentCache:
|
||||
case ApplicationState::InstallingContentCache:
|
||||
return UIState::DownloadScreen;
|
||||
case ApplicationState::LaunchingHighFidelity:
|
||||
return UIState::DownloadFinishedScreen;
|
||||
case ApplicationState::UnexpectedError:
|
||||
#ifdef BREAK_ON_ERROR
|
||||
__debugbreak();
|
||||
#endif
|
||||
return UIState::ErrorScreen;
|
||||
default:
|
||||
qDebug() << "FATAL: No UI for" << _applicationState;
|
||||
#ifdef BREAK_ON_ERROR
|
||||
__debugbreak();
|
||||
#endif
|
||||
return UIState::ErrorScreen;
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherState::restart() {
|
||||
setApplicationState(ApplicationState::Init);
|
||||
requestBuilds();
|
||||
}
|
||||
|
||||
void LauncherState::requestBuilds() {
|
||||
ASSERT_STATE(ApplicationState::Init);
|
||||
setApplicationState(ApplicationState::RequestingBuilds);
|
||||
|
||||
auto request = new BuildsRequest();
|
||||
|
||||
QObject::connect(request, &BuildsRequest::finished, this, [=] {
|
||||
ASSERT_STATE(ApplicationState::RequestingBuilds);
|
||||
if (request->getError() != BuildsRequest::Error::None) {
|
||||
setApplicationStateError("Could not retrieve latest builds");
|
||||
return;
|
||||
}
|
||||
|
||||
_latestBuilds = request->getLatestBuilds();
|
||||
|
||||
CommandlineOptions* options = CommandlineOptions::getInstance();
|
||||
qDebug() << "Latest version: " << _latestBuilds.launcherBuild.latestVersion
|
||||
<< "Curretn version: " << getBuildVersion().toInt();
|
||||
if (shouldDownloadLauncher() && !options->contains("--noUpdate")) {
|
||||
downloadLauncher();
|
||||
return;
|
||||
}
|
||||
getCurrentClientVersion();
|
||||
});
|
||||
|
||||
request->send(_networkAccessManager);
|
||||
}
|
||||
|
||||
QString LauncherState::getBuildVersion() {
|
||||
QString buildVersion { LAUNCHER_BUILD_VERSION };
|
||||
QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment();
|
||||
if (processEnvironment.contains("HQ_LAUNCHER_BUILD_VERSION")) {
|
||||
buildVersion = processEnvironment.value("HQ_LAUNCHER_BUILD_VERSION");
|
||||
}
|
||||
return buildVersion;
|
||||
}
|
||||
|
||||
bool LauncherState::shouldDownloadLauncher() {
|
||||
return _latestBuilds.launcherBuild.latestVersion != getBuildVersion().toInt();
|
||||
}
|
||||
|
||||
void LauncherState::getCurrentClientVersion() {
|
||||
ASSERT_STATE(ApplicationState::RequestingBuilds);
|
||||
|
||||
setApplicationState(ApplicationState::GettingCurrentClientVersion);
|
||||
|
||||
QProcess client;
|
||||
QEventLoop loop;
|
||||
|
||||
connect(&client, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), &loop, &QEventLoop::exit, Qt::QueuedConnection);
|
||||
connect(&client, &QProcess::errorOccurred, &loop, &QEventLoop::exit, Qt::QueuedConnection);
|
||||
|
||||
client.start(PathUtils::getClientExecutablePath(), { "--version" });
|
||||
loop.exec();
|
||||
|
||||
// TODO Handle errors
|
||||
auto output = client.readAllStandardOutput();
|
||||
|
||||
QRegularExpression regex { "Interface (?<version>\\d+)(-.*)?" };
|
||||
|
||||
auto match = regex.match(output);
|
||||
|
||||
if (match.hasMatch()) {
|
||||
_currentClientVersion = match.captured("version");
|
||||
} else {
|
||||
_currentClientVersion = QString::null;
|
||||
}
|
||||
qDebug() << "Current client version is: " << _currentClientVersion;
|
||||
|
||||
{
|
||||
auto path = PathUtils::getConfigFilePath();
|
||||
QFile configFile{ path };
|
||||
|
||||
if (configFile.open(QIODevice::ReadOnly)) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll());
|
||||
auto root = doc.object();
|
||||
|
||||
_config.launcherPath = PathUtils::getLauncherFilePath();
|
||||
_config.loggedIn = false;
|
||||
if (root.contains(configLoggedInKey)) {
|
||||
_config.loggedIn = root[configLoggedInKey].toBool();
|
||||
}
|
||||
if (root.contains(configLastLoginKey)) {
|
||||
_config.lastLogin = root[configLastLoginKey].toString();
|
||||
}
|
||||
if (root.contains(configHomeLocationKey)) {
|
||||
_config.homeLocation = root[configHomeLocationKey].toString();
|
||||
}
|
||||
if (root.contains(configLauncherPathKey)) {
|
||||
_config.launcherPath = root[configLauncherPathKey].toString();
|
||||
}
|
||||
} else {
|
||||
qDebug() << "Failed to open config.json";
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "Is logged-in: " << _config.loggedIn;
|
||||
if (_config.loggedIn) {
|
||||
downloadClient();
|
||||
} else {
|
||||
if (_config.lastLogin.isEmpty()) {
|
||||
setApplicationState(ApplicationState::WaitingForSignup);
|
||||
} else {
|
||||
_lastUsedUsername = _config.lastLogin;
|
||||
setApplicationState(ApplicationState::WaitingForLogin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void LauncherState::gotoSignup() {
|
||||
if (_applicationState == ApplicationState::WaitingForLogin) {
|
||||
setLastSignupErrorMessage("");
|
||||
_lastLoginErrorMessage = "";
|
||||
setApplicationState(ApplicationState::WaitingForSignup);
|
||||
} else {
|
||||
qDebug() << "Error, can't switch to signup page, current state is: " << _applicationState;
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherState::gotoLogin() {
|
||||
if (_applicationState == ApplicationState::WaitingForSignup) {
|
||||
setLastLoginErrorMessage("");
|
||||
setApplicationState(ApplicationState::WaitingForLogin);
|
||||
} else {
|
||||
qDebug() << "Error, can't switch to signup page, current state is: " << _applicationState;
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherState::signup(QString email, QString username, QString password, QString displayName) {
|
||||
ASSERT_STATE(ApplicationState::WaitingForSignup);
|
||||
|
||||
_username = username;
|
||||
_password = password;
|
||||
|
||||
setApplicationState(ApplicationState::RequestingSignup);
|
||||
|
||||
auto signupRequest = new SignupRequest();
|
||||
|
||||
_displayName = displayName;
|
||||
|
||||
{
|
||||
_lastSignupError = SignupRequest::Error::None;
|
||||
emit lastSignupErrorChanged();
|
||||
}
|
||||
|
||||
QObject::connect(signupRequest, &SignupRequest::finished, this, [this, signupRequest, username] {
|
||||
signupRequest->deleteLater();
|
||||
|
||||
|
||||
_lastSignupError = signupRequest->getError();
|
||||
emit lastSignupErrorChanged();
|
||||
|
||||
auto err = signupRequest->getError();
|
||||
if (err == SignupRequest::Error::ExistingUsername) {
|
||||
setLastSignupErrorMessage(_username + " is already taken. Please try a different username.");
|
||||
setApplicationState(ApplicationState::WaitingForSignup);
|
||||
return;
|
||||
} else if (err == SignupRequest::Error::BadPassword) {
|
||||
setLastSignupErrorMessage("That's an invalid password. Must be at least 6 characters.");
|
||||
setApplicationState(ApplicationState::WaitingForSignup);
|
||||
return;
|
||||
} else if (err == SignupRequest::Error::BadUsername) {
|
||||
setLastSignupErrorMessage("That's an invalid username. Please try another username.");
|
||||
setApplicationState(ApplicationState::WaitingForSignup);
|
||||
return;
|
||||
} else if (err == SignupRequest::Error::UserProfileAlreadyCompleted) {
|
||||
setLastSignupErrorMessage("An account with this email already exists. Please <b><a href='login'>log in</a></b>.");
|
||||
setApplicationState(ApplicationState::WaitingForSignup);
|
||||
return;
|
||||
} else if (err == SignupRequest::Error::NoSuchEmail) {
|
||||
setLastSignupErrorMessage("That email isn't setup yet. <a href='https://www.highfidelity.com/hq-support'>Request access</a>.");
|
||||
setApplicationState(ApplicationState::WaitingForSignup);
|
||||
return;
|
||||
} else if (err != SignupRequest::Error::None) {
|
||||
setApplicationStateError("Failed to sign up. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
setApplicationState(ApplicationState::RequestingLoginAfterSignup);
|
||||
|
||||
// After successfully signing up, attempt to login
|
||||
auto loginRequest = new LoginRequest();
|
||||
|
||||
_lastUsedUsername = username;
|
||||
_config.lastLogin = username;
|
||||
|
||||
connect(loginRequest, &LoginRequest::finished, this, [this, loginRequest]() {
|
||||
ASSERT_STATE(ApplicationState::RequestingLoginAfterSignup);
|
||||
|
||||
loginRequest->deleteLater();
|
||||
|
||||
auto err = loginRequest->getError();
|
||||
if (err == LoginRequest::Error::BadUsernameOrPassword) {
|
||||
setLastLoginErrorMessage("Invalid username or password.");
|
||||
setApplicationState(ApplicationState::WaitingForLogin);
|
||||
return;
|
||||
} else if (err != LoginRequest::Error::None) {
|
||||
setApplicationStateError("Failed to login. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
_config.loggedIn = true;
|
||||
_loginResponse = loginRequest->getToken();
|
||||
_loginTokenResponse = loginRequest->getRawToken();
|
||||
|
||||
requestSettings();
|
||||
});
|
||||
|
||||
setApplicationState(ApplicationState::RequestingLoginAfterSignup);
|
||||
loginRequest->send(_networkAccessManager, _username, _password);
|
||||
});
|
||||
signupRequest->send(_networkAccessManager, email, username, password);
|
||||
}
|
||||
|
||||
|
||||
void LauncherState::login(QString username, QString password, QString displayName) {
|
||||
ASSERT_STATE(ApplicationState::WaitingForLogin);
|
||||
|
||||
setApplicationState(ApplicationState::RequestingLogin);
|
||||
|
||||
_displayName = displayName;
|
||||
|
||||
auto request = new LoginRequest();
|
||||
|
||||
connect(request, &LoginRequest::finished, this, [this, request, username]() {
|
||||
ASSERT_STATE(ApplicationState::RequestingLogin);
|
||||
|
||||
request->deleteLater();
|
||||
|
||||
auto err = request->getError();
|
||||
if (err == LoginRequest::Error::BadUsernameOrPassword) {
|
||||
setLastLoginErrorMessage("Invalid username or password");
|
||||
setApplicationState(ApplicationState::WaitingForLogin);
|
||||
return;
|
||||
} else if (err != LoginRequest::Error::None) {
|
||||
setApplicationStateError("Failed to login. Please try again.");
|
||||
return;
|
||||
}
|
||||
|
||||
_lastUsedUsername = username;
|
||||
_config.lastLogin = username;
|
||||
_config.loggedIn = true;
|
||||
_loginResponse = request->getToken();
|
||||
_loginTokenResponse = request->getRawToken();
|
||||
|
||||
requestSettings();
|
||||
});
|
||||
|
||||
request->send(_networkAccessManager, username, password);
|
||||
}
|
||||
|
||||
void LauncherState::requestSettings() {
|
||||
// TODO Request settings if already logged in
|
||||
qDebug() << "Requesting settings";
|
||||
|
||||
auto request = new UserSettingsRequest();
|
||||
|
||||
connect(request, &UserSettingsRequest::finished, this, [this, request]() {
|
||||
auto userSettings = request->getUserSettings();
|
||||
if (userSettings.homeLocation.isEmpty()) {
|
||||
_config.homeLocation = "file:///~/serverless/tutorial.json";
|
||||
_contentCacheURL = "";
|
||||
} else {
|
||||
_config.homeLocation = userSettings.homeLocation;
|
||||
auto host = QUrl(_config.homeLocation).host();
|
||||
_contentCacheURL = "http://orgs.highfidelity.com/host-content-cache/" + host + ".zip";
|
||||
|
||||
qDebug() << "Content cache url: " << _contentCacheURL;
|
||||
}
|
||||
|
||||
qDebug() << "Home location is: " << _config.homeLocation;
|
||||
qDebug() << "Content cache url is: " << _contentCacheURL;
|
||||
|
||||
downloadClient();
|
||||
});
|
||||
|
||||
request->send(_networkAccessManager, _loginResponse);
|
||||
}
|
||||
|
||||
void LauncherState::downloadClient() {
|
||||
ASSERT_STATE({ ApplicationState::RequestingLogin, ApplicationState::RequestingLoginAfterSignup });
|
||||
|
||||
Build build;
|
||||
if (!_latestBuilds.getBuild(_buildTag, &build)) {
|
||||
qDebug() << "Cannot determine latest build";
|
||||
setApplicationState(ApplicationState::UnexpectedError);
|
||||
return;
|
||||
}
|
||||
|
||||
if (QString::number(build.latestVersion) == _currentClientVersion) {
|
||||
qDebug() << "Existing client install is up-to-date.";
|
||||
downloadContentCache();
|
||||
return;
|
||||
}
|
||||
|
||||
_interfaceDownloadProgress = 0;
|
||||
setApplicationState(ApplicationState::DownloadingClient);
|
||||
|
||||
// Start client download
|
||||
{
|
||||
qDebug() << "Latest build: " << build.tag << build.buildNumber << build.latestVersion << build.installerZipURL;
|
||||
auto request = new QNetworkRequest(QUrl(build.installerZipURL));
|
||||
auto reply = _networkAccessManager.get(*request);
|
||||
|
||||
QDir downloadDir{ PathUtils::getDownloadDirectory() };
|
||||
_clientZipFile.setFileName(downloadDir.absoluteFilePath("client.zip"));
|
||||
|
||||
qDebug() << "Opening " << _clientZipFile.fileName();
|
||||
if (!_clientZipFile.open(QIODevice::WriteOnly)) {
|
||||
setApplicationState(ApplicationState::UnexpectedError);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, &LauncherState::clientDownloadComplete);
|
||||
connect(reply, &QNetworkReply::readyRead, this, [this, reply]() {
|
||||
char buf[4096];
|
||||
while (reply->bytesAvailable() > 0) {
|
||||
qint64 size;
|
||||
size = reply->read(buf, (qint64)sizeof(buf));
|
||||
if (size == 0) {
|
||||
break;
|
||||
}
|
||||
_clientZipFile.write(buf, size);
|
||||
}
|
||||
});
|
||||
connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) {
|
||||
_interfaceDownloadProgress = (float)received / (float)total;
|
||||
emit downloadProgressChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherState::launcherDownloadComplete() {
|
||||
_launcherZipFile.close();
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
installLauncher();
|
||||
#elif defined(Q_OS_WIN)
|
||||
launchAutoUpdater(_launcherZipFile.fileName());
|
||||
#endif
|
||||
}
|
||||
|
||||
void LauncherState::clientDownloadComplete() {
|
||||
ASSERT_STATE(ApplicationState::DownloadingClient);
|
||||
_clientZipFile.close();
|
||||
installClient();
|
||||
}
|
||||
|
||||
|
||||
float LauncherState::calculateDownloadProgress() const{
|
||||
if (shouldDownloadContentCache()) {
|
||||
return (_interfaceDownloadProgress * 0.40f) + (_interfaceInstallProgress * 0.10f) +
|
||||
(_contentInstallProgress * 0.40f) + (_contentDownloadProgress * 0.10f);
|
||||
}
|
||||
|
||||
return (_interfaceDownloadProgress * 0.80f) + (_interfaceInstallProgress * 0.20f);
|
||||
}
|
||||
|
||||
void LauncherState::installClient() {
|
||||
ASSERT_STATE(ApplicationState::DownloadingClient);
|
||||
setApplicationState(ApplicationState::InstallingClient);
|
||||
|
||||
|
||||
auto clientDir = PathUtils::getClientDirectory();
|
||||
|
||||
auto clientPath = clientDir.absolutePath();
|
||||
_launcherDirectory.rmpath(clientPath);
|
||||
_launcherDirectory.mkpath(clientPath);
|
||||
|
||||
_interfaceInstallProgress = 0;
|
||||
|
||||
qDebug() << "Unzipping " << _clientZipFile.fileName() << " to " << clientDir.absolutePath();
|
||||
|
||||
auto unzipper = new Unzipper(_clientZipFile.fileName(), clientDir);
|
||||
unzipper->setAutoDelete(true);
|
||||
connect(unzipper, &Unzipper::progress, this, [this](float progress) {
|
||||
_interfaceInstallProgress = progress;
|
||||
emit downloadProgressChanged();
|
||||
});
|
||||
connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) {
|
||||
if (error) {
|
||||
qDebug() << "Unzipper finished with error: " << errorMessage;
|
||||
setApplicationState(ApplicationState::UnexpectedError);
|
||||
} else {
|
||||
qDebug() << "Unzipper finished without error";
|
||||
downloadContentCache();
|
||||
}
|
||||
});
|
||||
QThreadPool::globalInstance()->start(unzipper);
|
||||
}
|
||||
|
||||
void LauncherState::downloadLauncher() {
|
||||
auto request = new QNetworkRequest(QUrl(_latestBuilds.launcherBuild.installerZipURL));
|
||||
auto reply = _networkAccessManager.get(*request);
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
_launcherZipFile.setFileName(_launcherDirectory.absoluteFilePath("launcher.zip"));
|
||||
#elif defined(Q_OS_WIN)
|
||||
_launcherZipFile.setFileName(_launcherDirectory.absoluteFilePath("launcher.exe"));
|
||||
#endif
|
||||
|
||||
qDebug() << "opening " << _launcherZipFile.fileName();
|
||||
|
||||
if (!_launcherZipFile.open(QIODevice::WriteOnly)) {
|
||||
setApplicationState(ApplicationState::UnexpectedError);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, &LauncherState::launcherDownloadComplete);
|
||||
connect(reply, &QNetworkReply::readyRead, this, [this, reply]() {
|
||||
char buf[4096];
|
||||
while (reply->bytesAvailable() > 0) {
|
||||
qint64 size;
|
||||
size = reply->read(buf, (qint64)sizeof(buf));
|
||||
if (size == 0) {
|
||||
break;
|
||||
}
|
||||
_launcherZipFile.write(buf, size);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void LauncherState::installLauncher() {
|
||||
_launcherDirectory.rmpath("launcher_install");
|
||||
_launcherDirectory.mkpath("launcher_install");
|
||||
auto installDir = _launcherDirectory.absoluteFilePath("launcher_install");
|
||||
|
||||
qDebug() << "Uzipping " << _launcherZipFile.fileName() << " to " << installDir;
|
||||
|
||||
auto unzipper = new Unzipper(_launcherZipFile.fileName(), QDir(installDir));
|
||||
unzipper->setAutoDelete(true);
|
||||
connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) {
|
||||
if (error) {
|
||||
qDebug() << "Unzipper finished with error: " << errorMessage;
|
||||
} else {
|
||||
qDebug() << "Unzipper finished without error";
|
||||
|
||||
QDir installDirectory = _launcherDirectory.filePath("launcher_install");
|
||||
QString launcherPath;
|
||||
#if defined(Q_OS_WIN)
|
||||
launcherPath = installDirectory.absoluteFilePath("HQ Launcher.exe");
|
||||
#elif defined(Q_OS_MACOS)
|
||||
launcherPath = installDirectory.absoluteFilePath("HQ Launcher.app");
|
||||
#endif
|
||||
::launchAutoUpdater(launcherPath);
|
||||
}
|
||||
});
|
||||
|
||||
QThreadPool::globalInstance()->start(unzipper);
|
||||
}
|
||||
|
||||
void LauncherState::downloadContentCache() {
|
||||
ASSERT_STATE({ ApplicationState::RequestingLogin, ApplicationState::InstallingClient });
|
||||
|
||||
// Start content set cache download
|
||||
if (shouldDownloadContentCache()) {
|
||||
setApplicationState(ApplicationState::DownloadingContentCache);
|
||||
|
||||
_contentDownloadProgress = 0;
|
||||
|
||||
qDebug() << "Downloading content cache from: " << _contentCacheURL;
|
||||
QNetworkRequest request{ QUrl(_contentCacheURL) };
|
||||
request.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
auto reply = _networkAccessManager.get(request);
|
||||
|
||||
QDir downloadDir{ PathUtils::getDownloadDirectory() };
|
||||
_contentZipFile.setFileName(downloadDir.absoluteFilePath("content_cache.zip"));
|
||||
|
||||
qDebug() << "Opening " << _contentZipFile.fileName();
|
||||
if (!_contentZipFile.open(QIODevice::WriteOnly)) {
|
||||
setApplicationState(ApplicationState::UnexpectedError);
|
||||
return;
|
||||
}
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, &LauncherState::contentCacheDownloadComplete);
|
||||
connect(reply, &QNetworkReply::downloadProgress, this, [this](qint64 received, qint64 total) {
|
||||
_contentDownloadProgress = (float)received / (float)total;
|
||||
emit downloadProgressChanged();
|
||||
});
|
||||
} else {
|
||||
launchClient();
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherState::contentCacheDownloadComplete() {
|
||||
ASSERT_STATE(ApplicationState::DownloadingContentCache);
|
||||
|
||||
auto reply = static_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (reply->error()) {
|
||||
qDebug() << "Error downloading content cache: " << reply->error() << reply->readAll();
|
||||
qDebug() << "Continuing to launch client";
|
||||
_contentDownloadProgress = 100.0f;
|
||||
_contentInstallProgress = 100.0f;
|
||||
launchClient();
|
||||
return;
|
||||
}
|
||||
|
||||
char buf[4096];
|
||||
while (reply->bytesAvailable() > 0) {
|
||||
qint64 size;
|
||||
size = reply->read(buf, (qint64)sizeof(buf));
|
||||
_contentZipFile.write(buf, size);
|
||||
}
|
||||
|
||||
_contentZipFile.close();
|
||||
|
||||
installContentCache();
|
||||
}
|
||||
|
||||
|
||||
void LauncherState::installContentCache() {
|
||||
ASSERT_STATE(ApplicationState::DownloadingContentCache);
|
||||
setApplicationState(ApplicationState::InstallingContentCache);
|
||||
|
||||
auto installDir = PathUtils::getContentCachePath();
|
||||
|
||||
qDebug() << "Unzipping " << _contentZipFile.fileName() << " to " << installDir;
|
||||
|
||||
_contentInstallProgress = 0;
|
||||
|
||||
auto unzipper = new Unzipper(_contentZipFile.fileName(), QDir(installDir));
|
||||
unzipper->setAutoDelete(true);
|
||||
connect(unzipper, &Unzipper::progress, this, [this](float progress) {
|
||||
qDebug() << "Unzipper progress (content cache): " << progress;
|
||||
_contentInstallProgress = progress;
|
||||
emit downloadProgressChanged();
|
||||
});
|
||||
connect(unzipper, &Unzipper::finished, this, [this](bool error, QString errorMessage) {
|
||||
if (error) {
|
||||
qDebug() << "Unzipper finished with error: " << errorMessage;
|
||||
setApplicationState(ApplicationState::UnexpectedError);
|
||||
} else {
|
||||
qDebug() << "Unzipper finished without error";
|
||||
launchClient();
|
||||
}
|
||||
});
|
||||
QThreadPool::globalInstance()->start(unzipper);
|
||||
|
||||
}
|
||||
|
||||
#include <QTimer>
|
||||
#include <QCoreApplication>
|
||||
void LauncherState::launchClient() {
|
||||
ASSERT_STATE({
|
||||
ApplicationState::RequestingLogin,
|
||||
ApplicationState::InstallingClient,
|
||||
ApplicationState::InstallingContentCache
|
||||
});
|
||||
|
||||
setApplicationState(ApplicationState::LaunchingHighFidelity);
|
||||
|
||||
QDir installDirectory = PathUtils::getClientDirectory();
|
||||
QString clientPath = PathUtils::getClientExecutablePath();
|
||||
|
||||
auto path = PathUtils::getConfigFilePath();
|
||||
QFile configFile{ path };
|
||||
if (configFile.open(QIODevice::ReadWrite | QIODevice::Truncate)) {
|
||||
QJsonDocument doc = QJsonDocument::fromJson(configFile.readAll());
|
||||
doc.setObject({
|
||||
{ configHomeLocationKey, _config.homeLocation },
|
||||
{ configLastLoginKey, _config.lastLogin },
|
||||
{ configLoggedInKey, _config.loggedIn },
|
||||
{ configLauncherPathKey, PathUtils::getLauncherFilePath() },
|
||||
});
|
||||
qint64 result = configFile.write(doc.toJson());
|
||||
configFile.close();
|
||||
qDebug() << "Wrote data to config data: " << result;
|
||||
} else {
|
||||
qDebug() << "Failed to open config file";
|
||||
}
|
||||
|
||||
QString defaultScriptsPath;
|
||||
#if defined(Q_OS_WIN)
|
||||
defaultScriptsPath = installDirectory.filePath("scripts/simplifiedUIBootstrapper.js");
|
||||
#elif defined(Q_OS_MACOS)
|
||||
defaultScriptsPath = installDirectory.filePath("interface.app/Contents/Resources/scripts/simplifiedUIBootstrapper.js");
|
||||
#endif
|
||||
|
||||
QString contentCachePath = _launcherDirectory.filePath("cache");
|
||||
|
||||
::launchClient(clientPath, _config.homeLocation, defaultScriptsPath, _displayName, contentCachePath, _loginTokenResponse);
|
||||
QTimer::singleShot(3000, QCoreApplication::instance(), &QCoreApplication::quit);
|
||||
}
|
||||
|
||||
void LauncherState::setApplicationStateError(QString errorMessage) {
|
||||
_applicationErrorMessage = errorMessage;
|
||||
setApplicationState(ApplicationState::UnexpectedError);
|
||||
}
|
||||
|
||||
void LauncherState::setApplicationState(ApplicationState state) {
|
||||
qDebug() << "Changing application state: " << _applicationState << " -> " << state;
|
||||
|
||||
if (state == ApplicationState::UnexpectedError) {
|
||||
#ifdef BREAK_ON_ERROR
|
||||
__debugbreak();
|
||||
#endif
|
||||
}
|
||||
|
||||
_applicationState = state;
|
||||
UIState updatedUIState = getUIState();
|
||||
if (_uiState != updatedUIState) {
|
||||
emit uiStateChanged();
|
||||
emit updateSourceUrl(PathUtils::resourcePath(getCurrentUISource()));
|
||||
_uiState = getUIState();
|
||||
}
|
||||
|
||||
emit applicationStateChanged();
|
||||
}
|
||||
|
||||
LauncherState::ApplicationState LauncherState::getApplicationState() const {
|
||||
return _applicationState;
|
||||
}
|
||||
|
194
launchers/qt/src/LauncherState.h
Normal file
|
@ -0,0 +1,194 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QUrl>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QFile>
|
||||
|
||||
#include "LoginRequest.h"
|
||||
#include "SignupRequest.h"
|
||||
#include "UserSettingsRequest.h"
|
||||
#include "BuildsRequest.h"
|
||||
|
||||
struct LauncherConfig {
|
||||
QString lastLogin{ "" };
|
||||
QString launcherPath{ "" };
|
||||
bool loggedIn{ false };
|
||||
QString homeLocation{ "" };
|
||||
};
|
||||
|
||||
class LauncherState : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
Q_PROPERTY(UIState uiState READ getUIState NOTIFY uiStateChanged)
|
||||
Q_PROPERTY(ApplicationState applicationState READ getApplicationState NOTIFY applicationStateChanged)
|
||||
Q_PROPERTY(float downloadProgress READ getDownloadProgress NOTIFY downloadProgressChanged)
|
||||
Q_PROPERTY(QString lastLoginErrorMessage READ getLastLoginErrorMessage NOTIFY lastLoginErrorMessageChanged)
|
||||
Q_PROPERTY(QString lastSignupErrorMessage READ getLastSignupErrorMessage NOTIFY lastSignupErrorMessageChanged)
|
||||
Q_PROPERTY(QString buildVersion READ getBuildVersion)
|
||||
Q_PROPERTY(QString lastUsedUsername READ getLastUsedUsername)
|
||||
|
||||
public:
|
||||
LauncherState();
|
||||
~LauncherState() = default;
|
||||
|
||||
enum UIState {
|
||||
SplashScreen = 0,
|
||||
SignupScreen,
|
||||
LoginScreen,
|
||||
DownloadScreen,
|
||||
DownloadFinishedScreen,
|
||||
ErrorScreen,
|
||||
|
||||
UI_STATE_NUM
|
||||
};
|
||||
|
||||
enum class ApplicationState {
|
||||
Init,
|
||||
|
||||
UnexpectedError,
|
||||
|
||||
RequestingBuilds,
|
||||
GettingCurrentClientVersion,
|
||||
|
||||
WaitingForLogin,
|
||||
RequestingLogin,
|
||||
|
||||
WaitingForSignup,
|
||||
RequestingSignup,
|
||||
RequestingLoginAfterSignup,
|
||||
|
||||
DownloadingClient,
|
||||
DownloadingLauncher,
|
||||
DownloadingContentCache,
|
||||
|
||||
InstallingClient,
|
||||
InstallingLauncher,
|
||||
InstallingContentCache,
|
||||
|
||||
LaunchingHighFidelity,
|
||||
};
|
||||
|
||||
Q_ENUM(ApplicationState)
|
||||
|
||||
bool _isDebuggingScreens{ false };
|
||||
UIState _currentDebugScreen{ UIState::SplashScreen };
|
||||
void toggleDebugState();
|
||||
void gotoNextDebugScreen();
|
||||
void gotoPreviousDebugScreen();
|
||||
|
||||
Q_INVOKABLE QString getCurrentUISource() const;
|
||||
|
||||
void ASSERT_STATE(ApplicationState state);
|
||||
void ASSERT_STATE(const std::vector<ApplicationState>& states);
|
||||
|
||||
static void declareQML();
|
||||
|
||||
UIState getUIState() const;
|
||||
|
||||
void setLastLoginErrorMessage(const QString& msg);
|
||||
QString getLastLoginErrorMessage() const { return _lastLoginErrorMessage; }
|
||||
|
||||
void setLastSignupErrorMessage(const QString& msg);
|
||||
QString getLastSignupErrorMessage() const { return _lastSignupErrorMessage; }
|
||||
|
||||
QString getBuildVersion();
|
||||
QString getLastUsedUsername() const { return _lastUsedUsername; }
|
||||
|
||||
void setApplicationStateError(QString errorMessage);
|
||||
void setApplicationState(ApplicationState state);
|
||||
ApplicationState getApplicationState() const;
|
||||
|
||||
Q_INVOKABLE void gotoSignup();
|
||||
Q_INVOKABLE void gotoLogin();
|
||||
|
||||
// Request builds
|
||||
void requestBuilds();
|
||||
|
||||
// Signup
|
||||
Q_INVOKABLE void signup(QString email, QString username, QString password, QString displayName);
|
||||
|
||||
// Login
|
||||
Q_INVOKABLE void login(QString username, QString password, QString displayName);
|
||||
|
||||
// Request Settings
|
||||
void requestSettings();
|
||||
|
||||
Q_INVOKABLE void restart();
|
||||
|
||||
// Launcher
|
||||
void downloadLauncher();
|
||||
void installLauncher();
|
||||
|
||||
// Client
|
||||
void downloadClient();
|
||||
void installClient();
|
||||
|
||||
// Content Cache
|
||||
void downloadContentCache();
|
||||
void installContentCache();
|
||||
|
||||
// Launching
|
||||
void launchClient();
|
||||
|
||||
Q_INVOKABLE float getDownloadProgress() const { return calculateDownloadProgress(); }
|
||||
|
||||
Q_INVOKABLE void openURLInBrowser(QString url);
|
||||
|
||||
signals:
|
||||
void updateSourceUrl(QUrl sourceUrl);
|
||||
void uiStateChanged();
|
||||
void applicationStateChanged();
|
||||
void downloadProgressChanged();
|
||||
void lastSignupErrorChanged();
|
||||
void lastSignupErrorMessageChanged();
|
||||
void lastLoginErrorMessageChanged();
|
||||
|
||||
private slots:
|
||||
void clientDownloadComplete();
|
||||
void contentCacheDownloadComplete();
|
||||
void launcherDownloadComplete();
|
||||
|
||||
private:
|
||||
bool shouldDownloadContentCache() const;
|
||||
void getCurrentClientVersion();
|
||||
|
||||
float calculateDownloadProgress() const;
|
||||
|
||||
bool shouldDownloadLauncher();
|
||||
|
||||
QNetworkAccessManager _networkAccessManager;
|
||||
Builds _latestBuilds;
|
||||
QDir _launcherDirectory;
|
||||
|
||||
LauncherConfig _config;
|
||||
|
||||
// Application State
|
||||
ApplicationState _applicationState { ApplicationState::Init };
|
||||
UIState _uiState { UIState::SplashScreen };
|
||||
LoginToken _loginResponse;
|
||||
SignupRequest::Error _lastSignupError{ SignupRequest::Error::None };
|
||||
QString _lastLoginErrorMessage{ "" };
|
||||
QString _lastSignupErrorMessage{ "" };
|
||||
QString _lastUsedUsername;
|
||||
QString _displayName;
|
||||
QString _applicationErrorMessage;
|
||||
QString _currentClientVersion;
|
||||
QString _buildTag { QString::null };
|
||||
QString _contentCacheURL;
|
||||
QString _loginTokenResponse;
|
||||
QFile _clientZipFile;
|
||||
QFile _launcherZipFile;
|
||||
QFile _contentZipFile;
|
||||
|
||||
QString _username;
|
||||
QString _password;
|
||||
|
||||
float _downloadProgress { 0 };
|
||||
float _contentDownloadProgress { 0 };
|
||||
float _contentInstallProgress { 0 };
|
||||
float _interfaceDownloadProgress { 0 };
|
||||
float _interfaceInstallProgress { 0 };
|
||||
};
|
73
launchers/qt/src/LauncherWindow.cpp
Normal file
|
@ -0,0 +1,73 @@
|
|||
#include "LauncherWindow.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
#include <QCursor>
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include <shellapi.h>
|
||||
#include <propsys.h>
|
||||
#include <propkey.h>
|
||||
#endif
|
||||
|
||||
LauncherWindow::LauncherWindow() {
|
||||
#ifdef Q_OS_WIN
|
||||
// On Windows, disable pinning of the launcher.
|
||||
IPropertyStore* pps;
|
||||
HWND id = (HWND)this->winId();
|
||||
if (id == NULL) {
|
||||
qDebug() << "Failed to disable pinning, window id is null";
|
||||
} else {
|
||||
HRESULT hr = SHGetPropertyStoreForWindow(id, IID_PPV_ARGS(&pps));
|
||||
if (SUCCEEDED(hr)) {
|
||||
PROPVARIANT var;
|
||||
var.vt = VT_BOOL;
|
||||
var.boolVal = VARIANT_TRUE;
|
||||
hr = pps->SetValue(PKEY_AppUserModel_PreventPinning, var);
|
||||
pps->Release();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void LauncherWindow::keyPressEvent(QKeyEvent* event) {
|
||||
QQuickView::keyPressEvent(event);
|
||||
if (!event->isAccepted()) {
|
||||
if (event->key() == Qt::Key_Escape) {
|
||||
exit(0);
|
||||
} else if (event->key() == Qt::Key_Up) {
|
||||
_launcherState->toggleDebugState();
|
||||
} else if (event->key() == Qt::Key_Left) {
|
||||
_launcherState->gotoPreviousDebugScreen();
|
||||
} else if (event->key() == Qt::Key_Right) {
|
||||
_launcherState->gotoNextDebugScreen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherWindow::mousePressEvent(QMouseEvent* event) {
|
||||
QQuickView::mousePressEvent(event);
|
||||
if (!event->isAccepted()) {
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
_drag = true;
|
||||
_previousMousePos = QCursor::pos();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LauncherWindow::mouseReleaseEvent(QMouseEvent* event) {
|
||||
QQuickView::mouseReleaseEvent(event);
|
||||
_drag = false;
|
||||
}
|
||||
|
||||
void LauncherWindow::mouseMoveEvent(QMouseEvent* event) {
|
||||
QQuickView::mouseMoveEvent(event);
|
||||
if (!event->isAccepted()) {
|
||||
if (_drag) {
|
||||
QPoint cursorPos = QCursor::pos();
|
||||
QPoint offset = _previousMousePos - cursorPos;
|
||||
_previousMousePos = cursorPos;
|
||||
setPosition(position() - offset);
|
||||
}
|
||||
}
|
||||
}
|
21
launchers/qt/src/LauncherWindow.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#include "LauncherState.h"
|
||||
#include <QQuickView>
|
||||
#include <QPoint>
|
||||
#include <memory>
|
||||
|
||||
class LauncherWindow : public QQuickView {
|
||||
public:
|
||||
LauncherWindow();
|
||||
~LauncherWindow() = default;
|
||||
void keyPressEvent(QKeyEvent* event) override;
|
||||
void mousePressEvent(QMouseEvent* event) override;
|
||||
void mouseReleaseEvent(QMouseEvent* event) override;
|
||||
void mouseMoveEvent(QMouseEvent* event) override;
|
||||
void setLauncherStatePtr(std::shared_ptr<LauncherState> launcherState) { _launcherState = launcherState; }
|
||||
|
||||
private:
|
||||
bool _drag { false };
|
||||
QPoint _previousMousePos;
|
||||
|
||||
std::shared_ptr<LauncherState> _launcherState { nullptr };
|
||||
};
|
80
launchers/qt/src/LoginRequest.cpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
#include "LoginRequest.h"
|
||||
|
||||
#include "Helper.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
void LoginRequest::send(QNetworkAccessManager& nam, QString username, QString password) {
|
||||
QNetworkRequest request(QUrl(getMetaverseAPIDomain() + "/oauth/token"));
|
||||
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, getHTTPUserAgent());
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("grant_type", "password");
|
||||
query.addQueryItem("username", QUrl::toPercentEncoding(username));
|
||||
query.addQueryItem("password", QUrl::toPercentEncoding(password));
|
||||
query.addQueryItem("scope", "owner");
|
||||
|
||||
auto reply = nam.post(request, query.query(QUrl::FullyEncoded).toLatin1());
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, &LoginRequest::receivedResponse);
|
||||
}
|
||||
|
||||
void LoginRequest::receivedResponse() {
|
||||
_state = State::Finished;
|
||||
|
||||
auto reply = static_cast<QNetworkReply*>(sender());
|
||||
|
||||
auto statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
|
||||
|
||||
if (statusCode < 100) {
|
||||
qDebug() << "Error logging in: " << reply->readAll();
|
||||
_error = Error::Unknown;
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (statusCode >= 500 && statusCode < 600) {
|
||||
qDebug() << "Error logging in: " << reply->readAll();
|
||||
_error = Error::ServerError;
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = reply->readAll();
|
||||
QJsonParseError parseError;
|
||||
auto doc = QJsonDocument::fromJson(data, &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qDebug() << "Error parsing response for login" << data;
|
||||
_error = Error::BadResponse;
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
auto root = doc.object();
|
||||
|
||||
if (!root.contains("access_token")
|
||||
|| !root.contains("token_type")
|
||||
|| !root.contains("expires_in")
|
||||
|| !root.contains("refresh_token")
|
||||
|| !root.contains("scope")
|
||||
|| !root.contains("created_at")) {
|
||||
|
||||
_error = Error::BadUsernameOrPassword;
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
_token.accessToken = root["access_token"].toString();
|
||||
_token.refreshToken = root["refresh_token"].toString();
|
||||
_token.tokenType = root["token_type"].toString();
|
||||
|
||||
qDebug() << "Got response for login";
|
||||
_rawLoginToken = data;
|
||||
|
||||
emit finished();
|
||||
}
|
50
launchers/qt/src/LoginRequest.h
Normal file
|
@ -0,0 +1,50 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
struct LoginToken {
|
||||
QString accessToken;
|
||||
QString tokenType;
|
||||
QString refreshToken;
|
||||
};
|
||||
|
||||
class LoginRequest : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class State {
|
||||
Unsent,
|
||||
Sending,
|
||||
Finished
|
||||
};
|
||||
|
||||
enum class Error {
|
||||
None = 0,
|
||||
Unknown,
|
||||
ServerError,
|
||||
BadResponse,
|
||||
BadUsernameOrPassword
|
||||
};
|
||||
Q_ENUM(Error)
|
||||
|
||||
void send(QNetworkAccessManager& nam, QString username, QString password);
|
||||
Error getError() const { return _error; }
|
||||
|
||||
// The token is only valid if the request has finished without error
|
||||
QString getRawToken() const { return _rawLoginToken; }
|
||||
LoginToken getToken() const { return _token; }
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
|
||||
private slots:
|
||||
void receivedResponse();
|
||||
|
||||
private:
|
||||
State _state { State::Unsent };
|
||||
Error _error { Error::None };
|
||||
|
||||
QString _rawLoginToken;
|
||||
LoginToken _token;
|
||||
};
|
||||
|
9
launchers/qt/src/NSTask+NSTaskExecveAdditions.h
Normal file
|
@ -0,0 +1,9 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface NSTask (NSTaskExecveAdditions)
|
||||
- (void) replaceThisProcess;
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
73
launchers/qt/src/NSTask+NSTaskExecveAdditions.m
Normal file
|
@ -0,0 +1,73 @@
|
|||
#import "NSTask+NSTaskExecveAdditions.h"
|
||||
|
||||
#import <libgen.h>
|
||||
|
||||
char **
|
||||
toCArray(NSArray<NSString *> *array)
|
||||
{
|
||||
// Add one to count to accommodate the NULL that terminates the array.
|
||||
char **cArray = (char **) calloc([array count] + 1, sizeof(char *));
|
||||
if (cArray == NULL) {
|
||||
NSException *exception = [NSException
|
||||
exceptionWithName:@"MemoryException"
|
||||
reason:@"malloc failed"
|
||||
userInfo:nil];
|
||||
@throw exception;
|
||||
}
|
||||
char *str;
|
||||
for (NSUInteger i = 0; i < [array count]; i++) {
|
||||
str = (char *) [array[i] UTF8String];
|
||||
if (str == NULL) {
|
||||
NSException *exception = [NSException
|
||||
exceptionWithName:@"NULLStringException"
|
||||
reason:@"UTF8String was NULL"
|
||||
userInfo:nil];
|
||||
@throw exception;
|
||||
}
|
||||
if (asprintf(&cArray[i], "%s", str) == -1) {
|
||||
for (NSUInteger j = 0; j < i; j++) {
|
||||
free(cArray[j]);
|
||||
}
|
||||
free(cArray);
|
||||
NSException *exception = [NSException
|
||||
exceptionWithName:@"MemoryException"
|
||||
reason:@"malloc failed"
|
||||
userInfo:nil];
|
||||
@throw exception;
|
||||
}
|
||||
}
|
||||
return cArray;
|
||||
}
|
||||
|
||||
@implementation NSTask (NSTaskExecveAdditions)
|
||||
|
||||
- (void) replaceThisProcess {
|
||||
char **args = toCArray([@[[self launchPath]] arrayByAddingObjectsFromArray:[self arguments]]);
|
||||
|
||||
NSMutableArray *env = [[NSMutableArray alloc] init];
|
||||
NSDictionary* environvment = [[NSProcessInfo processInfo] environment];
|
||||
for (NSString* key in environvment) {
|
||||
NSString* environmentVariable = [[key stringByAppendingString:@"="] stringByAppendingString:environvment[key]];
|
||||
[env addObject:environmentVariable];
|
||||
}
|
||||
|
||||
char** envp = toCArray(env);
|
||||
// `execve` replaces the current process with `path`.
|
||||
// It will only return if it fails to replace the current process.
|
||||
chdir(dirname(args[0]));
|
||||
execve(args[0], (char * const *)args, envp);
|
||||
|
||||
// If we're here `execve` failed. :(
|
||||
for (NSUInteger i = 0; i < [[self arguments] count]; i++) {
|
||||
free((void *) args[i]);
|
||||
}
|
||||
free((void *) args);
|
||||
|
||||
NSException *exception = [NSException
|
||||
exceptionWithName:@"ExecveException"
|
||||
reason:[NSString stringWithFormat:@"couldn't execve: %s", strerror(errno)]
|
||||
userInfo:nil];
|
||||
@throw exception;
|
||||
}
|
||||
|
||||
@end
|
77
launchers/qt/src/PathUtils.cpp
Normal file
|
@ -0,0 +1,77 @@
|
|||
#include "PathUtils.h"
|
||||
|
||||
#include "Helper.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
#include <QStandardPaths>
|
||||
|
||||
QUrl PathUtils::resourcePath(const QString& source) {
|
||||
QString filePath = RESOURCE_PREFIX_URL + source;
|
||||
#ifdef HIFI_USE_LOCAL_FILE
|
||||
return QUrl::fromLocalFile(filePath);
|
||||
#else
|
||||
return QUrl(filePath);
|
||||
#endif
|
||||
}
|
||||
|
||||
QString PathUtils::fontPath(const QString& fontName) {
|
||||
#ifdef HIFI_USE_LOCAL_FILE
|
||||
return resourcePath("/fonts/" + fontName).toString(QUrl::PreferLocalFile);
|
||||
#else
|
||||
return ":/fonts/" + fontName;
|
||||
#endif
|
||||
}
|
||||
|
||||
QDir PathUtils::getLauncherDirectory() {
|
||||
return QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation);
|
||||
}
|
||||
|
||||
QDir PathUtils::getApplicationsDirectory() {
|
||||
return QDir(QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation)).absoluteFilePath("Launcher");
|
||||
}
|
||||
|
||||
// The client directory is where interface is installed to.
|
||||
QDir PathUtils::getClientDirectory() {
|
||||
return getLauncherDirectory().filePath("client");
|
||||
}
|
||||
|
||||
QDir PathUtils::getLogsDirectory() {
|
||||
return getLauncherDirectory().filePath("logs");
|
||||
}
|
||||
|
||||
// The download directory is used to store files downloaded during installation.
|
||||
QDir PathUtils::getDownloadDirectory() {
|
||||
return getLauncherDirectory().filePath("downloads");
|
||||
}
|
||||
|
||||
// The content cache path is the directory interface uses for caching data.
|
||||
// It is pre-populated on startup with domain content.
|
||||
QString PathUtils::getContentCachePath() {
|
||||
return getLauncherDirectory().filePath("contentcache");
|
||||
}
|
||||
|
||||
// The path to the interface binary.
|
||||
QString PathUtils::getClientExecutablePath() {
|
||||
QDir clientDirectory = getClientDirectory();
|
||||
#if defined(Q_OS_WIN)
|
||||
return clientDirectory.absoluteFilePath("interface.exe");
|
||||
#elif defined(Q_OS_MACOS)
|
||||
return clientDirectory.absoluteFilePath("interface.app/Contents/MacOS/interface");
|
||||
#endif
|
||||
}
|
||||
|
||||
// The path to the config.json file that the launcher uses to store information like
|
||||
// the last user that logged in.
|
||||
QString PathUtils::getConfigFilePath() {
|
||||
return getClientDirectory().absoluteFilePath("config.json");
|
||||
}
|
||||
|
||||
// The path to the launcher binary.
|
||||
QString PathUtils::getLauncherFilePath() {
|
||||
#if defined(Q_OS_WIN)
|
||||
return getLauncherDirectory().absoluteFilePath("HQ Launcher.exe");
|
||||
#elif defined(Q_OS_MACOS)
|
||||
return getBundlePath() + "/Contents/MacOS/HQ Launcher";
|
||||
#endif
|
||||
}
|
28
launchers/qt/src/PathUtils.h
Normal file
|
@ -0,0 +1,28 @@
|
|||
#pragma once
|
||||
|
||||
#include <QDir>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QFile>
|
||||
#include <QUrl>
|
||||
|
||||
class PathUtils : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
PathUtils() = default;
|
||||
~PathUtils() = default;
|
||||
Q_INVOKABLE static QUrl resourcePath(const QString& source);
|
||||
|
||||
static QString fontPath(const QString& fontName);
|
||||
|
||||
static QDir getLauncherDirectory();
|
||||
static QDir getApplicationsDirectory();
|
||||
static QDir getDownloadDirectory();
|
||||
static QDir getClientDirectory();
|
||||
static QDir getLogsDirectory();
|
||||
|
||||
static QString getContentCachePath();
|
||||
static QString getClientExecutablePath();
|
||||
static QString getConfigFilePath();
|
||||
static QString getLauncherFilePath();
|
||||
};
|
80
launchers/qt/src/SignupRequest.cpp
Normal file
|
@ -0,0 +1,80 @@
|
|||
#include "SignupRequest.h"
|
||||
|
||||
#include "Helper.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
void SignupRequest::send(QNetworkAccessManager& nam, QString email, QString username, QString password) {
|
||||
if (_state != State::Unsent) {
|
||||
qDebug() << "Error: Trying to send signuprequest, but not unsent";
|
||||
return;
|
||||
}
|
||||
|
||||
_state = State::Sending;
|
||||
|
||||
QUrl signupURL { getMetaverseAPIDomain() };
|
||||
signupURL.setPath("/api/v1/user/channel_user");
|
||||
QNetworkRequest request(signupURL);
|
||||
|
||||
request.setHeader(QNetworkRequest::UserAgentHeader, getHTTPUserAgent());
|
||||
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
|
||||
|
||||
QUrlQuery query;
|
||||
query.addQueryItem("email", QUrl::toPercentEncoding(email));
|
||||
query.addQueryItem("username", QUrl::toPercentEncoding(username));
|
||||
query.addQueryItem("password", QUrl::toPercentEncoding(password));
|
||||
|
||||
auto reply = nam.put(request, query.query(QUrl::FullyEncoded).toLatin1());
|
||||
QObject::connect(reply, &QNetworkReply::finished, this, &SignupRequest::receivedResponse);
|
||||
}
|
||||
|
||||
void SignupRequest::receivedResponse() {
|
||||
_state = State::Finished;
|
||||
|
||||
auto reply = static_cast<QNetworkReply*>(sender());
|
||||
|
||||
if (reply->error() && reply->size() == 0) {
|
||||
qDebug() << "Error signing up: " << reply->error() << reply->readAll();
|
||||
_error = Error::Unknown;
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = reply->readAll();
|
||||
qDebug() << "Signup response: " << data;
|
||||
QJsonParseError parseError;
|
||||
auto doc = QJsonDocument::fromJson(data, &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qDebug() << "Error parsing response for signup " << data;
|
||||
_error = Error::Unknown;
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
auto root = doc.object();
|
||||
|
||||
auto status = root["status"];
|
||||
if (status.isString() && status.toString() != "success") {
|
||||
auto error = root["data"].toString();
|
||||
|
||||
_error = Error::Unknown;
|
||||
|
||||
if (error == "no_such_email") {
|
||||
_error = Error::NoSuchEmail;
|
||||
} else if (error == "user_profile_already_completed") {
|
||||
_error = Error::UserProfileAlreadyCompleted;
|
||||
} else if (error == "bad_username") {
|
||||
_error = Error::BadUsername;
|
||||
} else if (error == "existing_username") {
|
||||
_error = Error::ExistingUsername;
|
||||
} else if (error == "bad_password") {
|
||||
_error = Error::BadPassword;
|
||||
}
|
||||
}
|
||||
|
||||
emit finished();
|
||||
}
|
38
launchers/qt/src/SignupRequest.h
Normal file
|
@ -0,0 +1,38 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
class SignupRequest : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class State {
|
||||
Unsent,
|
||||
Sending,
|
||||
Finished
|
||||
};
|
||||
|
||||
enum class Error {
|
||||
None = 0,
|
||||
Unknown,
|
||||
NoSuchEmail,
|
||||
UserProfileAlreadyCompleted,
|
||||
BadUsername,
|
||||
ExistingUsername,
|
||||
BadPassword,
|
||||
};
|
||||
Q_ENUM(Error)
|
||||
|
||||
void send(QNetworkAccessManager& nam, QString email, QString username, QString password);
|
||||
Error getError() const { return _error; }
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
|
||||
private slots:
|
||||
void receivedResponse();
|
||||
|
||||
private:
|
||||
State _state { State::Unsent };
|
||||
Error _error { Error::None };
|
||||
};
|
108
launchers/qt/src/Unzipper.cpp
Normal file
|
@ -0,0 +1,108 @@
|
|||
#include "Unzipper.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
#include <miniz/miniz.h>
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
Unzipper::Unzipper(const QString& zipFilePath, const QDir& outputDirectory) :
|
||||
_zipFilePath(zipFilePath), _outputDirectory(outputDirectory) {
|
||||
}
|
||||
|
||||
void Unzipper::run() {
|
||||
qDebug() << "Reading zip file" << _zipFilePath << ", extracting to" << _outputDirectory.absolutePath();
|
||||
|
||||
_outputDirectory.mkpath(_outputDirectory.absolutePath());
|
||||
|
||||
mz_zip_archive zip_archive;
|
||||
memset(&zip_archive, 0, sizeof(zip_archive));
|
||||
|
||||
auto status = mz_zip_reader_init_file(&zip_archive, _zipFilePath.toUtf8().data(), 0);
|
||||
|
||||
if (!status) {
|
||||
auto zip_error = mz_zip_get_last_error(&zip_archive);
|
||||
auto zip_error_msg = mz_zip_get_error_string(zip_error);
|
||||
emit finished(true, "Failed to initialize miniz: " + QString::number(zip_error) + " " + zip_error_msg);
|
||||
return;
|
||||
}
|
||||
|
||||
int fileCount = (int)mz_zip_reader_get_num_files(&zip_archive);
|
||||
|
||||
qDebug() << "Zip archive has a file count of " << fileCount;
|
||||
|
||||
if (fileCount == 0) {
|
||||
mz_zip_reader_end(&zip_archive);
|
||||
emit finished(false, "");
|
||||
return;
|
||||
}
|
||||
|
||||
mz_zip_archive_file_stat file_stat;
|
||||
if (!mz_zip_reader_file_stat(&zip_archive, 0, &file_stat)) {
|
||||
mz_zip_reader_end(&zip_archive);
|
||||
emit finished(true, "Zip archive cannot be stat'd");
|
||||
return;
|
||||
}
|
||||
|
||||
uint64_t totalSize = 0;
|
||||
uint64_t totalCompressedSize = 0;
|
||||
//bool _shouldFail = false;
|
||||
for (int i = 0; i < fileCount; i++) {
|
||||
if (!mz_zip_reader_file_stat(&zip_archive, i, &file_stat)) continue;
|
||||
|
||||
QString filename = file_stat.m_filename;
|
||||
QString fullFilename = _outputDirectory.filePath(filename);
|
||||
if (mz_zip_reader_is_file_a_directory(&zip_archive, i)) {
|
||||
if (!_outputDirectory.mkpath(fullFilename)) {
|
||||
mz_zip_reader_end(&zip_archive);
|
||||
emit finished(true, "Unzipping error while creating folder: " + fullFilename);
|
||||
return;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
constexpr uint16_t FILE_PERMISSIONS_MASK { 0777 };
|
||||
|
||||
uint16_t mod_attr = (file_stat.m_external_attr >> 16) & FILE_PERMISSIONS_MASK;
|
||||
uint16_t filetype_attr = (file_stat.m_external_attr >> 16) & S_IFMT;
|
||||
#ifdef Q_OS_MACOS
|
||||
bool is_symlink = filetype_attr == S_IFLNK;
|
||||
#else
|
||||
bool is_symlink = false;
|
||||
#endif
|
||||
|
||||
if (is_symlink) {
|
||||
#ifdef Q_OS_MACOS
|
||||
size_t size;
|
||||
auto data = mz_zip_reader_extract_to_heap(&zip_archive, i, &size, 0);
|
||||
auto target = QString::fromUtf8((char*)data, size);
|
||||
|
||||
qDebug() << "Extracted symlink: " << size << target;
|
||||
|
||||
symlink(target.toUtf8().data(), fullFilename.toUtf8().data());
|
||||
#else
|
||||
emit finished(true, "Error unzipping symlink");
|
||||
return;
|
||||
#endif
|
||||
} else if (mz_zip_reader_extract_to_file(&zip_archive, i, fullFilename.toUtf8().data(), 0)) {
|
||||
totalCompressedSize += (uint64_t)file_stat.m_comp_size;
|
||||
totalSize += (uint64_t)file_stat.m_uncomp_size;
|
||||
emit progress((float)totalCompressedSize / (float)zip_archive.m_archive_size);
|
||||
} else {
|
||||
emit finished(true, "Unzipping error unzipping file: " + fullFilename);
|
||||
return;
|
||||
}
|
||||
#ifdef Q_OS_MACOS
|
||||
chmod(fullFilename.toUtf8().data(), mod_attr);
|
||||
#endif
|
||||
}
|
||||
|
||||
qDebug() << "Done unzipping archive, total size:" << totalSize;
|
||||
|
||||
mz_zip_reader_end(&zip_archive);
|
||||
|
||||
emit finished(false, "");
|
||||
}
|
21
launchers/qt/src/Unzipper.h
Normal file
|
@ -0,0 +1,21 @@
|
|||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QDir>
|
||||
#include <QObject>
|
||||
#include <QRunnable>
|
||||
|
||||
class Unzipper : public QObject, public QRunnable {
|
||||
Q_OBJECT
|
||||
public:
|
||||
Unzipper(const QString& zipFilePath, const QDir& outputDirectory);
|
||||
void run() override;
|
||||
|
||||
signals:
|
||||
void progress(float progress);
|
||||
void finished(bool error, QString errorMessage);
|
||||
|
||||
private:
|
||||
const QString _zipFilePath;
|
||||
const QDir _outputDirectory;
|
||||
};
|
67
launchers/qt/src/UserSettingsRequest.cpp
Normal file
|
@ -0,0 +1,67 @@
|
|||
#include "UserSettingsRequest.h"
|
||||
|
||||
#include "Helper.h"
|
||||
|
||||
#include <QUrlQuery>
|
||||
#include <QNetworkReply>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
|
||||
const QByteArray ACCESS_TOKEN_AUTHORIZATION_HEADER = "Authorization";
|
||||
|
||||
void UserSettingsRequest::send(QNetworkAccessManager& nam, const LoginToken& token) {
|
||||
_state = State::Sending;
|
||||
|
||||
QUrl lockerURL{ getMetaverseAPIDomain() };
|
||||
lockerURL.setPath("/api/v1/user/locker");
|
||||
|
||||
QNetworkRequest lockerRequest(lockerURL);
|
||||
lockerRequest.setAttribute(QNetworkRequest::FollowRedirectsAttribute, true);
|
||||
lockerRequest.setHeader(QNetworkRequest::UserAgentHeader, getHTTPUserAgent());
|
||||
lockerRequest.setRawHeader(ACCESS_TOKEN_AUTHORIZATION_HEADER, QString("Bearer %1").arg(token.accessToken).toUtf8());
|
||||
|
||||
QNetworkReply* lockerReply = nam.get(lockerRequest);
|
||||
connect(lockerReply, &QNetworkReply::finished, this, &UserSettingsRequest::receivedResponse);
|
||||
}
|
||||
|
||||
void UserSettingsRequest::receivedResponse() {
|
||||
_state = State::Finished;
|
||||
|
||||
auto reply = static_cast<QNetworkReply*>(sender());
|
||||
|
||||
qDebug() << "Got reply: " << reply->error();
|
||||
if (reply->error()) {
|
||||
_error = Error::Unknown;
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
auto data = reply->readAll();
|
||||
qDebug() << "Settings: " << data;
|
||||
QJsonParseError parseError;
|
||||
auto doc = QJsonDocument::fromJson(data, &parseError);
|
||||
|
||||
if (parseError.error != QJsonParseError::NoError) {
|
||||
qDebug() << "Error parsing settings";
|
||||
_error = Error::Unknown;
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
auto root = doc.object();
|
||||
if (root["status"] != "success") {
|
||||
qDebug() << "Status is not \"success\"";
|
||||
_error = Error::Unknown;
|
||||
emit finished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (root["data"].toObject().contains("home_location")) {
|
||||
QJsonValue homeLocation = root["data"].toObject()["home_location"];
|
||||
if (homeLocation.isString()) {
|
||||
_userSettings.homeLocation = homeLocation.toString();
|
||||
}
|
||||
}
|
||||
|
||||
emit finished();
|
||||
}
|
43
launchers/qt/src/UserSettingsRequest.h
Normal file
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QNetworkAccessManager>
|
||||
|
||||
#include "LoginRequest.h"
|
||||
|
||||
struct UserSettings {
|
||||
QString homeLocation{ QString::null };
|
||||
};
|
||||
|
||||
class UserSettingsRequest : public QObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
enum class State {
|
||||
Unsent,
|
||||
Sending,
|
||||
Finished
|
||||
};
|
||||
|
||||
enum class Error {
|
||||
None = 0,
|
||||
Unknown,
|
||||
};
|
||||
Q_ENUM(Error)
|
||||
|
||||
void send(QNetworkAccessManager& nam, const LoginToken& token);
|
||||
Error getError() const { return _error; }
|
||||
|
||||
UserSettings getUserSettings() const { return _userSettings; }
|
||||
|
||||
signals:
|
||||
void finished();
|
||||
|
||||
private slots:
|
||||
void receivedResponse();
|
||||
|
||||
private:
|
||||
State _state { State::Unsent };
|
||||
Error _error { Error::None };
|
||||
|
||||
UserSettings _userSettings;
|
||||
};
|
88
launchers/qt/src/main.cpp
Normal file
|
@ -0,0 +1,88 @@
|
|||
#include <QtPlugin>
|
||||
|
||||
#include <qsharedmemory.h>
|
||||
|
||||
#include "LauncherWindow.h"
|
||||
#include "Launcher.h"
|
||||
#include "CommandlineOptions.h"
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include <QProcess>
|
||||
#include "Helper.h"
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
#include "LauncherInstaller_windows.h"
|
||||
Q_IMPORT_PLUGIN(QWindowsIntegrationPlugin);
|
||||
#elif defined(Q_OS_MACOS)
|
||||
Q_IMPORT_PLUGIN(QCocoaIntegrationPlugin);
|
||||
#endif
|
||||
|
||||
Q_IMPORT_PLUGIN(QtQuick2Plugin);
|
||||
Q_IMPORT_PLUGIN(QtQuickControls2Plugin);
|
||||
Q_IMPORT_PLUGIN(QtQuickTemplates2Plugin);
|
||||
|
||||
bool hasSuffix(const std::string& path, const std::string& suffix) {
|
||||
if (path.substr(path.find_last_of(".") + 1) == suffix) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
|
||||
QCoreApplication::setOrganizationName("High Fidelity");
|
||||
QCoreApplication::setApplicationName("Launcher");
|
||||
|
||||
Q_INIT_RESOURCE(resources);
|
||||
cleanLogFile();
|
||||
qInstallMessageHandler(messageHandler);
|
||||
CommandlineOptions* options = CommandlineOptions::getInstance();
|
||||
options->parse(argc, argv);
|
||||
bool didUpdate = false;
|
||||
|
||||
#ifdef Q_OS_MAC
|
||||
if (isLauncherAlreadyRunning()) {
|
||||
return 0;
|
||||
}
|
||||
closeInterfaceIfRunning();
|
||||
if (argc == 3) {
|
||||
if (hasSuffix(argv[1], "app") && hasSuffix(argv[2], "app")) {
|
||||
bool success = swapLaunchers(argv[1], argv[2]);
|
||||
qDebug() << "Successfully installed Launcher: " << success;
|
||||
options->append("--noUpdate");
|
||||
didUpdate = true;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (options->contains("--version")) {
|
||||
std::cout << LAUNCHER_BUILD_VERSION << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
LauncherInstaller launcherInstaller;
|
||||
if (options->contains("--uninstall") || options->contains("--resumeUninstall")) {
|
||||
launcherInstaller.uninstall();
|
||||
return 0;
|
||||
} else if (options->contains("--restart") || launcherInstaller.runningOutsideOfInstallDir()) {
|
||||
launcherInstaller.install();
|
||||
}
|
||||
|
||||
int interfacePID = -1;
|
||||
if (isProcessRunning("interface.exe", interfacePID)) {
|
||||
shutdownProcess(interfacePID, 0);
|
||||
}
|
||||
#endif
|
||||
|
||||
QProcessEnvironment processEnvironment = QProcessEnvironment::systemEnvironment();
|
||||
if (processEnvironment.contains("HQ_LAUNCHER_BUILD_VERSION")) {
|
||||
if (didUpdate || options->contains("--restart")) {
|
||||
options->append("--noUpdate");
|
||||
}
|
||||
}
|
||||
|
||||
Launcher launcher(argc, argv);
|
||||
return launcher.exec();
|
||||
}
|
|
@ -144,7 +144,7 @@ def signBuild(executablePath):
|
|||
|
||||
|
||||
def zipDarwinLauncher():
|
||||
launcherSourcePath = os.path.join(SOURCE_PATH, 'launchers', sys.platform)
|
||||
launcherSourcePath = os.path.join(SOURCE_PATH, 'launchers', 'qt')
|
||||
launcherBuildPath = os.path.join(BUILD_PATH, 'launcher')
|
||||
|
||||
archiveName = computeArchiveName('HQ Launcher')
|
||||
|
@ -165,7 +165,7 @@ def zipDarwinLauncher():
|
|||
|
||||
|
||||
def buildLightLauncher():
|
||||
launcherSourcePath = os.path.join(SOURCE_PATH, 'launchers', sys.platform)
|
||||
launcherSourcePath = os.path.join(SOURCE_PATH, 'launchers', 'qt')
|
||||
launcherBuildPath = os.path.join(BUILD_PATH, 'launcher')
|
||||
if not os.path.exists(launcherBuildPath):
|
||||
os.makedirs(launcherBuildPath)
|
||||
|
|
104
tools/qt-builder/qt-lite-build-steps.md
Normal file
|
@ -0,0 +1,104 @@
|
|||
## Requirements
|
||||
### Windows
|
||||
1. Visual Studio 2017
|
||||
2. Perl - http://strawberryperl.com/
|
||||
|
||||
|
||||
# Windows
|
||||
Command Prompt
|
||||
```
|
||||
git clone --single-branch --branch 5.9 https://code.qt.io/qt/qt5.git
|
||||
cd qt5
|
||||
perl ./init-repository -force --module-subset=qtbase,qtdeclarative,qtgraphicaleffects,qtquickcontrols,qtquickcontrols2,qtmultimedia
|
||||
```
|
||||
|
||||
Modify the following lines in `qtbase/mkspecs/common/msvc-desktop.conf`, changing `-MD` to `-MT`, and `-MDd` to `-MTd`
|
||||
```
|
||||
QMAKE_CFLAGS_RELEASE = $$QMAKE_CFLAGS_OPTIMIZE -MD
|
||||
QMAKE_CFLAGS_RELEASE_WITH_DEBUGINFO += $$QMAKE_CFLAGS_OPTIMIZE -Zi -MD
|
||||
QMAKE_CFLAGS_DEBUG = -Zi -MDd
|
||||
```
|
||||
|
||||
|
||||
Command Prompt
|
||||
```
|
||||
cp path-to-your-hifi-directory/tools/qt-builder/qt5vars.bat ./
|
||||
mkdir qt-build
|
||||
cd qt-build
|
||||
..\qt5vars.bat
|
||||
..\configure.bat
|
||||
```
|
||||
|
||||
Download ssl-static.zip and unzip to ssl-static folder next to qt5 folder
|
||||
`https://hifi-content.s3.amazonaws.com/dante/ssl-static-windows.zip`
|
||||
remove config.opt in the build folder
|
||||
copy over the config file from qt-builder
|
||||
```
|
||||
cp path-to-your-hifi-directory/tools/qt-builder/qt-lite-windows-config ./config.opt
|
||||
```
|
||||
delete config.cache
|
||||
|
||||
modify the config.opt file vaules `-L` and `-I` to point to the loaction of your openssl static lib
|
||||
|
||||
```
|
||||
OPENSSL_LIBS=-lssleay32 -llibeay32
|
||||
-I\path\to\openssl\include
|
||||
-L\path\to\openssl\lib
|
||||
```
|
||||
|
||||
Command Prompt
|
||||
```
|
||||
rem config.cache
|
||||
config.status.bat
|
||||
jom
|
||||
jom install
|
||||
```
|
||||
|
||||
# OSX
|
||||
Must use Apple LLVM version 8.1.0 (clang-802.0.42)
|
||||
Command Prompt
|
||||
```
|
||||
git clone --single-branch --branch 5.9 https://code.qt.io/qt/qt5.git
|
||||
cd qt5
|
||||
./init-repository -force --module-subset=qtbase,qtdeclarative,qtgraphicaleffects,qtquickcontrols,qtquickcontrols2,qtmultimedia,,
|
||||
```
|
||||
|
||||
Command Prompt
|
||||
```
|
||||
mkdir qt-build
|
||||
cd qt-build
|
||||
../configure
|
||||
```
|
||||
|
||||
Download ssl-static.zip and unzip to ssl-static folder next to qt5 folder
|
||||
`https://hifi-content.s3.amazonaws.com/dante/openssl-static-osx.zip`
|
||||
copy over the config file from qt-builder
|
||||
```
|
||||
cp path-to-your-hifi-directory/tools/qt-builder/qt-lite-osx-config ./config.opt
|
||||
```
|
||||
delete config.cache
|
||||
|
||||
modify the config.opt file vaules `-L` and `-I`to point to the loaction of your openssl static lib
|
||||
|
||||
```
|
||||
OPENSSL_LIBS=-lssl -lcrypto
|
||||
-L/path/to/openssl/lib
|
||||
-I/path/to/openssl/include
|
||||
```
|
||||
|
||||
|
||||
Comand Prompt
|
||||
```
|
||||
rm config.cache
|
||||
config.status
|
||||
make
|
||||
make install
|
||||
```
|
||||
|
||||
|
||||
#Building a static version of openssl on windows
|
||||
https://wiki.openssl.org/index.php/Compilation_and_Installation#OpenSSL_1.0.2
|
||||
follow the instructions in that link.
|
||||
|
||||
Keeping in mind that you need to use the non-dll commands (ex: 'nmake -f ms\ntdll.mak clean for the DLL target and nmake -f ms\nt.mak clean for static libraries.'
|
||||
so you'd want to use 'ms\nt.mak'
|
184
tools/qt-builder/qt-lite-osx-config.opt
Normal file
|
@ -0,0 +1,184 @@
|
|||
-static
|
||||
-optimize-size
|
||||
-qt-libpng
|
||||
-no-libjpeg
|
||||
-qt-sqlite
|
||||
-qt-zlib
|
||||
-qt-freetype
|
||||
-qt-pcre
|
||||
-strip
|
||||
-opensource
|
||||
-release
|
||||
-nomake
|
||||
tests
|
||||
-nomake
|
||||
examples
|
||||
-nomake
|
||||
tests
|
||||
-no-compile-examples
|
||||
-no-pch
|
||||
-confirm-license
|
||||
-skip
|
||||
qtmultimedia
|
||||
-prefix
|
||||
./qt-install
|
||||
-openssl-linked
|
||||
OPENSSL_LIBS=-lssl -lcrypto
|
||||
-L/path/to/openssl/lib
|
||||
-I/path/to/openssl/include
|
||||
-no-feature-widgets
|
||||
-no-feature-dbus
|
||||
-no-feature-xml
|
||||
-no-feature-sql
|
||||
-no-feature-concurrent
|
||||
-no-feature-quicktemplates2-hover
|
||||
-no-feature-quicktemplates2-multitouch
|
||||
-no-feature-quickcontrols2-material
|
||||
-no-feature-quickcontrols2-universal
|
||||
-no-feature-qml-network
|
||||
-no-feature-qml-profiler
|
||||
-no-feature-quick-listview
|
||||
-no-feature-quick-particles
|
||||
-no-feature-abstractbutton
|
||||
-no-feature-abstractslider
|
||||
-no-feature-buttongroup
|
||||
-no-feature-calendarwidget
|
||||
-no-feature-checkbox
|
||||
-no-feature-combobox
|
||||
-no-feature-commandlinkbutton
|
||||
-no-feature-contextmenu
|
||||
-no-feature-datetimeedit
|
||||
-no-feature-dial
|
||||
-no-feature-dockwidget
|
||||
-no-feature-fontcombobox
|
||||
-no-feature-formlayout
|
||||
-no-feature-graphicseffect
|
||||
-no-feature-graphicsview
|
||||
-no-feature-groupbox
|
||||
-no-feature-keysequenceedit
|
||||
-no-feature-label
|
||||
-no-feature-lcdnumber
|
||||
-no-feature-lineedit
|
||||
-no-feature-listwidget
|
||||
-no-feature-mainwindow
|
||||
-no-feature-mdiarea
|
||||
-no-feature-menu
|
||||
-no-feature-menubar
|
||||
-no-feature-printpreviewwidget
|
||||
-no-feature-progressbar
|
||||
-no-feature-pushbutton
|
||||
-no-feature-radiobutton
|
||||
-no-feature-resizehandler
|
||||
-no-feature-rubberband
|
||||
-no-feature-scrollarea
|
||||
-no-feature-scrollbar
|
||||
-no-feature-scroller
|
||||
-no-feature-sizegrip
|
||||
-no-feature-slider
|
||||
-no-feature-spinbox
|
||||
-no-feature-splashscreen
|
||||
-no-feature-splitter
|
||||
-no-feature-stackedwidget
|
||||
-no-feature-statusbar
|
||||
-no-feature-statustip
|
||||
-no-feature-syntaxhighlighter
|
||||
-no-feature-tabbar
|
||||
-no-feature-tablewidget
|
||||
-no-feature-tabwidget
|
||||
-no-feature-textbrowser
|
||||
-no-feature-textedit
|
||||
-no-feature-toolbar
|
||||
-no-feature-toolbox
|
||||
-no-feature-toolbutton
|
||||
-no-feature-tooltip
|
||||
-no-feature-treewidget
|
||||
-no-feature-validator
|
||||
-no-feature-widgettextcontrol
|
||||
-no-feature-quick-designer
|
||||
-no-feature-quick-flipable
|
||||
-no-feature-quick-pathview
|
||||
-no-feature-qml-profiler
|
||||
-no-feature-gif
|
||||
-no-feature-ico
|
||||
-no-feature-harfbuzz
|
||||
-no-feature-qml-debug
|
||||
-no-feature-quick-listview
|
||||
-no-feature-quick-sprite
|
||||
-no-feature-quick-path
|
||||
-no-feature-quick-canvas
|
||||
-no-feature-quick-animatedimage
|
||||
-no-feature-qml-interpreter
|
||||
-no-feature-action
|
||||
-no-feature-cssparser
|
||||
-no-feature-sharedmemory
|
||||
-no-feature-tabletevent
|
||||
-no-feature-texthtmlparser
|
||||
-no-feature-textodfwriter
|
||||
-no-feature-sessionmanager
|
||||
-no-feature-systemsemaphore
|
||||
-no-feature-im
|
||||
-no-feature-effects
|
||||
-no-feature-appstore-compliant
|
||||
-no-feature-big_codecs
|
||||
-no-feature-codecs
|
||||
-no-feature-colordialog
|
||||
-no-feature-colornames
|
||||
-no-feature-columnview
|
||||
-no-feature-commandlineparser
|
||||
-no-feature-cups
|
||||
-no-feature-d3d12
|
||||
-no-feature-datawidgetmapper
|
||||
-no-feature-datetimeparser
|
||||
-no-feature-desktopservices
|
||||
-no-feature-dialog
|
||||
-no-feature-dialogbuttonbox
|
||||
-no-feature-dirmodel
|
||||
-no-feature-dom
|
||||
-no-feature-errormessage
|
||||
-no-feature-filedialog
|
||||
-no-feature-filesystemiterator
|
||||
-no-feature-filesystemwatcher
|
||||
-no-feature-fontdialog
|
||||
-no-feature-fscompleter
|
||||
-no-feature-gestures
|
||||
-no-feature-iconv
|
||||
-no-feature-wizard
|
||||
-no-feature-xmlstreamwriter
|
||||
-no-feature-whatsthis
|
||||
-no-feature-undoview
|
||||
-no-feature-undostack
|
||||
-no-feature-undogroup
|
||||
-no-feature-undocommand
|
||||
-no-feature-treeview
|
||||
-no-feature-translation
|
||||
-no-feature-topleveldomain
|
||||
-no-feature-tableview
|
||||
-no-feature-style-stylesheet
|
||||
-no-feature-stringlistmodel
|
||||
-no-feature-sortfilterproxymodel
|
||||
-no-feature-wheelevent
|
||||
-no-feature-statemachine
|
||||
-no-feature-standarditemmodel
|
||||
-no-feature-proxymodel
|
||||
-no-feature-printer
|
||||
-no-feature-printpreviewdialog
|
||||
-no-feature-printdialog
|
||||
-no-feature-picture
|
||||
-no-feature-pdf
|
||||
-no-feature-movie
|
||||
-no-feature-messagebox
|
||||
-no-feature-listview
|
||||
-no-feature-itemmodel
|
||||
-no-feature-inputdialog
|
||||
-no-feature-filesystemmodel
|
||||
-no-feature-identityproxymodel
|
||||
-no-feature-mimetype
|
||||
-no-feature-paint_debug
|
||||
-no-feature-progressdialog
|
||||
-no-feature-quick-positioners
|
||||
-no-feature-sha3-fast
|
||||
-no-feature-shortcut
|
||||
-no-feature-completer
|
||||
-no-feature-image_heuristic_mask
|
||||
-no-feature-image_text
|
||||
-no-feature-imageformat_bmp
|
188
tools/qt-builder/qt-lite-windows-config.opt
Normal file
|
@ -0,0 +1,188 @@
|
|||
-static
|
||||
-optimize-size
|
||||
-qt-libpng
|
||||
-no-libjpeg
|
||||
-qt-sqlite
|
||||
-qt-zlib
|
||||
-qt-freetype
|
||||
-qt-pcre
|
||||
-strip
|
||||
-opensource
|
||||
-release
|
||||
-nomake
|
||||
tests
|
||||
-nomake
|
||||
examples
|
||||
-nomake
|
||||
tests
|
||||
-no-compile-examples
|
||||
-no-pch
|
||||
-confirm-license
|
||||
-opengl
|
||||
desktop
|
||||
-skip
|
||||
qtmultimedia
|
||||
-skip
|
||||
qttranslations
|
||||
-prefix
|
||||
./qt-install
|
||||
|
||||
-openssl-linked
|
||||
OPENSSL_LIBS=-lssleay32 -llibeay32
|
||||
-I\path\to\openssl\include
|
||||
-L\path\to\openssl\lib
|
||||
-no-feature-widgets
|
||||
-no-feature-dbus
|
||||
-no-feature-xml
|
||||
-no-feature-sql
|
||||
-no-feature-concurrent
|
||||
-no-feature-quicktemplates2-hover
|
||||
-no-feature-quicktemplates2-multitouch
|
||||
-no-feature-quickcontrols2-material
|
||||
-no-feature-quickcontrols2-universal
|
||||
-no-feature-qml-network
|
||||
-no-feature-qml-profiler
|
||||
-no-feature-quick-listview
|
||||
-no-feature-quick-particles
|
||||
-no-feature-abstractbutton
|
||||
-no-feature-abstractslider
|
||||
-no-feature-buttongroup
|
||||
-no-feature-calendarwidget
|
||||
-no-feature-checkbox
|
||||
-no-feature-combobox
|
||||
-no-feature-commandlinkbutton
|
||||
-no-feature-contextmenu
|
||||
-no-feature-datetimeedit
|
||||
-no-feature-dial
|
||||
-no-feature-dockwidget
|
||||
-no-feature-fontcombobox
|
||||
-no-feature-formlayout
|
||||
-no-feature-graphicseffect
|
||||
-no-feature-graphicsview
|
||||
-no-feature-groupbox
|
||||
-no-feature-keysequenceedit
|
||||
-no-feature-label
|
||||
-no-feature-lcdnumber
|
||||
-no-feature-lineedit
|
||||
-no-feature-listwidget
|
||||
-no-feature-mainwindow
|
||||
-no-feature-mdiarea
|
||||
-no-feature-menu
|
||||
-no-feature-menubar
|
||||
-no-feature-printpreviewwidget
|
||||
-no-feature-progressbar
|
||||
-no-feature-pushbutton
|
||||
-no-feature-radiobutton
|
||||
-no-feature-resizehandler
|
||||
-no-feature-rubberband
|
||||
-no-feature-scrollarea
|
||||
-no-feature-scrollbar
|
||||
-no-feature-scroller
|
||||
-no-feature-sizegrip
|
||||
-no-feature-slider
|
||||
-no-feature-spinbox
|
||||
-no-feature-splashscreen
|
||||
-no-feature-splitter
|
||||
-no-feature-stackedwidget
|
||||
-no-feature-statusbar
|
||||
-no-feature-statustip
|
||||
-no-feature-syntaxhighlighter
|
||||
-no-feature-tabbar
|
||||
-no-feature-tablewidget
|
||||
-no-feature-tabwidget
|
||||
-no-feature-textbrowser
|
||||
-no-feature-textedit
|
||||
-no-feature-toolbar
|
||||
-no-feature-toolbox
|
||||
-no-feature-toolbutton
|
||||
-no-feature-tooltip
|
||||
-no-feature-treewidget
|
||||
-no-feature-validator
|
||||
-no-feature-widgettextcontrol
|
||||
-no-feature-quick-designer
|
||||
-no-feature-quick-flipable
|
||||
-no-feature-quick-pathview
|
||||
-no-feature-qml-profiler
|
||||
-no-feature-gif
|
||||
-no-feature-ico
|
||||
-no-feature-harfbuzz
|
||||
-no-feature-qml-debug
|
||||
-no-feature-quick-listview
|
||||
-no-feature-quick-sprite
|
||||
-no-feature-quick-path
|
||||
-no-feature-quick-canvas
|
||||
-no-feature-quick-animatedimage
|
||||
-no-feature-qml-interpreter
|
||||
-no-feature-action
|
||||
-no-feature-cssparser
|
||||
-no-feature-sharedmemory
|
||||
-no-feature-tabletevent
|
||||
-no-feature-texthtmlparser
|
||||
-no-feature-textodfwriter
|
||||
-no-feature-sessionmanager
|
||||
-no-feature-systemsemaphore
|
||||
-no-feature-im
|
||||
-no-feature-effects
|
||||
-no-feature-appstore-compliant
|
||||
-no-feature-big_codecs
|
||||
-no-feature-codecs
|
||||
-no-feature-colordialog
|
||||
-no-feature-colornames
|
||||
-no-feature-columnview
|
||||
-no-feature-commandlineparser
|
||||
-no-feature-cups
|
||||
-no-feature-d3d12
|
||||
-no-feature-datawidgetmapper
|
||||
-no-feature-datetimeparser
|
||||
-no-feature-desktopservices
|
||||
-no-feature-dialog
|
||||
-no-feature-dialogbuttonbox
|
||||
-no-feature-dirmodel
|
||||
-no-feature-dom
|
||||
-no-feature-errormessage
|
||||
-no-feature-filedialog
|
||||
-no-feature-filesystemiterator
|
||||
-no-feature-filesystemwatcher
|
||||
-no-feature-fontdialog
|
||||
-no-feature-fscompleter
|
||||
-no-feature-gestures
|
||||
-no-feature-iconv
|
||||
-no-feature-wizard
|
||||
-no-feature-xmlstreamwriter
|
||||
-no-feature-whatsthis
|
||||
-no-feature-undoview
|
||||
-no-feature-undostack
|
||||
-no-feature-undogroup
|
||||
-no-feature-undocommand
|
||||
-no-feature-treeview
|
||||
-no-feature-translation
|
||||
-no-feature-topleveldomain
|
||||
-no-feature-tableview
|
||||
-no-feature-style-stylesheet
|
||||
-no-feature-stringlistmodel
|
||||
-no-feature-sortfilterproxymodel
|
||||
-no-feature-wheelevent
|
||||
-no-feature-statemachine
|
||||
-no-feature-standarditemmodel
|
||||
-no-feature-proxymodel
|
||||
-no-feature-printer
|
||||
-no-feature-printpreviewdialog
|
||||
-no-feature-printdialog
|
||||
-no-feature-picture
|
||||
-no-feature-pdf
|
||||
-no-feature-movie
|
||||
-no-feature-messagebox
|
||||
-no-feature-listview
|
||||
-no-feature-itemmodel
|
||||
-no-feature-inputdialog
|
||||
-no-feature-filesystemmodel
|
||||
-no-feature-identityproxymodel
|
||||
-no-feature-mimetype
|
||||
-no-feature-paint_debug
|
||||
-no-feature-progressdialog
|
||||
-no-feature-quick-positioners
|
||||
-no-feature-sha3-fast
|
||||
-no-feature-completer
|
||||
-no-feature-image_heuristic_mask
|
||||
-no-feature-image_text
|
||||
-no-feature-imageformat_bmp
|