Merge pull request #16122 from danteruiz/qt-launcher

Qt launcher
This commit is contained in:
Shannon Romano 2019-10-29 17:05:37 -07:00 committed by GitHub
commit 4d001a1040
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
78 changed files with 15441 additions and 2 deletions

1
.gitignore vendored
View file

@ -92,6 +92,7 @@ npm-debug.log
# Resource binary file
interface/compiledResources
*.rcc
# GPUCache
interface/resources/GPUCache/*

View file

@ -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
View 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()

View 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>

View 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)

File diff suppressed because it is too large Load diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 299 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

View 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()

View 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)

View 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)

View 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>

View file

@ -0,0 +1 @@
IDI_ICON1 ICON DISCARDABLE "@CONFIGURE_ICON_PATH@"

View file

@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource prefix="@QRC_PREFIX_PATH@">
@QRC_CONTENTS@
</qresource>
</RCC>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View 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...
/////////////////////////////////////////////////////////////////////////////

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

View 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");
}
}

View 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");
}
}

View 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");
}
}

View 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");
}
}

View 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");
}
}

View 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
}
}

View file

@ -0,0 +1,7 @@
import QtQuick 2.3
import QtQuick 2.1
HFTextRegular {
color: "#FF0014"
}

View 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;
}
}
}
}
}

View file

@ -0,0 +1,8 @@
import QtQuick 2.3
import QtQuick 2.1
Text {
font.family: "Graphik Semibold"
font.pixelSize: 32
color: "#ffffff"
}

View 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"
}

View 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
}
}

View file

@ -0,0 +1,7 @@
// login
import "HFBase"
LoginBase {
anchors.fill: parent
}

View 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");
}
}

View 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
}
}
]
}
}

View 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();
}

View 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;
};

View 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;
}

View 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
View 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
View 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();

View 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];
}
}
}

View 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;
}

View 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() {
}

View 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;
};

View 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;
}

View 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;
};

View 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;
}

View 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 };
};

View 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);
}
}
}

View 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 };
};

View 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();
}

View 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;
};

View file

@ -0,0 +1,9 @@
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface NSTask (NSTaskExecveAdditions)
- (void) replaceThisProcess;
@end
NS_ASSUME_NONNULL_END

View 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

View 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
}

View 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();
};

View 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();
}

View 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 };
};

View 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, "");
}

View 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;
};

View 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();
}

View 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
View 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();
}

View file

@ -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)

View 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'

View 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

View 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