diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 1ede3f74..b387f46a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -23,6 +23,13 @@ body: - macOS - Linux - Other +- type: textarea + attributes: + label: Version of PolyMC + description: The version of PolyMC used in the bug report. + placeholder: PolyMC 1.2.2 + validations: + required: true - type: textarea attributes: label: Description of bug diff --git a/CMakeLists.txt b/CMakeLists.txt index b052fa1e..4d3683d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -91,7 +91,7 @@ set(Launcher_MSA_CLIENT_ID "549033b2-1532-4d4e-ae77-1bbaa46f9d74" CACHE STRING " # CurseForge API Key # CHANGE THIS IF YOU FORK THIS PROJECT! -set(Launcher_CURSEFORGE_API_KEY "$2a$10$iR1RdPDG95FWdILZbHuoMOlV4vL4eckBx7QPZR6SVZmliEb9ZQplu" CACHE STRING "CurseForge API Key") +set(Launcher_CURSEFORGE_API_KEY "$2a$10$1Oqr2MX3O4n/ilhFGc597u8tfI3L2Hyr9/rtWDAMRjghSQV2QUuxq" CACHE STRING "CurseForge API Key") # Bug tracker URL set(Launcher_BUG_TRACKER_URL "https://github.com/PolyMC/PolyMC/issues" CACHE STRING "URL for the bug tracker.") diff --git a/buildconfig/BuildConfig.h b/buildconfig/BuildConfig.h index a920a3d4..8594e46d 100644 --- a/buildconfig/BuildConfig.h +++ b/buildconfig/BuildConfig.h @@ -151,6 +151,9 @@ class Config { */ QString TECHNIC_API_BUILD = "multimc"; + QString MODRINTH_STAGING_URL = "https://staging-api.modrinth.com/v2"; + QString MODRINTH_PROD_URL = "https://api.modrinth.com/v2"; + /** * \brief Converts the Version to a string. * \return The version number in string format (major.minor.revision.build). diff --git a/cmake/BundleUtilities.cmake b/cmake/BundleUtilities.cmake deleted file mode 100644 index e3f50b94..00000000 --- a/cmake/BundleUtilities.cmake +++ /dev/null @@ -1,786 +0,0 @@ -# - Functions to help assemble a standalone bundle application. -# A collection of CMake utility functions useful for dealing with .app -# bundles on the Mac and bundle-like directories on any OS. -# -# The following functions are provided by this module: -# fixup_bundle -# copy_and_fixup_bundle -# verify_app -# get_bundle_main_executable -# get_dotapp_dir -# get_bundle_and_executable -# get_bundle_all_executables -# get_item_key -# clear_bundle_keys -# set_bundle_key_values -# get_bundle_keys -# copy_resolved_item_into_bundle -# copy_resolved_framework_into_bundle -# fixup_bundle_item -# verify_bundle_prerequisites -# verify_bundle_symlinks -# Requires CMake 2.6 or greater because it uses function, break and -# PARENT_SCOPE. Also depends on GetPrerequisites.cmake. -# -# FIXUP_BUNDLE( ) -# Fix up a bundle in-place and make it standalone, such that it can be -# drag-n-drop copied to another machine and run on that machine as long as all -# of the system libraries are compatible. -# -# If you pass plugins to fixup_bundle as the libs parameter, you should install -# them or copy them into the bundle before calling fixup_bundle. The "libs" -# parameter is a list of libraries that must be fixed up, but that cannot be -# determined by otool output analysis. (i.e., plugins) -# -# Gather all the keys for all the executables and libraries in a bundle, and -# then, for each key, copy each prerequisite into the bundle. Then fix each one -# up according to its own list of prerequisites. -# -# Then clear all the keys and call verify_app on the final bundle to ensure -# that it is truly standalone. -# -# COPY_AND_FIXUP_BUNDLE( ) -# Makes a copy of the bundle at location and then fixes up the -# new copied bundle in-place at ... -# -# VERIFY_APP() -# Verifies that an application appears valid based on running analysis -# tools on it. Calls "message(FATAL_ERROR" if the application is not verified. -# -# GET_BUNDLE_MAIN_EXECUTABLE( ) -# The result will be the full path name of the bundle's main executable file -# or an "error:" prefixed string if it could not be determined. -# -# GET_DOTAPP_DIR( ) -# Returns the nearest parent dir whose name ends with ".app" given the full -# path to an executable. If there is no such parent dir, then simply return -# the dir containing the executable. -# -# The returned directory may or may not exist. -# -# GET_BUNDLE_AND_EXECUTABLE( ) -# Takes either a ".app" directory name or the name of an executable -# nested inside a ".app" directory and returns the path to the ".app" -# directory in and the path to its main executable in -# -# -# GET_BUNDLE_ALL_EXECUTABLES( ) -# Scans the given bundle recursively for all executable files and accumulates -# them into a variable. -# -# GET_ITEM_KEY( ) -# Given a file (item) name, generate a key that should be unique considering -# the set of libraries that need copying or fixing up to make a bundle -# standalone. This is essentially the file name including extension with "." -# replaced by "_" -# -# This key is used as a prefix for CMake variables so that we can associate a -# set of variables with a given item based on its key. -# -# CLEAR_BUNDLE_KEYS() -# Loop over the list of keys, clearing all the variables associated with each -# key. After the loop, clear the list of keys itself. -# -# Caller of get_bundle_keys should call clear_bundle_keys when done with list -# of keys. -# -# SET_BUNDLE_KEY_VALUES( -# ) -# Add a key to the list (if necessary) for the given item. If added, -# also set all the variables associated with that key. -# -# GET_BUNDLE_KEYS( ) -# Loop over all the executable and library files within the bundle (and given -# as extra ) and accumulate a list of keys representing them. Set -# values associated with each key such that we can loop over all of them and -# copy prerequisite libs into the bundle and then do appropriate -# install_name_tool fixups. -# -# COPY_RESOLVED_ITEM_INTO_BUNDLE( ) -# Copy a resolved item into the bundle if necessary. Copy is not necessary if -# the resolved_item is "the same as" the resolved_embedded_item. -# -# COPY_RESOLVED_FRAMEWORK_INTO_BUNDLE( ) -# Copy a resolved framework into the bundle if necessary. Copy is not necessary -# if the resolved_item is "the same as" the resolved_embedded_item. -# -# By default, BU_COPY_FULL_FRAMEWORK_CONTENTS is not set. If you want full -# frameworks embedded in your bundles, set BU_COPY_FULL_FRAMEWORK_CONTENTS to -# ON before calling fixup_bundle. By default, -# COPY_RESOLVED_FRAMEWORK_INTO_BUNDLE copies the framework dylib itself plus -# the framework Resources directory. -# -# FIXUP_BUNDLE_ITEM( ) -# Get the direct/non-system prerequisites of the resolved embedded item. For -# each prerequisite, change the way it is referenced to the value of the -# _EMBEDDED_ITEM keyed variable for that prerequisite. (Most likely changing to -# an "@executable_path" style reference.) -# -# This function requires that the resolved_embedded_item be "inside" the bundle -# already. In other words, if you pass plugins to fixup_bundle as the libs -# parameter, you should install them or copy them into the bundle before -# calling fixup_bundle. The "libs" parameter is a list of libraries that must -# be fixed up, but that cannot be determined by otool output analysis. (i.e., -# plugins) -# -# Also, change the id of the item being fixed up to its own _EMBEDDED_ITEM -# value. -# -# Accumulate changes in a local variable and make *one* call to -# install_name_tool at the end of the function with all the changes at once. -# -# If the BU_CHMOD_BUNDLE_ITEMS variable is set then bundle items will be -# marked writable before install_name_tool tries to change them. -# -# VERIFY_BUNDLE_PREREQUISITES( ) -# Verifies that the sum of all prerequisites of all files inside the bundle -# are contained within the bundle or are "system" libraries, presumed to exist -# everywhere. -# -# VERIFY_BUNDLE_SYMLINKS( ) -# Verifies that any symlinks found in the bundle point to other files that are -# already also in the bundle... Anything that points to an external file causes -# this function to fail the verification. - -#============================================================================= -# Copyright 2008-2009 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -# The functions defined in this file depend on the get_prerequisites function -# (and possibly others) found in: -# -get_filename_component(BundleUtilities_cmake_dir "${CMAKE_CURRENT_LIST_FILE}" PATH) -include("${BundleUtilities_cmake_dir}/GetPrerequisites.cmake") - - -function(get_bundle_main_executable bundle result_var) - set(result "error: '${bundle}/Contents/Info.plist' file does not exist") - - if(EXISTS "${bundle}/Contents/Info.plist") - set(result "error: no CFBundleExecutable in '${bundle}/Contents/Info.plist' file") - set(line_is_main_executable 0) - set(bundle_executable "") - - # Read Info.plist as a list of lines: - # - set(eol_char "E") - file(READ "${bundle}/Contents/Info.plist" info_plist) - string(REGEX REPLACE ";" "\\\\;" info_plist "${info_plist}") - string(REGEX REPLACE "\n" "${eol_char};" info_plist "${info_plist}") - - # Scan the lines for "CFBundleExecutable" - the line after that - # is the name of the main executable. - # - foreach(line ${info_plist}) - if(line_is_main_executable) - string(REGEX REPLACE "^.*(.*).*$" "\\1" bundle_executable "${line}") - break() - endif() - - if(line MATCHES "^.*CFBundleExecutable.*$") - set(line_is_main_executable 1) - endif() - endforeach() - - if(NOT "${bundle_executable}" STREQUAL "") - if(EXISTS "${bundle}/Contents/MacOS/${bundle_executable}") - set(result "${bundle}/Contents/MacOS/${bundle_executable}") - else() - - # Ultimate goal: - # If not in "Contents/MacOS" then scan the bundle for matching files. If - # there is only one executable file that matches, then use it, otherwise - # it's an error... - # - #file(GLOB_RECURSE file_list "${bundle}/${bundle_executable}") - - # But for now, pragmatically, it's an error. Expect the main executable - # for the bundle to be in Contents/MacOS, it's an error if it's not: - # - set(result "error: '${bundle}/Contents/MacOS/${bundle_executable}' does not exist") - endif() - endif() - else() - # - # More inclusive technique... (This one would work on Windows and Linux - # too, if a developer followed the typical Mac bundle naming convention...) - # - # If there is no Info.plist file, try to find an executable with the same - # base name as the .app directory: - # - endif() - - set(${result_var} "${result}" PARENT_SCOPE) -endfunction() - - -function(get_dotapp_dir exe dotapp_dir_var) - set(s "${exe}") - - if(s MATCHES "^.*/.*\\.app/.*$") - # If there is a ".app" parent directory, - # ascend until we hit it: - # (typical of a Mac bundle executable) - # - set(done 0) - while(NOT ${done}) - get_filename_component(snamewe "${s}" NAME_WE) - get_filename_component(sname "${s}" NAME) - get_filename_component(sdir "${s}" PATH) - set(s "${sdir}") - if(sname MATCHES "\\.app$") - set(done 1) - set(dotapp_dir "${sdir}/${sname}") - endif() - endwhile() - else() - # Otherwise use a directory containing the exe - # (typical of a non-bundle executable on Mac, Windows or Linux) - # - is_file_executable("${s}" is_executable) - if(is_executable) - get_filename_component(sdir "${s}" PATH) - set(dotapp_dir "${sdir}") - else() - set(dotapp_dir "${s}") - endif() - endif() - - - set(${dotapp_dir_var} "${dotapp_dir}" PARENT_SCOPE) -endfunction() - - -function(get_bundle_and_executable app bundle_var executable_var valid_var) - set(valid 0) - - if(EXISTS "${app}") - # Is it a directory ending in .app? - if(IS_DIRECTORY "${app}") - if(app MATCHES "\\.app$") - get_bundle_main_executable("${app}" executable) - if(EXISTS "${app}" AND EXISTS "${executable}") - set(${bundle_var} "${app}" PARENT_SCOPE) - set(${executable_var} "${executable}" PARENT_SCOPE) - set(valid 1) - #message(STATUS "info: handled .app directory case...") - else() - message(STATUS "warning: *NOT* handled - .app directory case...") - endif() - else() - message(STATUS "warning: *NOT* handled - directory but not .app case...") - endif() - else() - # Is it an executable file? - is_file_executable("${app}" is_executable) - if(is_executable) - get_dotapp_dir("${app}" dotapp_dir) - if(EXISTS "${dotapp_dir}") - set(${bundle_var} "${dotapp_dir}" PARENT_SCOPE) - set(${executable_var} "${app}" PARENT_SCOPE) - set(valid 1) - #message(STATUS "info: handled executable file in .app dir case...") - else() - get_filename_component(app_dir "${app}" PATH) - set(${bundle_var} "${app_dir}" PARENT_SCOPE) - set(${executable_var} "${app}" PARENT_SCOPE) - set(valid 1) - #message(STATUS "info: handled executable file in any dir case...") - endif() - else() - message(STATUS "warning: *NOT* handled - not .app dir, not executable file...") - endif() - endif() - else() - message(STATUS "warning: *NOT* handled - directory/file ${app} does not exist...") - endif() - - if(NOT valid) - set(${bundle_var} "error: not a bundle" PARENT_SCOPE) - set(${executable_var} "error: not a bundle" PARENT_SCOPE) - endif() - - set(${valid_var} ${valid} PARENT_SCOPE) -endfunction() - - -function(get_bundle_all_executables bundle exes_var) - set(exes "") - - file(GLOB_RECURSE file_list "${bundle}/*") - foreach(f ${file_list}) - is_file_executable("${f}" is_executable) - if(is_executable) - set(exes ${exes} "${f}") - endif() - endforeach() - - set(${exes_var} "${exes}" PARENT_SCOPE) -endfunction() - - -function(get_item_key item key_var) - get_filename_component(item_name "${item}" NAME) - if(WIN32) - string(TOLOWER "${item_name}" item_name) - endif() - string(REGEX REPLACE "\\." "_" ${key_var} "${item_name}") - set(${key_var} ${${key_var}} PARENT_SCOPE) -endfunction() - - -function(clear_bundle_keys keys_var) - foreach(key ${${keys_var}}) - set(${key}_ITEM PARENT_SCOPE) - set(${key}_RESOLVED_ITEM PARENT_SCOPE) - set(${key}_DEFAULT_EMBEDDED_PATH PARENT_SCOPE) - set(${key}_EMBEDDED_ITEM PARENT_SCOPE) - set(${key}_RESOLVED_EMBEDDED_ITEM PARENT_SCOPE) - set(${key}_COPYFLAG PARENT_SCOPE) - endforeach() - set(${keys_var} PARENT_SCOPE) -endfunction() - - -function(set_bundle_key_values keys_var context item exepath dirs copyflag) - get_filename_component(item_name "${item}" NAME) - - get_item_key("${item}" key) - - list(LENGTH ${keys_var} length_before) - gp_append_unique(${keys_var} "${key}") - list(LENGTH ${keys_var} length_after) - - if(NOT length_before EQUAL length_after) - gp_resolve_item("${context}" "${item}" "${exepath}" "${dirs}" resolved_item) - - gp_item_default_embedded_path("${item}" default_embedded_path) - - if(item MATCHES "[^/]+\\.framework/") - # For frameworks, construct the name under the embedded path from the - # opening "${item_name}.framework/" to the closing "/${item_name}": - # - string(REGEX REPLACE "^.*(${item_name}.framework/.*/?${item_name}).*$" "${default_embedded_path}/\\1" embedded_item "${item}") - else() - # For other items, just use the same name as the original, but in the - # embedded path: - # - set(embedded_item "${default_embedded_path}/${item_name}") - endif() - - # Replace @executable_path and resolve ".." references: - # - string(REPLACE "@executable_path" "${exepath}" resolved_embedded_item "${embedded_item}") - get_filename_component(resolved_embedded_item "${resolved_embedded_item}" ABSOLUTE) - - # *But* -- if we are not copying, then force resolved_embedded_item to be - # the same as resolved_item. In the case of multiple executables in the - # original bundle, using the default_embedded_path results in looking for - # the resolved executable next to the main bundle executable. This is here - # so that exes in the other sibling directories (like "bin") get fixed up - # properly... - # - if(NOT copyflag) - set(resolved_embedded_item "${resolved_item}") - endif() - - set(${keys_var} ${${keys_var}} PARENT_SCOPE) - set(${key}_ITEM "${item}" PARENT_SCOPE) - set(${key}_RESOLVED_ITEM "${resolved_item}" PARENT_SCOPE) - set(${key}_DEFAULT_EMBEDDED_PATH "${default_embedded_path}" PARENT_SCOPE) - set(${key}_EMBEDDED_ITEM "${embedded_item}" PARENT_SCOPE) - set(${key}_RESOLVED_EMBEDDED_ITEM "${resolved_embedded_item}" PARENT_SCOPE) - set(${key}_COPYFLAG "${copyflag}" PARENT_SCOPE) - else() - #message("warning: item key '${key}' already in the list, subsequent references assumed identical to first") - endif() -endfunction() - - -function(get_bundle_keys app libs dirs keys_var) - set(${keys_var} PARENT_SCOPE) - - get_bundle_and_executable("${app}" bundle executable valid) - if(valid) - # Always use the exepath of the main bundle executable for @executable_path - # replacements: - # - get_filename_component(exepath "${executable}" PATH) - - # But do fixups on all executables in the bundle: - # - get_bundle_all_executables("${bundle}" exes) - - # For each extra lib, accumulate a key as well and then also accumulate - # any of its prerequisites. (Extra libs are typically dynamically loaded - # plugins: libraries that are prerequisites for full runtime functionality - # but that do not show up in otool -L output...) - # - foreach(lib ${libs}) - set_bundle_key_values(${keys_var} "${lib}" "${lib}" "${exepath}" "${dirs}" 0) - - set(prereqs "") - get_prerequisites("${lib}" prereqs 1 1 "${exepath}" "${dirs}") - foreach(pr ${prereqs}) - set_bundle_key_values(${keys_var} "${lib}" "${pr}" "${exepath}" "${dirs}" 1) - endforeach() - endforeach() - - # For each executable found in the bundle, accumulate keys as we go. - # The list of keys should be complete when all prerequisites of all - # binaries in the bundle have been analyzed. - # - foreach(exe ${exes}) - # Add the exe itself to the keys: - # - set_bundle_key_values(${keys_var} "${exe}" "${exe}" "${exepath}" "${dirs}" 0) - - # Add each prerequisite to the keys: - # - set(prereqs "") - get_prerequisites("${exe}" prereqs 1 1 "${exepath}" "${dirs}") - foreach(pr ${prereqs}) - set_bundle_key_values(${keys_var} "${exe}" "${pr}" "${exepath}" "${dirs}" 1) - endforeach() - endforeach() - - # Propagate values to caller's scope: - # - set(${keys_var} ${${keys_var}} PARENT_SCOPE) - foreach(key ${${keys_var}}) - set(${key}_ITEM "${${key}_ITEM}" PARENT_SCOPE) - set(${key}_RESOLVED_ITEM "${${key}_RESOLVED_ITEM}" PARENT_SCOPE) - set(${key}_DEFAULT_EMBEDDED_PATH "${${key}_DEFAULT_EMBEDDED_PATH}" PARENT_SCOPE) - set(${key}_EMBEDDED_ITEM "${${key}_EMBEDDED_ITEM}" PARENT_SCOPE) - set(${key}_RESOLVED_EMBEDDED_ITEM "${${key}_RESOLVED_EMBEDDED_ITEM}" PARENT_SCOPE) - set(${key}_COPYFLAG "${${key}_COPYFLAG}" PARENT_SCOPE) - endforeach() - endif() -endfunction() - - -function(copy_resolved_item_into_bundle resolved_item resolved_embedded_item) - if(WIN32) - # ignore case on Windows - string(TOLOWER "${resolved_item}" resolved_item_compare) - string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) - else() - set(resolved_item_compare "${resolved_item}") - set(resolved_embedded_item_compare "${resolved_embedded_item}") - endif() - - if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") - message(STATUS "warning: resolved_item == resolved_embedded_item - not copying...") - else() - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") - if(UNIX AND NOT APPLE) - file(RPATH_REMOVE FILE "${resolved_embedded_item}") - endif() - endif() - -endfunction() - - -function(copy_resolved_framework_into_bundle resolved_item resolved_embedded_item) - if(WIN32) - # ignore case on Windows - string(TOLOWER "${resolved_item}" resolved_item_compare) - string(TOLOWER "${resolved_embedded_item}" resolved_embedded_item_compare) - else() - set(resolved_item_compare "${resolved_item}") - set(resolved_embedded_item_compare "${resolved_embedded_item}") - endif() - - if("${resolved_item_compare}" STREQUAL "${resolved_embedded_item_compare}") - message(STATUS "warning: resolved_item == resolved_embedded_item - not copying...") - else() - if(BU_COPY_FULL_FRAMEWORK_CONTENTS) - # Full Framework (everything): - get_filename_component(resolved_dir "${resolved_item}" PATH) - get_filename_component(resolved_dir "${resolved_dir}/../.." ABSOLUTE) - get_filename_component(resolved_embedded_dir "${resolved_embedded_item}" PATH) - get_filename_component(resolved_embedded_dir "${resolved_embedded_dir}/../.." ABSOLUTE) - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_dir}' '${resolved_embedded_dir}'") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${resolved_dir}" "${resolved_embedded_dir}") - else() - # Framework lib itself: - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy ${resolved_item} ${resolved_embedded_item}") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${resolved_item}" "${resolved_embedded_item}") - - # Plus Resources, if they exist: - string(REGEX REPLACE "^(.*)/[^/]+/[^/]+/[^/]+$" "\\1/Resources" resolved_resources "${resolved_item}") - string(REGEX REPLACE "^(.*)/[^/]+/[^/]+/[^/]+$" "\\1/Resources" resolved_embedded_resources "${resolved_embedded_item}") - if(EXISTS "${resolved_resources}") - #message(STATUS "copying COMMAND ${CMAKE_COMMAND} -E copy_directory '${resolved_resources}' '${resolved_embedded_resources}'") - execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${resolved_resources}" "${resolved_embedded_resources}") - endif() - endif() - if(UNIX AND NOT APPLE) - file(RPATH_REMOVE FILE "${resolved_embedded_item}") - endif() - endif() - -endfunction() - - -function(fixup_bundle_item resolved_embedded_item exepath dirs) - # This item's key is "ikey": - # - get_item_key("${resolved_embedded_item}" ikey) - - # Ensure the item is "inside the .app bundle" -- it should not be fixed up if - # it is not in the .app bundle... Otherwise, we'll modify files in the build - # tree, or in other varied locations around the file system, with our call to - # install_name_tool. Make sure that doesn't happen here: - # - get_dotapp_dir("${exepath}" exe_dotapp_dir) - string(LENGTH "${exe_dotapp_dir}/" exe_dotapp_dir_length) - string(LENGTH "${resolved_embedded_item}" resolved_embedded_item_length) - set(path_too_short 0) - set(is_embedded 0) - if(${resolved_embedded_item_length} LESS ${exe_dotapp_dir_length}) - set(path_too_short 1) - endif() - if(NOT path_too_short) - string(SUBSTRING "${resolved_embedded_item}" 0 ${exe_dotapp_dir_length} item_substring) - if("${exe_dotapp_dir}/" STREQUAL "${item_substring}") - set(is_embedded 1) - endif() - endif() - if(NOT is_embedded) - message(" exe_dotapp_dir/='${exe_dotapp_dir}/'") - message(" item_substring='${item_substring}'") - message(" resolved_embedded_item='${resolved_embedded_item}'") - message("") - message("Install or copy the item into the bundle before calling fixup_bundle.") - message("Or maybe there's a typo or incorrect path in one of the args to fixup_bundle?") - message("") - message(FATAL_ERROR "cannot fixup an item that is not in the bundle...") - endif() - - set(prereqs "") - get_prerequisites("${resolved_embedded_item}" prereqs 1 0 "${exepath}" "${dirs}") - - set(changes "") - - foreach(pr ${prereqs}) - # Each referenced item's key is "rkey" in the loop: - # - get_item_key("${pr}" rkey) - - if(NOT "${${rkey}_EMBEDDED_ITEM}" STREQUAL "") - set(changes ${changes} "-change" "${pr}" "${${rkey}_EMBEDDED_ITEM}") - else() - message("warning: unexpected reference to '${pr}'") - endif() - endforeach() - - if(BU_CHMOD_BUNDLE_ITEMS) - execute_process(COMMAND chmod u+w "${resolved_embedded_item}") - endif() - - # Change this item's id and all of its references in one call - # to install_name_tool: - # - execute_process(COMMAND install_name_tool - ${changes} -id "${${ikey}_EMBEDDED_ITEM}" "${resolved_embedded_item}" - ) -endfunction() - - -function(fixup_bundle app libs dirs) - message(STATUS "fixup_bundle") - message(STATUS " app='${app}'") - message(STATUS " libs='${libs}'") - message(STATUS " dirs='${dirs}'") - - get_bundle_and_executable("${app}" bundle executable valid) - if(valid) - get_filename_component(exepath "${executable}" PATH) - - message(STATUS "fixup_bundle: preparing...") - get_bundle_keys("${app}" "${libs}" "${dirs}" keys) - - message(STATUS "fixup_bundle: copying...") - list(LENGTH keys n) - math(EXPR n ${n}*2) - - set(i 0) - foreach(key ${keys}) - math(EXPR i ${i}+1) - if(${${key}_COPYFLAG}) - message(STATUS "${i}/${n}: copying '${${key}_RESOLVED_ITEM}'") - else() - message(STATUS "${i}/${n}: *NOT* copying '${${key}_RESOLVED_ITEM}'") - endif() - - set(show_status 0) - if(show_status) - message(STATUS "key='${key}'") - message(STATUS "item='${${key}_ITEM}'") - message(STATUS "resolved_item='${${key}_RESOLVED_ITEM}'") - message(STATUS "default_embedded_path='${${key}_DEFAULT_EMBEDDED_PATH}'") - message(STATUS "embedded_item='${${key}_EMBEDDED_ITEM}'") - message(STATUS "resolved_embedded_item='${${key}_RESOLVED_EMBEDDED_ITEM}'") - message(STATUS "copyflag='${${key}_COPYFLAG}'") - message(STATUS "") - endif() - - if(${${key}_COPYFLAG}) - set(item "${${key}_ITEM}") - if(item MATCHES "[^/]+\\.framework/") - copy_resolved_framework_into_bundle("${${key}_RESOLVED_ITEM}" - "${${key}_RESOLVED_EMBEDDED_ITEM}") - else() - copy_resolved_item_into_bundle("${${key}_RESOLVED_ITEM}" - "${${key}_RESOLVED_EMBEDDED_ITEM}") - endif() - endif() - endforeach() - - message(STATUS "fixup_bundle: fixing...") - foreach(key ${keys}) - math(EXPR i ${i}+1) - if(APPLE) - message(STATUS "${i}/${n}: fixing up '${${key}_RESOLVED_EMBEDDED_ITEM}'") - fixup_bundle_item("${${key}_RESOLVED_EMBEDDED_ITEM}" "${exepath}" "${dirs}") - else() - message(STATUS "${i}/${n}: fix-up not required on this platform '${${key}_RESOLVED_EMBEDDED_ITEM}'") - endif() - endforeach() - - message(STATUS "fixup_bundle: cleaning up...") - clear_bundle_keys(keys) - - message(STATUS "fixup_bundle: verifying...") - verify_app("${app}") - else() - message(SEND_ERROR "error: fixup_bundle: not a valid bundle") - endif() - - message(STATUS "fixup_bundle: done") -endfunction() - - -function(copy_and_fixup_bundle src dst libs dirs) - execute_process(COMMAND ${CMAKE_COMMAND} -E copy_directory "${src}" "${dst}") - fixup_bundle("${dst}" "${libs}" "${dirs}") -endfunction() - - -function(verify_bundle_prerequisites bundle result_var info_var) - set(result 1) - set(info "") - set(count 0) - - get_bundle_main_executable("${bundle}" main_bundle_exe) - - file(GLOB_RECURSE file_list "${bundle}/*") - foreach(f ${file_list}) - is_file_executable("${f}" is_executable) - if(is_executable) - get_filename_component(exepath "${f}" PATH) - math(EXPR count "${count} + 1") - - message(STATUS "executable file ${count}: ${f}") - - set(prereqs "") - get_prerequisites("${f}" prereqs 1 1 "${exepath}" "") - - # On the Mac, - # "embedded" and "system" prerequisites are fine... anything else means - # the bundle's prerequisites are not verified (i.e., the bundle is not - # really "standalone") - # - # On Windows (and others? Linux/Unix/...?) - # "local" and "system" prereqs are fine... - # - set(external_prereqs "") - - foreach(p ${prereqs}) - set(p_type "") - gp_file_type("${f}" "${p}" p_type) - - if(APPLE) - if(NOT "${p_type}" STREQUAL "embedded" AND NOT "${p_type}" STREQUAL "system") - set(external_prereqs ${external_prereqs} "${p}") - endif() - else() - if(NOT "${p_type}" STREQUAL "local" AND NOT "${p_type}" STREQUAL "system") - set(external_prereqs ${external_prereqs} "${p}") - endif() - endif() - endforeach() - - if(external_prereqs) - # Found non-system/somehow-unacceptable prerequisites: - set(result 0) - set(info ${info} "external prerequisites found:\nf='${f}'\nexternal_prereqs='${external_prereqs}'\n") - endif() - endif() - endforeach() - - if(result) - set(info "Verified ${count} executable files in '${bundle}'") - endif() - - set(${result_var} "${result}" PARENT_SCOPE) - set(${info_var} "${info}" PARENT_SCOPE) -endfunction() - - -function(verify_bundle_symlinks bundle result_var info_var) - set(result 1) - set(info "") - set(count 0) - - # TODO: implement this function for real... - # Right now, it is just a stub that verifies unconditionally... - - set(${result_var} "${result}" PARENT_SCOPE) - set(${info_var} "${info}" PARENT_SCOPE) -endfunction() - - -function(verify_app app) - set(verified 0) - set(info "") - - get_bundle_and_executable("${app}" bundle executable valid) - - message(STATUS "===========================================================================") - message(STATUS "Analyzing app='${app}'") - message(STATUS "bundle='${bundle}'") - message(STATUS "executable='${executable}'") - message(STATUS "valid='${valid}'") - - # Verify that the bundle does not have any "external" prerequisites: - # - verify_bundle_prerequisites("${bundle}" verified info) - message(STATUS "verified='${verified}'") - message(STATUS "info='${info}'") - message(STATUS "") - - if(verified) - # Verify that the bundle does not have any symlinks to external files: - # - verify_bundle_symlinks("${bundle}" verified info) - message(STATUS "verified='${verified}'") - message(STATUS "info='${info}'") - message(STATUS "") - endif() - - if(NOT verified) - message(FATAL_ERROR "error: verify_app failed") - endif() -endfunction() diff --git a/cmake/GetPrerequisites.cmake b/cmake/GetPrerequisites.cmake deleted file mode 100644 index 39c2cc63..00000000 --- a/cmake/GetPrerequisites.cmake +++ /dev/null @@ -1,902 +0,0 @@ -# - Functions to analyze and list executable file prerequisites. -# This module provides functions to list the .dll, .dylib or .so -# files that an executable or shared library file depends on. (Its -# prerequisites.) -# -# It uses various tools to obtain the list of required shared library files: -# dumpbin (Windows) -# objdump (MinGW on Windows) -# ldd (Linux/Unix) -# otool (Mac OSX) -# The following functions are provided by this module: -# get_prerequisites -# list_prerequisites -# list_prerequisites_by_glob -# gp_append_unique -# is_file_executable -# gp_item_default_embedded_path -# (projects can override with gp_item_default_embedded_path_override) -# gp_resolve_item -# (projects can override with gp_resolve_item_override) -# gp_resolved_file_type -# (projects can override with gp_resolved_file_type_override) -# gp_file_type -# Requires CMake 2.6 or greater because it uses function, break, return and -# PARENT_SCOPE. -# -# GET_PREREQUISITES( -# ) -# Get the list of shared library files required by . The list in -# the variable named should be empty on first entry to -# this function. On exit, will contain the list of -# required shared library files. -# -# is the full path to an executable file. is the -# name of a CMake variable to contain the results. must be 0 -# or 1 indicating whether to include or exclude "system" prerequisites. If -# is set to 1 all prerequisites will be found recursively, if set to -# 0 only direct prerequisites are listed. is the path to the top -# level executable used for @executable_path replacment on the Mac. is -# a list of paths where libraries might be found: these paths are searched -# first when a target without any path info is given. Then standard system -# locations are also searched: PATH, Framework locations, /usr/lib... -# -# LIST_PREREQUISITES( [ [ []]]) -# Print a message listing the prerequisites of . -# -# is the name of a shared library or executable target or the full -# path to a shared library or executable file. If is set to 1 all -# prerequisites will be found recursively, if set to 0 only direct -# prerequisites are listed. must be 0 or 1 indicating whether -# to include or exclude "system" prerequisites. With set to 0 only -# the full path names of the prerequisites are printed, set to 1 extra -# informatin will be displayed. -# -# LIST_PREREQUISITES_BY_GLOB( ) -# Print the prerequisites of shared library and executable files matching a -# globbing pattern. is GLOB or GLOB_RECURSE and is a -# globbing expression used with "file(GLOB" or "file(GLOB_RECURSE" to retrieve -# a list of matching files. If a matching file is executable, its prerequisites -# are listed. -# -# Any additional (optional) arguments provided are passed along as the -# optional arguments to the list_prerequisites calls. -# -# GP_APPEND_UNIQUE( ) -# Append to the list variable only if the value is not -# already in the list. -# -# IS_FILE_EXECUTABLE( ) -# Return 1 in if is a binary executable, 0 otherwise. -# -# GP_ITEM_DEFAULT_EMBEDDED_PATH( ) -# Return the path that others should refer to the item by when the item -# is embedded inside a bundle. -# -# Override on a per-project basis by providing a project-specific -# gp_item_default_embedded_path_override function. -# -# GP_RESOLVE_ITEM( ) -# Resolve an item into an existing full path file. -# -# Override on a per-project basis by providing a project-specific -# gp_resolve_item_override function. -# -# GP_RESOLVED_FILE_TYPE( ) -# Return the type of with respect to . String -# describing type of prerequisite is returned in variable named . -# -# Use and if necessary to resolve non-absolute -# values -- but only for non-embedded items. -# -# Possible types are: -# system -# local -# embedded -# other -# Override on a per-project basis by providing a project-specific -# gp_resolved_file_type_override function. -# -# GP_FILE_TYPE( ) -# Return the type of with respect to . String -# describing type of prerequisite is returned in variable named . -# -# Possible types are: -# system -# local -# embedded -# other - -#============================================================================= -# Copyright 2008-2009 Kitware, Inc. -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -function(gp_append_unique list_var value) - set(contains 0) - - foreach(item ${${list_var}}) - if("${item}" STREQUAL "${value}") - set(contains 1) - break() - endif() - endforeach() - - if(NOT contains) - set(${list_var} ${${list_var}} "${value}" PARENT_SCOPE) - endif() -endfunction() - - -function(is_file_executable file result_var) - # - # A file is not executable until proven otherwise: - # - set(${result_var} 0 PARENT_SCOPE) - - get_filename_component(file_full "${file}" ABSOLUTE) - string(TOLOWER "${file_full}" file_full_lower) - - # If file name ends in .exe on Windows, *assume* executable: - # - if(WIN32 AND NOT UNIX) - if("${file_full_lower}" MATCHES "\\.exe$") - set(${result_var} 1 PARENT_SCOPE) - return() - endif() - - # A clause could be added here that uses output or return value of dumpbin - # to determine ${result_var}. In 99%+? practical cases, the exe name - # match will be sufficient... - # - endif() - - # Use the information returned from the Unix shell command "file" to - # determine if ${file_full} should be considered an executable file... - # - # If the file command's output contains "executable" and does *not* contain - # "text" then it is likely an executable suitable for prerequisite analysis - # via the get_prerequisites macro. - # - if(UNIX) - if(NOT file_cmd) - find_program(file_cmd "file") - mark_as_advanced(file_cmd) - endif() - - if(file_cmd) - execute_process(COMMAND "${file_cmd}" "${file_full}" - OUTPUT_VARIABLE file_ov - OUTPUT_STRIP_TRAILING_WHITESPACE - ) - - # Replace the name of the file in the output with a placeholder token - # (the string " _file_full_ ") so that just in case the path name of - # the file contains the word "text" or "executable" we are not fooled - # into thinking "the wrong thing" because the file name matches the - # other 'file' command output we are looking for... - # - string(REPLACE "${file_full}" " _file_full_ " file_ov "${file_ov}") - string(TOLOWER "${file_ov}" file_ov) - - #message(STATUS "file_ov='${file_ov}'") - if("${file_ov}" MATCHES "executable") - #message(STATUS "executable!") - if("${file_ov}" MATCHES "text") - #message(STATUS "but text, so *not* a binary executable!") - else() - set(${result_var} 1 PARENT_SCOPE) - return() - endif() - endif() - - # Also detect position independent executables on Linux, - # where "file" gives "shared object ... (uses shared libraries)" - if("${file_ov}" MATCHES "shared object.*\(uses shared libs\)") - set(${result_var} 1 PARENT_SCOPE) - return() - endif() - - # "file" version 5.22 does not print "(used shared libraries)" - # but uses "interpreter" - if("${file_ov}" MATCHES "shared object.*interpreter") - set(${result_var} 1 PARENT_SCOPE) - return() - endif() - - else() - message(STATUS "warning: No 'file' command, skipping execute_process...") - endif() - endif() -endfunction() - - -function(gp_item_default_embedded_path item default_embedded_path_var) - - # On Windows and Linux, "embed" prerequisites in the same directory - # as the executable by default: - # - set(path "@executable_path") - set(overridden 0) - - # On the Mac, relative to the executable depending on the type - # of the thing we are embedding: - # - if(APPLE) - # - # The assumption here is that all executables in the bundle will be - # in same-level-directories inside the bundle. The parent directory - # of an executable inside the bundle should be MacOS or a sibling of - # MacOS and all embedded paths returned from here will begin with - # "@executable_path/../" and will work from all executables in all - # such same-level-directories inside the bundle. - # - - # By default, embed things right next to the main bundle executable: - # - set(path "@executable_path/../../Contents/MacOS") - - # Embed .dylibs right next to the main bundle executable: - # - if(item MATCHES "\\.dylib$") - set(path "@executable_path/../MacOS") - set(overridden 1) - endif() - - # Embed frameworks in the embedded "Frameworks" directory (sibling of MacOS): - # - if(NOT overridden) - if(item MATCHES "[^/]+\\.framework/") - set(path "@executable_path/../Frameworks") - set(overridden 1) - endif() - endif() - endif() - - # Provide a hook so that projects can override the default embedded location - # of any given library by whatever logic they choose: - # - if(COMMAND gp_item_default_embedded_path_override) - gp_item_default_embedded_path_override("${item}" path) - endif() - - set(${default_embedded_path_var} "${path}" PARENT_SCOPE) -endfunction() - - -function(gp_resolve_item context item exepath dirs resolved_item_var) - set(resolved 0) - set(resolved_item "${item}") - - # Is it already resolved? - # - if(IS_ABSOLUTE "${resolved_item}" AND EXISTS "${resolved_item}") - set(resolved 1) - endif() - - if(NOT resolved) - if(item MATCHES "@executable_path") - # - # @executable_path references are assumed relative to exepath - # - string(REPLACE "@executable_path" "${exepath}" ri "${item}") - get_filename_component(ri "${ri}" ABSOLUTE) - - if(EXISTS "${ri}") - #message(STATUS "info: embedded item exists (${ri})") - set(resolved 1) - set(resolved_item "${ri}") - else() - message(STATUS "warning: embedded item does not exist '${ri}'") - endif() - endif() - endif() - - if(NOT resolved) - if(item MATCHES "@loader_path") - # - # @loader_path references are assumed relative to the - # PATH of the given "context" (presumably another library) - # - get_filename_component(contextpath "${context}" PATH) - string(REPLACE "@loader_path" "${contextpath}" ri "${item}") - get_filename_component(ri "${ri}" ABSOLUTE) - - if(EXISTS "${ri}") - #message(STATUS "info: embedded item exists (${ri})") - set(resolved 1) - set(resolved_item "${ri}") - else() - message(STATUS "warning: embedded item does not exist '${ri}'") - endif() - endif() - endif() - - if(NOT resolved) - if(item MATCHES "@rpath") - # - # @rpath references are relative to the paths built into the binaries with -rpath - # We handle this case like we do for other Unixes - # - string(REPLACE "@rpath/" "" norpath_item "${item}") - - set(ri "ri-NOTFOUND") - find_file(ri "${norpath_item}" ${exepath} ${dirs} NO_DEFAULT_PATH) - if(ri) - #message(STATUS "info: 'find_file' in exepath/dirs (${ri})") - set(resolved 1) - set(resolved_item "${ri}") - set(ri "ri-NOTFOUND") - endif() - - endif() - endif() - - if(NOT resolved) - set(ri "ri-NOTFOUND") - find_file(ri "${item}" ${exepath} ${dirs} NO_DEFAULT_PATH) - find_file(ri "${item}" ${exepath} ${dirs} /usr/lib) - if(ri) - #message(STATUS "info: 'find_file' in exepath/dirs (${ri})") - set(resolved 1) - set(resolved_item "${ri}") - set(ri "ri-NOTFOUND") - endif() - endif() - - if(NOT resolved) - if(item MATCHES "[^/]+\\.framework/") - set(fw "fw-NOTFOUND") - find_file(fw "${item}" - "~/Library/Frameworks" - "/Library/Frameworks" - "/System/Library/Frameworks" - ) - if(fw) - #message(STATUS "info: 'find_file' found framework (${fw})") - set(resolved 1) - set(resolved_item "${fw}") - set(fw "fw-NOTFOUND") - endif() - endif() - endif() - - # Using find_program on Windows will find dll files that are in the PATH. - # (Converting simple file names into full path names if found.) - # - if(WIN32 AND NOT UNIX) - if(NOT resolved) - set(ri "ri-NOTFOUND") - find_program(ri "${item}" PATHS "${exepath};${dirs}" NO_DEFAULT_PATH) - find_program(ri "${item}" PATHS "${exepath};${dirs}") - if(ri) - #message(STATUS "info: 'find_program' in exepath/dirs (${ri})") - set(resolved 1) - set(resolved_item "${ri}") - set(ri "ri-NOTFOUND") - endif() - endif() - endif() - - # Provide a hook so that projects can override item resolution - # by whatever logic they choose: - # - if(COMMAND gp_resolve_item_override) - gp_resolve_item_override("${context}" "${item}" "${exepath}" "${dirs}" resolved_item resolved) - endif() - - if(NOT resolved) - message(STATUS " -warning: cannot resolve item '${item}' - - possible problems: - need more directories? - need to use InstallRequiredSystemLibraries? - run in install tree instead of build tree? -") -# message(STATUS " -#****************************************************************************** -#warning: cannot resolve item '${item}' -# -# possible problems: -# need more directories? -# need to use InstallRequiredSystemLibraries? -# run in install tree instead of build tree? -# -# context='${context}' -# item='${item}' -# exepath='${exepath}' -# dirs='${dirs}' -# resolved_item_var='${resolved_item_var}' -#****************************************************************************** -#") - endif() - - set(${resolved_item_var} "${resolved_item}" PARENT_SCOPE) -endfunction() - - -function(gp_resolved_file_type original_file file exepath dirs type_var) - #message(STATUS "**") - - if(NOT IS_ABSOLUTE "${original_file}") - message(STATUS "warning: gp_resolved_file_type expects absolute full path for first arg original_file") - endif() - - set(is_embedded 0) - set(is_local 0) - set(is_system 0) - - set(resolved_file "${file}") - - if("${file}" MATCHES "^@(executable|loader)_path") - set(is_embedded 1) - endif() - - if(NOT is_embedded) - if(NOT IS_ABSOLUTE "${file}") - gp_resolve_item("${original_file}" "${file}" "${exepath}" "${dirs}" resolved_file) - endif() - - string(TOLOWER "${original_file}" original_lower) - string(TOLOWER "${resolved_file}" lower) - - if(UNIX) - if(resolved_file MATCHES "^(/lib/|/lib32/|/lib64/|/usr/lib/|/usr/lib32/|/usr/lib64/|/usr/X11R6/|/usr/bin/)") - set(is_system 1) - endif() - endif() - - if(APPLE) - if(resolved_file MATCHES "^(/System/Library/|/usr/lib/)") - set(is_system 1) - endif() - endif() - - if(WIN32) - string(TOLOWER "$ENV{SystemRoot}" sysroot) - string(REGEX REPLACE "\\\\" "/" sysroot "${sysroot}") - - string(TOLOWER "$ENV{windir}" windir) - string(REGEX REPLACE "\\\\" "/" windir "${windir}") - - if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)") - set(is_system 1) - endif() - - if(UNIX) - # if cygwin, we can get the properly formed windows paths from cygpath - find_program(CYGPATH_EXECUTABLE cygpath) - - if(CYGPATH_EXECUTABLE) - execute_process(COMMAND ${CYGPATH_EXECUTABLE} -W - OUTPUT_VARIABLE env_windir - OUTPUT_STRIP_TRAILING_WHITESPACE) - execute_process(COMMAND ${CYGPATH_EXECUTABLE} -S - OUTPUT_VARIABLE env_sysdir - OUTPUT_STRIP_TRAILING_WHITESPACE) - string(TOLOWER "${env_windir}" windir) - string(TOLOWER "${env_sysdir}" sysroot) - - if(lower MATCHES "^(${sysroot}/sys(tem|wow)|${windir}/sys(tem|wow)|(.*/)*msvc[^/]+dll)") - set(is_system 1) - endif() - endif() - endif() - endif() - - if(NOT is_system) - get_filename_component(original_path "${original_lower}" PATH) - get_filename_component(path "${lower}" PATH) - if("${original_path}" STREQUAL "${path}") - set(is_local 1) - else() - string(LENGTH "${original_path}/" original_length) - string(LENGTH "${lower}" path_length) - if(${path_length} GREATER ${original_length}) - string(SUBSTRING "${lower}" 0 ${original_length} path) - if("${original_path}/" STREQUAL "${path}") - set(is_embedded 1) - endif() - endif() - endif() - endif() - endif() - - # Return type string based on computed booleans: - # - set(type "other") - - if(is_system) - set(type "system") - elseif(is_embedded) - set(type "embedded") - elseif(is_local) - set(type "local") - endif() - - #message(STATUS "gp_resolved_file_type: '${file}' '${resolved_file}'") - #message(STATUS " type: '${type}'") - - if(NOT is_embedded) - if(NOT IS_ABSOLUTE "${resolved_file}") - if(lower MATCHES "^msvc[^/]+dll" AND is_system) - message(STATUS "info: non-absolute msvc file '${file}' returning type '${type}'") - else() - message(STATUS "warning: gp_resolved_file_type non-absolute file '${file}' returning type '${type}' -- possibly incorrect") - endif() - endif() - endif() - - # Provide a hook so that projects can override the decision on whether a - # library belongs to the system or not by whatever logic they choose: - # - if(COMMAND gp_resolved_file_type_override) - gp_resolved_file_type_override("${resolved_file}" type) - endif() - - set(${type_var} "${type}" PARENT_SCOPE) - - #message(STATUS "**") -endfunction() - - -function(gp_file_type original_file file type_var) - if(NOT IS_ABSOLUTE "${original_file}") - message(STATUS "warning: gp_file_type expects absolute full path for first arg original_file") - endif() - - get_filename_component(exepath "${original_file}" PATH) - - set(type "") - gp_resolved_file_type("${original_file}" "${file}" "${exepath}" "" type) - - set(${type_var} "${type}" PARENT_SCOPE) -endfunction() - - -function(get_prerequisites target prerequisites_var exclude_system recurse exepath dirs) - set(verbose 0) - set(eol_char "E") - - if(NOT IS_ABSOLUTE "${target}") - message("warning: target '${target}' is not absolute...") - endif() - - if(NOT EXISTS "${target}") - message("warning: target '${target}' does not exist...") - endif() - - set(gp_cmd_paths ${gp_cmd_paths} - "C:/Program Files/Microsoft Visual Studio 9.0/VC/bin" - "C:/Program Files (x86)/Microsoft Visual Studio 9.0/VC/bin" - "C:/Program Files/Microsoft Visual Studio 8/VC/BIN" - "C:/Program Files (x86)/Microsoft Visual Studio 8/VC/BIN" - "C:/Program Files/Microsoft Visual Studio .NET 2003/VC7/BIN" - "C:/Program Files (x86)/Microsoft Visual Studio .NET 2003/VC7/BIN" - "/usr/local/bin" - "/usr/bin" - ) - - # - # - # Try to choose the right tool by default. Caller can set gp_tool prior to - # calling this function to force using a different tool. - # - if("${gp_tool}" STREQUAL "") - set(gp_tool "ldd") - - if(APPLE) - set(gp_tool "otool") - endif() - - if(WIN32 AND NOT UNIX) # This is how to check for cygwin, har! - find_program(gp_dumpbin "dumpbin" PATHS ${gp_cmd_paths}) - if(gp_dumpbin) - set(gp_tool "dumpbin") - else() # Try harder. Maybe we're on MinGW - set(gp_tool "objdump") - endif() - endif() - endif() - - find_program(gp_cmd ${gp_tool} PATHS ${gp_cmd_paths}) - - if(NOT gp_cmd) - message(FATAL_ERROR "FATAL ERROR: could not find '${gp_tool}' - cannot analyze prerequisites!") - return() - endif() - - set(gp_tool_known 0) - - if("${gp_tool}" STREQUAL "ldd") - set(gp_cmd_args "") - set(gp_regex "^[\t ]*[^\t ]+ => ([^\t\(]+) .*${eol_char}$") - set(gp_regex_error "not found${eol_char}$") - set(gp_regex_fallback "^[\t ]*([^\t ]+) => ([^\t ]+).*${eol_char}$") - set(gp_regex_cmp_count 1) - set(gp_tool_known 1) - endif() - - if("${gp_tool}" STREQUAL "otool") - set(gp_cmd_args "-L") - set(gp_regex "^\t([^\t]+) \\(compatibility version ([0-9]+.[0-9]+.[0-9]+), current version ([0-9]+.[0-9]+.[0-9]+)\\)${eol_char}$") - set(gp_regex_error "") - set(gp_regex_fallback "") - set(gp_regex_cmp_count 3) - set(gp_tool_known 1) - endif() - - if("${gp_tool}" STREQUAL "dumpbin") - set(gp_cmd_args "/dependents") - set(gp_regex "^ ([^ ].*[Dd][Ll][Ll])${eol_char}$") - set(gp_regex_error "") - set(gp_regex_fallback "") - set(gp_regex_cmp_count 1) - set(gp_tool_known 1) - endif() - - if("${gp_tool}" STREQUAL "objdump") - set(gp_cmd_args "-p") - set(gp_regex "^\t*DLL Name: (.*\\.[Dd][Ll][Ll])${eol_char}$") - set(gp_regex_error "") - set(gp_regex_fallback "") - set(gp_regex_cmp_count 1) - set(gp_tool_known 1) - endif() - - if(NOT gp_tool_known) - message(STATUS "warning: gp_tool='${gp_tool}' is an unknown tool...") - message(STATUS "CMake function get_prerequisites needs more code to handle '${gp_tool}'") - message(STATUS "Valid gp_tool values are dumpbin, ldd, objdump and otool.") - return() - endif() - - - if("${gp_tool}" STREQUAL "dumpbin") - # When running dumpbin, it also needs the "Common7/IDE" directory in the - # PATH. It will already be in the PATH if being run from a Visual Studio - # command prompt. Add it to the PATH here in case we are running from a - # different command prompt. - # - get_filename_component(gp_cmd_dir "${gp_cmd}" PATH) - get_filename_component(gp_cmd_dlls_dir "${gp_cmd_dir}/../../Common7/IDE" ABSOLUTE) - # Use cmake paths as a user may have a PATH element ending with a backslash. - # This will escape the list delimiter and create havoc! - if(EXISTS "${gp_cmd_dlls_dir}") - # only add to the path if it is not already in the path - set(gp_found_cmd_dlls_dir 0) - file(TO_CMAKE_PATH "$ENV{PATH}" env_path) - foreach(gp_env_path_element ${env_path}) - if("${gp_env_path_element}" STREQUAL "${gp_cmd_dlls_dir}") - set(gp_found_cmd_dlls_dir 1) - endif() - endforeach() - - if(NOT gp_found_cmd_dlls_dir) - file(TO_NATIVE_PATH "${gp_cmd_dlls_dir}" gp_cmd_dlls_dir) - set(ENV{PATH} "$ENV{PATH};${gp_cmd_dlls_dir}") - endif() - endif() - endif() - # - # - - if("${gp_tool}" STREQUAL "ldd") - set(old_ld_env "$ENV{LD_LIBRARY_PATH}") - foreach(dir ${exepath} ${dirs}) - set(ENV{LD_LIBRARY_PATH} "${dir}:$ENV{LD_LIBRARY_PATH}") - endforeach() - endif() - - - # Track new prerequisites at each new level of recursion. Start with an - # empty list at each level: - # - set(unseen_prereqs) - - # Run gp_cmd on the target: - # - execute_process( - COMMAND ${gp_cmd} ${gp_cmd_args} ${target} - OUTPUT_VARIABLE gp_cmd_ov - ) - - if("${gp_tool}" STREQUAL "ldd") - set(ENV{LD_LIBRARY_PATH} "${old_ld_env}") - endif() - - if(verbose) - message(STATUS "") - message(STATUS "gp_cmd_ov='${gp_cmd_ov}'") - message(STATUS "") - endif() - - get_filename_component(target_dir "${target}" PATH) - - # Convert to a list of lines: - # - string(REGEX REPLACE ";" "\\\\;" candidates "${gp_cmd_ov}") - string(REGEX REPLACE "\n" "${eol_char};" candidates "${candidates}") - - # check for install id and remove it from list, since otool -L can include a - # reference to itself - set(gp_install_id) - if("${gp_tool}" STREQUAL "otool") - execute_process( - COMMAND otool -D ${target} - OUTPUT_VARIABLE gp_install_id_ov - ) - # second line is install name - string(REGEX REPLACE ".*:\n" "" gp_install_id "${gp_install_id_ov}") - if(gp_install_id) - # trim - string(REGEX MATCH "[^\n ].*[^\n ]" gp_install_id "${gp_install_id}") - #message("INSTALL ID is \"${gp_install_id}\"") - endif() - endif() - - # Analyze each line for file names that match the regular expression: - # - foreach(candidate ${candidates}) - if("${candidate}" MATCHES "${gp_regex}") - - # Extract information from each candidate: - if(gp_regex_error AND "${candidate}" MATCHES "${gp_regex_error}") - string(REGEX REPLACE "${gp_regex_fallback}" "\\1" raw_item "${candidate}") - else() - string(REGEX REPLACE "${gp_regex}" "\\1" raw_item "${candidate}") - endif() - - if(gp_regex_cmp_count GREATER 1) - string(REGEX REPLACE "${gp_regex}" "\\2" raw_compat_version "${candidate}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" compat_major_version "${raw_compat_version}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" compat_minor_version "${raw_compat_version}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" compat_patch_version "${raw_compat_version}") - endif() - - if(gp_regex_cmp_count GREATER 2) - string(REGEX REPLACE "${gp_regex}" "\\3" raw_current_version "${candidate}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\1" current_major_version "${raw_current_version}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\2" current_minor_version "${raw_current_version}") - string(REGEX REPLACE "^([0-9]+)\\.([0-9]+)\\.([0-9]+)$" "\\3" current_patch_version "${raw_current_version}") - endif() - - # Use the raw_item as the list entries returned by this function. Use the - # gp_resolve_item function to resolve it to an actual full path file if - # necessary. - # - set(item "${raw_item}") - - # Add each item unless it is excluded: - # - set(add_item 1) - - if("${item}" STREQUAL "${gp_install_id}") - set(add_item 0) - endif() - - if(add_item AND ${exclude_system}) - set(type "") - gp_resolved_file_type("${target}" "${item}" "${exepath}" "${dirs}" type) - - if("${type}" STREQUAL "system") - set(add_item 0) - endif() - endif() - - if(add_item) - list(LENGTH ${prerequisites_var} list_length_before_append) - gp_append_unique(${prerequisites_var} "${item}") - list(LENGTH ${prerequisites_var} list_length_after_append) - - if(${recurse}) - # If item was really added, this is the first time we have seen it. - # Add it to unseen_prereqs so that we can recursively add *its* - # prerequisites... - # - # But first: resolve its name to an absolute full path name such - # that the analysis tools can simply accept it as input. - # - if(NOT list_length_before_append EQUAL list_length_after_append) - gp_resolve_item("${target}" "${item}" "${exepath}" "${dirs}" resolved_item) - set(unseen_prereqs ${unseen_prereqs} "${resolved_item}") - endif() - endif() - endif() - else() - if(verbose) - message(STATUS "ignoring non-matching line: '${candidate}'") - endif() - endif() - endforeach() - - list(LENGTH ${prerequisites_var} prerequisites_var_length) - if(prerequisites_var_length GREATER 0) - list(SORT ${prerequisites_var}) - endif() - if(${recurse}) - set(more_inputs ${unseen_prereqs}) - foreach(input ${more_inputs}) - get_prerequisites("${input}" ${prerequisites_var} ${exclude_system} ${recurse} "${exepath}" "${dirs}") - endforeach() - endif() - - set(${prerequisites_var} ${${prerequisites_var}} PARENT_SCOPE) -endfunction() - - -function(list_prerequisites target) - if("${ARGV1}" STREQUAL "") - set(all 1) - else() - set(all "${ARGV1}") - endif() - - if("${ARGV2}" STREQUAL "") - set(exclude_system 0) - else() - set(exclude_system "${ARGV2}") - endif() - - if("${ARGV3}" STREQUAL "") - set(verbose 0) - else() - set(verbose "${ARGV3}") - endif() - - set(count 0) - set(count_str "") - set(print_count "${verbose}") - set(print_prerequisite_type "${verbose}") - set(print_target "${verbose}") - set(type_str "") - - get_filename_component(exepath "${target}" PATH) - - set(prereqs "") - get_prerequisites("${target}" prereqs ${exclude_system} ${all} "${exepath}" "") - - if(print_target) - message(STATUS "File '${target}' depends on:") - endif() - - foreach(d ${prereqs}) - math(EXPR count "${count} + 1") - - if(print_count) - set(count_str "${count}. ") - endif() - - if(print_prerequisite_type) - gp_file_type("${target}" "${d}" type) - set(type_str " (${type})") - endif() - - message(STATUS "${count_str}${d}${type_str}") - endforeach() -endfunction() - - -function(list_prerequisites_by_glob glob_arg glob_exp) - message(STATUS "=============================================================================") - message(STATUS "List prerequisites of executables matching ${glob_arg} '${glob_exp}'") - message(STATUS "") - file(${glob_arg} file_list ${glob_exp}) - foreach(f ${file_list}) - is_file_executable("${f}" is_f_executable) - if(is_f_executable) - message(STATUS "=============================================================================") - list_prerequisites("${f}" ${ARGN}) - message(STATUS "") - endif() - endforeach() -endfunction() diff --git a/cmake/MacOSXBundleInfo.plist.in b/cmake/MacOSXBundleInfo.plist.in index 0e3a43c6..9e663d31 100644 --- a/cmake/MacOSXBundleInfo.plist.in +++ b/cmake/MacOSXBundleInfo.plist.in @@ -2,10 +2,10 @@ - NSCameraUsageDescription - A Minecraft mod wants to access your camera. - NSMicrophoneUsageDescription - A Minecraft mod wants to access your microphone. + NSCameraUsageDescription + A Minecraft mod wants to access your camera. + NSMicrophoneUsageDescription + A Minecraft mod wants to access your microphone. NSPrincipalClass NSApplication NSHighResolutionCapable diff --git a/cmake/UseJava.cmake b/cmake/UseJava.cmake deleted file mode 100644 index 1a5ef107..00000000 --- a/cmake/UseJava.cmake +++ /dev/null @@ -1,881 +0,0 @@ -# - Use Module for Java -# This file provides functions for Java. It is assumed that FindJava.cmake -# has already been loaded. See FindJava.cmake for information on how to -# load Java into your CMake project. -# -# add_jar(TARGET_NAME SRC1 SRC2 .. SRCN RCS1 RCS2 .. RCSN) -# -# This command creates a .jar. It compiles the given source -# files (SRC) and adds the given resource files (RCS) to the jar file. -# If only resource files are given then just a jar file is created. -# -# Additional instructions: -# To add compile flags to the target you can set these flags with -# the following variable: -# -# set(CMAKE_JAVA_COMPILE_FLAGS -nowarn) -# -# To add a path or a jar file to the class path you can do this -# with the CMAKE_JAVA_INCLUDE_PATH variable. -# -# set(CMAKE_JAVA_INCLUDE_PATH /usr/share/java/shibboleet.jar) -# -# To use a different output name for the target you can set it with: -# -# set(CMAKE_JAVA_TARGET_OUTPUT_NAME shibboleet.jar) -# add_jar(foobar foobar.java) -# -# To use a different output directory than CMAKE_CURRENT_BINARY_DIR -# you can set it with: -# -# set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${PROJECT_BINARY_DIR}/bin) -# -# To define an entry point in your jar you can set it with: -# -# set(CMAKE_JAVA_JAR_ENTRY_POINT com/examples/MyProject/Main) -# -# To add a VERSION to the target output name you can set it using -# CMAKE_JAVA_TARGET_VERSION. This will create a jar file with the name -# shibboleet-1.0.0.jar and will create a symlink shibboleet.jar -# pointing to the jar with the version information. -# -# set(CMAKE_JAVA_TARGET_VERSION 1.2.0) -# add_jar(shibboleet shibbotleet.java) -# -# If the target is a JNI library, utilize the following commands to -# create a JNI symbolic link: -# -# set(CMAKE_JNI_TARGET TRUE) -# set(CMAKE_JAVA_TARGET_VERSION 1.2.0) -# add_jar(shibboleet shibbotleet.java) -# install_jar(shibboleet ${LIB_INSTALL_DIR}/shibboleet) -# install_jni_symlink(shibboleet ${JAVA_LIB_INSTALL_DIR}) -# -# If a single target needs to produce more than one jar from its -# java source code, to prevent the accumulation of duplicate class -# files in subsequent jars, set/reset CMAKE_JAR_CLASSES_PREFIX prior -# to calling the add_jar() function: -# -# set(CMAKE_JAR_CLASSES_PREFIX com/redhat/foo) -# add_jar(foo foo.java) -# -# set(CMAKE_JAR_CLASSES_PREFIX com/redhat/bar) -# add_jar(bar bar.java) -# -# Target Properties: -# The add_jar() functions sets some target properties. You can get these -# properties with the -# get_property(TARGET PROPERTY ) -# command. -# -# INSTALL_FILES The files which should be installed. This is used by -# install_jar(). -# JNI_SYMLINK The JNI symlink which should be installed. -# This is used by install_jni_symlink(). -# JAR_FILE The location of the jar file so that you can include -# it. -# CLASS_DIR The directory where the class files can be found. For -# example to use them with javah. -# -# find_jar( -# name | NAMES name1 [name2 ...] -# [PATHS path1 [path2 ... ENV var]] -# [VERSIONS version1 [version2]] -# [DOC "cache documentation string"] -# ) -# -# This command is used to find a full path to the named jar. A cache -# entry named by is created to stor the result of this command. If -# the full path to a jar is found the result is stored in the variable -# and the search will not repeated unless the variable is cleared. If -# nothing is found, the result will be -NOTFOUND, and the search -# will be attempted again next time find_jar is invoked with the same -# variable. -# The name of the full path to a file that is searched for is specified -# by the names listed after NAMES argument. Additional search locations -# can be specified after the PATHS argument. If you require special a -# version of a jar file you can specify it with the VERSIONS argument. -# The argument after DOC will be used for the documentation string in -# the cache. -# -# install_jar(TARGET_NAME DESTINATION) -# -# This command installs the TARGET_NAME files to the given DESTINATION. -# It should be called in the same scope as add_jar() or it will fail. -# -# install_jni_symlink(TARGET_NAME DESTINATION) -# -# This command installs the TARGET_NAME JNI symlinks to the given -# DESTINATION. It should be called in the same scope as add_jar() -# or it will fail. -# -# create_javadoc( -# PACKAGES pkg1 [pkg2 ...] -# [SOURCEPATH ] -# [CLASSPATH ] -# [INSTALLPATH ] -# [DOCTITLE "the documentation title"] -# [WINDOWTITLE "the title of the document"] -# [AUTHOR TRUE|FALSE] -# [USE TRUE|FALSE] -# [VERSION TRUE|FALSE] -# ) -# -# Create java documentation based on files or packages. For more -# details please read the javadoc manpage. -# -# There are two main signatures for create_javadoc. The first -# signature works with package names on a path with source files: -# -# Example: -# create_javadoc(my_example_doc -# PACKAGES com.exmaple.foo com.example.bar -# SOURCEPATH "${CMAKE_CURRENT_SOURCE_DIR}" -# CLASSPATH ${CMAKE_JAVA_INCLUDE_PATH} -# WINDOWTITLE "My example" -# DOCTITLE "

My example

" -# AUTHOR TRUE -# USE TRUE -# VERSION TRUE -# ) -# -# The second signature for create_javadoc works on a given list of -# files. -# -# create_javadoc( -# FILES file1 [file2 ...] -# [CLASSPATH ] -# [INSTALLPATH ] -# [DOCTITLE "the documentation title"] -# [WINDOWTITLE "the title of the document"] -# [AUTHOR TRUE|FALSE] -# [USE TRUE|FALSE] -# [VERSION TRUE|FALSE] -# ) -# -# Example: -# create_javadoc(my_example_doc -# FILES ${example_SRCS} -# CLASSPATH ${CMAKE_JAVA_INCLUDE_PATH} -# WINDOWTITLE "My example" -# DOCTITLE "

My example

" -# AUTHOR TRUE -# USE TRUE -# VERSION TRUE -# ) -# -# Both signatures share most of the options. These options are the -# same as what you can find in the javadoc manpage. Please look at -# the manpage for CLASSPATH, DOCTITLE, WINDOWTITLE, AUTHOR, USE and -# VERSION. -# -# The documentation will be by default installed to -# -# ${CMAKE_INSTALL_PREFIX}/share/javadoc/ -# -# if you don't set the INSTALLPATH. -# - -#============================================================================= -# Copyright 2010-2011 Andreas schneider -# Copyright 2010 Ben Boeckel -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -function (__java_copy_file src dest comment) - add_custom_command( - OUTPUT ${dest} - COMMAND cmake -E copy_if_different - ARGS ${src} - ${dest} - DEPENDS ${src} - COMMENT ${comment}) -endfunction (__java_copy_file src dest comment) - -# define helper scripts -set(_JAVA_CLASS_FILELIST_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/UseJavaClassFilelist.cmake) -set(_JAVA_SYMLINK_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/UseJavaSymlinks.cmake) - -function(add_jar _TARGET_NAME) - set(_JAVA_SOURCE_FILES ${ARGN}) - - if (NOT DEFINED CMAKE_JAVA_TARGET_OUTPUT_DIR) - set(CMAKE_JAVA_TARGET_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) - endif(NOT DEFINED CMAKE_JAVA_TARGET_OUTPUT_DIR) - - if (CMAKE_JAVA_JAR_ENTRY_POINT) - set(_ENTRY_POINT_OPTION e) - set(_ENTRY_POINT_VALUE ${CMAKE_JAVA_JAR_ENTRY_POINT}) - endif (CMAKE_JAVA_JAR_ENTRY_POINT) - - if (LIBRARY_OUTPUT_PATH) - set(CMAKE_JAVA_LIBRARY_OUTPUT_PATH ${LIBRARY_OUTPUT_PATH}) - else (LIBRARY_OUTPUT_PATH) - set(CMAKE_JAVA_LIBRARY_OUTPUT_PATH ${CMAKE_JAVA_TARGET_OUTPUT_DIR}) - endif (LIBRARY_OUTPUT_PATH) - - set(CMAKE_JAVA_INCLUDE_PATH - ${CMAKE_JAVA_INCLUDE_PATH} - ${CMAKE_CURRENT_SOURCE_DIR} - ${CMAKE_JAVA_OBJECT_OUTPUT_PATH} - ${CMAKE_JAVA_LIBRARY_OUTPUT_PATH} - ) - - if (WIN32 AND NOT CYGWIN AND NOT CMAKE_CROSSCOMPILING) - set(CMAKE_JAVA_INCLUDE_FLAG_SEP ";") - else () - set(CMAKE_JAVA_INCLUDE_FLAG_SEP ":") - endif() - - foreach (JAVA_INCLUDE_DIR ${CMAKE_JAVA_INCLUDE_PATH}) - set(CMAKE_JAVA_INCLUDE_PATH_FINAL "${CMAKE_JAVA_INCLUDE_PATH_FINAL}${CMAKE_JAVA_INCLUDE_FLAG_SEP}${JAVA_INCLUDE_DIR}") - endforeach(JAVA_INCLUDE_DIR) - - set(CMAKE_JAVA_CLASS_OUTPUT_PATH "${CMAKE_JAVA_TARGET_OUTPUT_DIR}${CMAKE_FILES_DIRECTORY}/${_TARGET_NAME}.dir") - - set(_JAVA_TARGET_OUTPUT_NAME "${_TARGET_NAME}.jar") - if (CMAKE_JAVA_TARGET_OUTPUT_NAME AND CMAKE_JAVA_TARGET_VERSION) - set(_JAVA_TARGET_OUTPUT_NAME "${CMAKE_JAVA_TARGET_OUTPUT_NAME}-${CMAKE_JAVA_TARGET_VERSION}.jar") - set(_JAVA_TARGET_OUTPUT_LINK "${CMAKE_JAVA_TARGET_OUTPUT_NAME}.jar") - elseif (CMAKE_JAVA_TARGET_VERSION) - set(_JAVA_TARGET_OUTPUT_NAME "${_TARGET_NAME}-${CMAKE_JAVA_TARGET_VERSION}.jar") - set(_JAVA_TARGET_OUTPUT_LINK "${_TARGET_NAME}.jar") - elseif (CMAKE_JAVA_TARGET_OUTPUT_NAME) - set(_JAVA_TARGET_OUTPUT_NAME "${CMAKE_JAVA_TARGET_OUTPUT_NAME}.jar") - endif (CMAKE_JAVA_TARGET_OUTPUT_NAME AND CMAKE_JAVA_TARGET_VERSION) - # reset - set(CMAKE_JAVA_TARGET_OUTPUT_NAME) - - set(_JAVA_CLASS_FILES) - set(_JAVA_COMPILE_FILES) - set(_JAVA_DEPENDS) - set(_JAVA_RESOURCE_FILES) - foreach(_JAVA_SOURCE_FILE ${_JAVA_SOURCE_FILES}) - get_filename_component(_JAVA_EXT ${_JAVA_SOURCE_FILE} EXT) - get_filename_component(_JAVA_FILE ${_JAVA_SOURCE_FILE} NAME_WE) - get_filename_component(_JAVA_PATH ${_JAVA_SOURCE_FILE} PATH) - get_filename_component(_JAVA_FULL ${_JAVA_SOURCE_FILE} ABSOLUTE) - - file(RELATIVE_PATH _JAVA_REL_BINARY_PATH ${CMAKE_JAVA_TARGET_OUTPUT_DIR} ${_JAVA_FULL}) - file(RELATIVE_PATH _JAVA_REL_SOURCE_PATH ${CMAKE_CURRENT_SOURCE_DIR} ${_JAVA_FULL}) - string(LENGTH ${_JAVA_REL_BINARY_PATH} _BIN_LEN) - string(LENGTH ${_JAVA_REL_SOURCE_PATH} _SRC_LEN) - if (${_BIN_LEN} LESS ${_SRC_LEN}) - set(_JAVA_REL_PATH ${_JAVA_REL_BINARY_PATH}) - else (${_BIN_LEN} LESS ${_SRC_LEN}) - set(_JAVA_REL_PATH ${_JAVA_REL_SOURCE_PATH}) - endif (${_BIN_LEN} LESS ${_SRC_LEN}) - get_filename_component(_JAVA_REL_PATH ${_JAVA_REL_PATH} PATH) - - if (_JAVA_EXT MATCHES ".java") - list(APPEND _JAVA_COMPILE_FILES ${_JAVA_SOURCE_FILE}) - set(_JAVA_CLASS_FILE "${CMAKE_JAVA_CLASS_OUTPUT_PATH}/${_JAVA_REL_PATH}/${_JAVA_FILE}.class") - set(_JAVA_CLASS_FILES ${_JAVA_CLASS_FILES} ${_JAVA_CLASS_FILE}) - - elseif (_JAVA_EXT MATCHES ".jar" - OR _JAVA_EXT MATCHES ".war" - OR _JAVA_EXT MATCHES ".ear" - OR _JAVA_EXT MATCHES ".sar") - list(APPEND CMAKE_JAVA_INCLUDE_PATH ${_JAVA_SOURCE_FILE}) - - elseif (_JAVA_EXT STREQUAL "") - list(APPEND CMAKE_JAVA_INCLUDE_PATH ${JAVA_JAR_TARGET_${_JAVA_SOURCE_FILE}} ${JAVA_JAR_TARGET_${_JAVA_SOURCE_FILE}_CLASSPATH}) - list(APPEND _JAVA_DEPENDS ${JAVA_JAR_TARGET_${_JAVA_SOURCE_FILE}}) - - else (_JAVA_EXT MATCHES ".java") - __java_copy_file(${CMAKE_CURRENT_SOURCE_DIR}/${_JAVA_SOURCE_FILE} - ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/${_JAVA_SOURCE_FILE} - "Copying ${_JAVA_SOURCE_FILE} to the build directory") - list(APPEND _JAVA_RESOURCE_FILES ${_JAVA_SOURCE_FILE}) - endif (_JAVA_EXT MATCHES ".java") - endforeach(_JAVA_SOURCE_FILE) - - # create an empty java_class_filelist - if (NOT EXISTS ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist) - file(WRITE ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist "") - endif() - - if (_JAVA_COMPILE_FILES) - # Compile the java files and create a list of class files - add_custom_command( - # NOTE: this command generates an artificial dependency file - OUTPUT ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_compiled_${_TARGET_NAME} - COMMAND ${Java_JAVAC_EXECUTABLE} - ${CMAKE_JAVA_COMPILE_FLAGS} - -classpath "${CMAKE_JAVA_INCLUDE_PATH_FINAL}" - -d ${CMAKE_JAVA_CLASS_OUTPUT_PATH} - ${_JAVA_COMPILE_FILES} - COMMAND ${CMAKE_COMMAND} -E touch ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_compiled_${_TARGET_NAME} - DEPENDS ${_JAVA_COMPILE_FILES} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - COMMENT "Building Java objects for ${_TARGET_NAME}.jar" - ) - add_custom_command( - OUTPUT ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist - COMMAND ${CMAKE_COMMAND} - -DCMAKE_JAVA_CLASS_OUTPUT_PATH=${CMAKE_JAVA_CLASS_OUTPUT_PATH} - -DCMAKE_JAR_CLASSES_PREFIX="${CMAKE_JAR_CLASSES_PREFIX}" - -P ${_JAVA_CLASS_FILELIST_SCRIPT} - DEPENDS ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_compiled_${_TARGET_NAME} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) - endif (_JAVA_COMPILE_FILES) - - # create the jar file - set(_JAVA_JAR_OUTPUT_PATH - ${CMAKE_JAVA_TARGET_OUTPUT_DIR}/${_JAVA_TARGET_OUTPUT_NAME}) - if (CMAKE_JNI_TARGET) - add_custom_command( - OUTPUT ${_JAVA_JAR_OUTPUT_PATH} - COMMAND ${Java_JAR_EXECUTABLE} - -cf${_ENTRY_POINT_OPTION} ${_JAVA_JAR_OUTPUT_PATH} ${_ENTRY_POINT_VALUE} - ${_JAVA_RESOURCE_FILES} @java_class_filelist - COMMAND ${CMAKE_COMMAND} - -D_JAVA_TARGET_DIR=${CMAKE_JAVA_TARGET_OUTPUT_DIR} - -D_JAVA_TARGET_OUTPUT_NAME=${_JAVA_TARGET_OUTPUT_NAME} - -D_JAVA_TARGET_OUTPUT_LINK=${_JAVA_TARGET_OUTPUT_LINK} - -P ${_JAVA_SYMLINK_SCRIPT} - COMMAND ${CMAKE_COMMAND} - -D_JAVA_TARGET_DIR=${CMAKE_JAVA_TARGET_OUTPUT_DIR} - -D_JAVA_TARGET_OUTPUT_NAME=${_JAVA_JAR_OUTPUT_PATH} - -D_JAVA_TARGET_OUTPUT_LINK=${_JAVA_TARGET_OUTPUT_LINK} - -P ${_JAVA_SYMLINK_SCRIPT} - DEPENDS ${_JAVA_RESOURCE_FILES} ${_JAVA_DEPENDS} ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist - WORKING_DIRECTORY ${CMAKE_JAVA_CLASS_OUTPUT_PATH} - COMMENT "Creating Java archive ${_JAVA_TARGET_OUTPUT_NAME}" - ) - else () - add_custom_command( - OUTPUT ${_JAVA_JAR_OUTPUT_PATH} - COMMAND ${Java_JAR_EXECUTABLE} - -cf${_ENTRY_POINT_OPTION} ${_JAVA_JAR_OUTPUT_PATH} ${_ENTRY_POINT_VALUE} - ${_JAVA_RESOURCE_FILES} @java_class_filelist - COMMAND ${CMAKE_COMMAND} - -D_JAVA_TARGET_DIR=${CMAKE_JAVA_TARGET_OUTPUT_DIR} - -D_JAVA_TARGET_OUTPUT_NAME=${_JAVA_TARGET_OUTPUT_NAME} - -D_JAVA_TARGET_OUTPUT_LINK=${_JAVA_TARGET_OUTPUT_LINK} - -P ${_JAVA_SYMLINK_SCRIPT} - WORKING_DIRECTORY ${CMAKE_JAVA_CLASS_OUTPUT_PATH} - DEPENDS ${_JAVA_RESOURCE_FILES} ${_JAVA_DEPENDS} ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist - COMMENT "Creating Java archive ${_JAVA_TARGET_OUTPUT_NAME}" - ) - endif (CMAKE_JNI_TARGET) - - # Add the target and make sure we have the latest resource files. - add_custom_target(${_TARGET_NAME} ALL DEPENDS ${_JAVA_JAR_OUTPUT_PATH}) - - set_property( - TARGET - ${_TARGET_NAME} - PROPERTY - INSTALL_FILES - ${_JAVA_JAR_OUTPUT_PATH} - ) - - if (_JAVA_TARGET_OUTPUT_LINK) - set_property( - TARGET - ${_TARGET_NAME} - PROPERTY - INSTALL_FILES - ${_JAVA_JAR_OUTPUT_PATH} - ${CMAKE_JAVA_TARGET_OUTPUT_DIR}/${_JAVA_TARGET_OUTPUT_LINK} - ) - - if (CMAKE_JNI_TARGET) - set_property( - TARGET - ${_TARGET_NAME} - PROPERTY - JNI_SYMLINK - ${CMAKE_JAVA_TARGET_OUTPUT_DIR}/${_JAVA_TARGET_OUTPUT_LINK} - ) - endif (CMAKE_JNI_TARGET) - endif (_JAVA_TARGET_OUTPUT_LINK) - - set_property( - TARGET - ${_TARGET_NAME} - PROPERTY - JAR_FILE - ${_JAVA_JAR_OUTPUT_PATH} - ) - - set_property( - TARGET - ${_TARGET_NAME} - PROPERTY - CLASSDIR - ${CMAKE_JAVA_CLASS_OUTPUT_PATH} - ) - -endfunction(add_jar) - -function(INSTALL_JAR _TARGET_NAME _DESTINATION) - get_property(__FILES - TARGET - ${_TARGET_NAME} - PROPERTY - INSTALL_FILES - ) - - if (__FILES) - install( - FILES - ${__FILES} - DESTINATION - ${_DESTINATION} - ) - else (__FILES) - message(SEND_ERROR "The target ${_TARGET_NAME} is not known in this scope.") - endif (__FILES) -endfunction(INSTALL_JAR _TARGET_NAME _DESTINATION) - -function(INSTALL_JNI_SYMLINK _TARGET_NAME _DESTINATION) - get_property(__SYMLINK - TARGET - ${_TARGET_NAME} - PROPERTY - JNI_SYMLINK - ) - - if (__SYMLINK) - install( - FILES - ${__SYMLINK} - DESTINATION - ${_DESTINATION} - ) - else (__SYMLINK) - message(SEND_ERROR "The target ${_TARGET_NAME} is not known in this scope.") - endif (__SYMLINK) -endfunction(INSTALL_JNI_SYMLINK _TARGET_NAME _DESTINATION) - -function (find_jar VARIABLE) - set(_jar_names) - set(_jar_files) - set(_jar_versions) - set(_jar_paths - /usr/share/java/ - /usr/local/share/java/ - ${Java_JAR_PATHS}) - set(_jar_doc "NOTSET") - - set(_state "name") - - foreach (arg ${ARGN}) - if (${_state} STREQUAL "name") - if (${arg} STREQUAL "VERSIONS") - set(_state "versions") - elseif (${arg} STREQUAL "NAMES") - set(_state "names") - elseif (${arg} STREQUAL "PATHS") - set(_state "paths") - elseif (${arg} STREQUAL "DOC") - set(_state "doc") - else (${arg} STREQUAL "NAMES") - set(_jar_names ${arg}) - if (_jar_doc STREQUAL "NOTSET") - set(_jar_doc "Finding ${arg} jar") - endif (_jar_doc STREQUAL "NOTSET") - endif (${arg} STREQUAL "VERSIONS") - elseif (${_state} STREQUAL "versions") - if (${arg} STREQUAL "NAMES") - set(_state "names") - elseif (${arg} STREQUAL "PATHS") - set(_state "paths") - elseif (${arg} STREQUAL "DOC") - set(_state "doc") - else (${arg} STREQUAL "NAMES") - set(_jar_versions ${_jar_versions} ${arg}) - endif (${arg} STREQUAL "NAMES") - elseif (${_state} STREQUAL "names") - if (${arg} STREQUAL "VERSIONS") - set(_state "versions") - elseif (${arg} STREQUAL "PATHS") - set(_state "paths") - elseif (${arg} STREQUAL "DOC") - set(_state "doc") - else (${arg} STREQUAL "VERSIONS") - set(_jar_names ${_jar_names} ${arg}) - if (_jar_doc STREQUAL "NOTSET") - set(_jar_doc "Finding ${arg} jar") - endif (_jar_doc STREQUAL "NOTSET") - endif (${arg} STREQUAL "VERSIONS") - elseif (${_state} STREQUAL "paths") - if (${arg} STREQUAL "VERSIONS") - set(_state "versions") - elseif (${arg} STREQUAL "NAMES") - set(_state "names") - elseif (${arg} STREQUAL "DOC") - set(_state "doc") - else (${arg} STREQUAL "VERSIONS") - set(_jar_paths ${_jar_paths} ${arg}) - endif (${arg} STREQUAL "VERSIONS") - elseif (${_state} STREQUAL "doc") - if (${arg} STREQUAL "VERSIONS") - set(_state "versions") - elseif (${arg} STREQUAL "NAMES") - set(_state "names") - elseif (${arg} STREQUAL "PATHS") - set(_state "paths") - else (${arg} STREQUAL "VERSIONS") - set(_jar_doc ${arg}) - endif (${arg} STREQUAL "VERSIONS") - endif (${_state} STREQUAL "name") - endforeach (arg ${ARGN}) - - if (NOT _jar_names) - message(FATAL_ERROR "find_jar: No name to search for given") - endif (NOT _jar_names) - - foreach (jar_name ${_jar_names}) - foreach (version ${_jar_versions}) - set(_jar_files ${_jar_files} ${jar_name}-${version}.jar) - endforeach (version ${_jar_versions}) - set(_jar_files ${_jar_files} ${jar_name}.jar) - endforeach (jar_name ${_jar_names}) - - find_file(${VARIABLE} - NAMES ${_jar_files} - PATHS ${_jar_paths} - DOC ${_jar_doc} - NO_DEFAULT_PATH) -endfunction (find_jar VARIABLE) - -function(create_javadoc _target) - set(_javadoc_packages) - set(_javadoc_files) - set(_javadoc_sourcepath) - set(_javadoc_classpath) - set(_javadoc_installpath "${CMAKE_INSTALL_PREFIX}/share/javadoc") - set(_javadoc_doctitle) - set(_javadoc_windowtitle) - set(_javadoc_author FALSE) - set(_javadoc_version FALSE) - set(_javadoc_use FALSE) - - set(_state "package") - - foreach (arg ${ARGN}) - if (${_state} STREQUAL "package") - if (${arg} STREQUAL "PACKAGES") - set(_state "packages") - elseif (${arg} STREQUAL "FILES") - set(_state "files") - elseif (${arg} STREQUAL "SOURCEPATH") - set(_state "sourcepath") - elseif (${arg} STREQUAL "CLASSPATH") - set(_state "classpath") - elseif (${arg} STREQUAL "INSTALLPATH") - set(_state "installpath") - elseif (${arg} STREQUAL "DOCTITLE") - set(_state "doctitle") - elseif (${arg} STREQUAL "WINDOWTITLE") - set(_state "windowtitle") - elseif (${arg} STREQUAL "AUTHOR") - set(_state "author") - elseif (${arg} STREQUAL "USE") - set(_state "use") - elseif (${arg} STREQUAL "VERSION") - set(_state "version") - else () - set(_javadoc_packages ${arg}) - set(_state "packages") - endif () - elseif (${_state} STREQUAL "packages") - if (${arg} STREQUAL "FILES") - set(_state "files") - elseif (${arg} STREQUAL "SOURCEPATH") - set(_state "sourcepath") - elseif (${arg} STREQUAL "CLASSPATH") - set(_state "classpath") - elseif (${arg} STREQUAL "INSTALLPATH") - set(_state "installpath") - elseif (${arg} STREQUAL "DOCTITLE") - set(_state "doctitle") - elseif (${arg} STREQUAL "WINDOWTITLE") - set(_state "windowtitle") - elseif (${arg} STREQUAL "AUTHOR") - set(_state "author") - elseif (${arg} STREQUAL "USE") - set(_state "use") - elseif (${arg} STREQUAL "VERSION") - set(_state "version") - else () - list(APPEND _javadoc_packages ${arg}) - endif () - elseif (${_state} STREQUAL "files") - if (${arg} STREQUAL "PACKAGES") - set(_state "packages") - elseif (${arg} STREQUAL "SOURCEPATH") - set(_state "sourcepath") - elseif (${arg} STREQUAL "CLASSPATH") - set(_state "classpath") - elseif (${arg} STREQUAL "INSTALLPATH") - set(_state "installpath") - elseif (${arg} STREQUAL "DOCTITLE") - set(_state "doctitle") - elseif (${arg} STREQUAL "WINDOWTITLE") - set(_state "windowtitle") - elseif (${arg} STREQUAL "AUTHOR") - set(_state "author") - elseif (${arg} STREQUAL "USE") - set(_state "use") - elseif (${arg} STREQUAL "VERSION") - set(_state "version") - else () - list(APPEND _javadoc_files ${arg}) - endif () - elseif (${_state} STREQUAL "sourcepath") - if (${arg} STREQUAL "PACKAGES") - set(_state "packages") - elseif (${arg} STREQUAL "FILES") - set(_state "files") - elseif (${arg} STREQUAL "CLASSPATH") - set(_state "classpath") - elseif (${arg} STREQUAL "INSTALLPATH") - set(_state "installpath") - elseif (${arg} STREQUAL "DOCTITLE") - set(_state "doctitle") - elseif (${arg} STREQUAL "WINDOWTITLE") - set(_state "windowtitle") - elseif (${arg} STREQUAL "AUTHOR") - set(_state "author") - elseif (${arg} STREQUAL "USE") - set(_state "use") - elseif (${arg} STREQUAL "VERSION") - set(_state "version") - else () - list(APPEND _javadoc_sourcepath ${arg}) - endif () - elseif (${_state} STREQUAL "classpath") - if (${arg} STREQUAL "PACKAGES") - set(_state "packages") - elseif (${arg} STREQUAL "FILES") - set(_state "files") - elseif (${arg} STREQUAL "SOURCEPATH") - set(_state "sourcepath") - elseif (${arg} STREQUAL "INSTALLPATH") - set(_state "installpath") - elseif (${arg} STREQUAL "DOCTITLE") - set(_state "doctitle") - elseif (${arg} STREQUAL "WINDOWTITLE") - set(_state "windowtitle") - elseif (${arg} STREQUAL "AUTHOR") - set(_state "author") - elseif (${arg} STREQUAL "USE") - set(_state "use") - elseif (${arg} STREQUAL "VERSION") - set(_state "version") - else () - list(APPEND _javadoc_classpath ${arg}) - endif () - elseif (${_state} STREQUAL "installpath") - if (${arg} STREQUAL "PACKAGES") - set(_state "packages") - elseif (${arg} STREQUAL "FILES") - set(_state "files") - elseif (${arg} STREQUAL "SOURCEPATH") - set(_state "sourcepath") - elseif (${arg} STREQUAL "DOCTITLE") - set(_state "doctitle") - elseif (${arg} STREQUAL "WINDOWTITLE") - set(_state "windowtitle") - elseif (${arg} STREQUAL "AUTHOR") - set(_state "author") - elseif (${arg} STREQUAL "USE") - set(_state "use") - elseif (${arg} STREQUAL "VERSION") - set(_state "version") - else () - set(_javadoc_installpath ${arg}) - endif () - elseif (${_state} STREQUAL "doctitle") - if (${arg} STREQUAL "PACKAGES") - set(_state "packages") - elseif (${arg} STREQUAL "FILES") - set(_state "files") - elseif (${arg} STREQUAL "SOURCEPATH") - set(_state "sourcepath") - elseif (${arg} STREQUAL "INSTALLPATH") - set(_state "installpath") - elseif (${arg} STREQUAL "CLASSPATH") - set(_state "classpath") - elseif (${arg} STREQUAL "WINDOWTITLE") - set(_state "windowtitle") - elseif (${arg} STREQUAL "AUTHOR") - set(_state "author") - elseif (${arg} STREQUAL "USE") - set(_state "use") - elseif (${arg} STREQUAL "VERSION") - set(_state "version") - else () - set(_javadoc_doctitle ${arg}) - endif () - elseif (${_state} STREQUAL "windowtitle") - if (${arg} STREQUAL "PACKAGES") - set(_state "packages") - elseif (${arg} STREQUAL "FILES") - set(_state "files") - elseif (${arg} STREQUAL "SOURCEPATH") - set(_state "sourcepath") - elseif (${arg} STREQUAL "CLASSPATH") - set(_state "classpath") - elseif (${arg} STREQUAL "INSTALLPATH") - set(_state "installpath") - elseif (${arg} STREQUAL "DOCTITLE") - set(_state "doctitle") - elseif (${arg} STREQUAL "AUTHOR") - set(_state "author") - elseif (${arg} STREQUAL "USE") - set(_state "use") - elseif (${arg} STREQUAL "VERSION") - set(_state "version") - else () - set(_javadoc_windowtitle ${arg}) - endif () - elseif (${_state} STREQUAL "author") - if (${arg} STREQUAL "PACKAGES") - set(_state "packages") - elseif (${arg} STREQUAL "FILES") - set(_state "files") - elseif (${arg} STREQUAL "SOURCEPATH") - set(_state "sourcepath") - elseif (${arg} STREQUAL "CLASSPATH") - set(_state "classpath") - elseif (${arg} STREQUAL "INSTALLPATH") - set(_state "installpath") - elseif (${arg} STREQUAL "DOCTITLE") - set(_state "doctitle") - elseif (${arg} STREQUAL "WINDOWTITLE") - set(_state "windowtitle") - elseif (${arg} STREQUAL "AUTHOR") - set(_state "author") - elseif (${arg} STREQUAL "USE") - set(_state "use") - elseif (${arg} STREQUAL "VERSION") - set(_state "version") - else () - set(_javadoc_author ${arg}) - endif () - elseif (${_state} STREQUAL "use") - if (${arg} STREQUAL "PACKAGES") - set(_state "packages") - elseif (${arg} STREQUAL "FILES") - set(_state "files") - elseif (${arg} STREQUAL "SOURCEPATH") - set(_state "sourcepath") - elseif (${arg} STREQUAL "CLASSPATH") - set(_state "classpath") - elseif (${arg} STREQUAL "INSTALLPATH") - set(_state "installpath") - elseif (${arg} STREQUAL "DOCTITLE") - set(_state "doctitle") - elseif (${arg} STREQUAL "WINDOWTITLE") - set(_state "windowtitle") - elseif (${arg} STREQUAL "AUTHOR") - set(_state "author") - elseif (${arg} STREQUAL "USE") - set(_state "use") - elseif (${arg} STREQUAL "VERSION") - set(_state "version") - else () - set(_javadoc_use ${arg}) - endif () - elseif (${_state} STREQUAL "version") - if (${arg} STREQUAL "PACKAGES") - set(_state "packages") - elseif (${arg} STREQUAL "FILES") - set(_state "files") - elseif (${arg} STREQUAL "SOURCEPATH") - set(_state "sourcepath") - elseif (${arg} STREQUAL "CLASSPATH") - set(_state "classpath") - elseif (${arg} STREQUAL "INSTALLPATH") - set(_state "installpath") - elseif (${arg} STREQUAL "DOCTITLE") - set(_state "doctitle") - elseif (${arg} STREQUAL "WINDOWTITLE") - set(_state "windowtitle") - elseif (${arg} STREQUAL "AUTHOR") - set(_state "author") - elseif (${arg} STREQUAL "USE") - set(_state "use") - elseif (${arg} STREQUAL "VERSION") - set(_state "version") - else () - set(_javadoc_version ${arg}) - endif () - endif (${_state} STREQUAL "package") - endforeach (arg ${ARGN}) - - set(_javadoc_builddir ${CMAKE_CURRENT_BINARY_DIR}/javadoc/${_target}) - set(_javadoc_options -d ${_javadoc_builddir}) - - if (_javadoc_sourcepath) - set(_start TRUE) - foreach(_path ${_javadoc_sourcepath}) - if (_start) - set(_sourcepath ${_path}) - set(_start FALSE) - else (_start) - set(_sourcepath ${_sourcepath}:${_path}) - endif (_start) - endforeach(_path ${_javadoc_sourcepath}) - set(_javadoc_options ${_javadoc_options} -sourcepath ${_sourcepath}) - endif (_javadoc_sourcepath) - - if (_javadoc_classpath) - set(_start TRUE) - foreach(_path ${_javadoc_classpath}) - if (_start) - set(_classpath ${_path}) - set(_start FALSE) - else (_start) - set(_classpath ${_classpath}:${_path}) - endif (_start) - endforeach(_path ${_javadoc_classpath}) - set(_javadoc_options ${_javadoc_options} -classpath "${_classpath}") - endif (_javadoc_classpath) - - if (_javadoc_doctitle) - set(_javadoc_options ${_javadoc_options} -doctitle '${_javadoc_doctitle}') - endif (_javadoc_doctitle) - - if (_javadoc_windowtitle) - set(_javadoc_options ${_javadoc_options} -windowtitle '${_javadoc_windowtitle}') - endif (_javadoc_windowtitle) - - if (_javadoc_author) - set(_javadoc_options ${_javadoc_options} -author) - endif (_javadoc_author) - - if (_javadoc_use) - set(_javadoc_options ${_javadoc_options} -use) - endif (_javadoc_use) - - if (_javadoc_version) - set(_javadoc_options ${_javadoc_options} -version) - endif (_javadoc_version) - - add_custom_target(${_target}_javadoc ALL - COMMAND ${Java_JAVADOC_EXECUTABLE} ${_javadoc_options} - ${_javadoc_files} - ${_javadoc_packages} - WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} - ) - - install( - DIRECTORY ${_javadoc_builddir} - DESTINATION ${_javadoc_installpath} - ) -endfunction(create_javadoc) diff --git a/cmake/UseJavaClassFilelist.cmake b/cmake/UseJavaClassFilelist.cmake deleted file mode 100644 index c842bf71..00000000 --- a/cmake/UseJavaClassFilelist.cmake +++ /dev/null @@ -1,52 +0,0 @@ -# -# This script create a list of compiled Java class files to be added to a -# jar file. This avoids including cmake files which get created in the -# binary directory. -# - -#============================================================================= -# Copyright 2010-2011 Andreas schneider -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -if (CMAKE_JAVA_CLASS_OUTPUT_PATH) - if (EXISTS "${CMAKE_JAVA_CLASS_OUTPUT_PATH}") - - set(_JAVA_GLOBBED_FILES) - if (CMAKE_JAR_CLASSES_PREFIX) - foreach(JAR_CLASS_PREFIX ${CMAKE_JAR_CLASSES_PREFIX}) - message(STATUS "JAR_CLASS_PREFIX: ${JAR_CLASS_PREFIX}") - - file(GLOB_RECURSE _JAVA_GLOBBED_TMP_FILES "${CMAKE_JAVA_CLASS_OUTPUT_PATH}/${JAR_CLASS_PREFIX}/*.class") - if (_JAVA_GLOBBED_TMP_FILES) - list(APPEND _JAVA_GLOBBED_FILES ${_JAVA_GLOBBED_TMP_FILES}) - endif (_JAVA_GLOBBED_TMP_FILES) - endforeach(JAR_CLASS_PREFIX ${CMAKE_JAR_CLASSES_PREFIX}) - else() - file(GLOB_RECURSE _JAVA_GLOBBED_FILES "${CMAKE_JAVA_CLASS_OUTPUT_PATH}/*.class") - endif (CMAKE_JAR_CLASSES_PREFIX) - - set(_JAVA_CLASS_FILES) - # file(GLOB_RECURSE foo RELATIVE) is broken so we need this. - foreach(_JAVA_GLOBBED_FILE ${_JAVA_GLOBBED_FILES}) - file(RELATIVE_PATH _JAVA_CLASS_FILE ${CMAKE_JAVA_CLASS_OUTPUT_PATH} ${_JAVA_GLOBBED_FILE}) - set(_JAVA_CLASS_FILES ${_JAVA_CLASS_FILES}${_JAVA_CLASS_FILE}\n) - endforeach(_JAVA_GLOBBED_FILE ${_JAVA_GLOBBED_FILES}) - - # write to file - file(WRITE ${CMAKE_JAVA_CLASS_OUTPUT_PATH}/java_class_filelist ${_JAVA_CLASS_FILES}) - - else (EXISTS "${CMAKE_JAVA_CLASS_OUTPUT_PATH}") - message(SEND_ERROR "FATAL: Java class output path doesn't exist") - endif (EXISTS "${CMAKE_JAVA_CLASS_OUTPUT_PATH}") -else (CMAKE_JAVA_CLASS_OUTPUT_PATH) - message(SEND_ERROR "FATAL: Can't find CMAKE_JAVA_CLASS_OUTPUT_PATH") -endif (CMAKE_JAVA_CLASS_OUTPUT_PATH) diff --git a/cmake/UseJavaSymlinks.cmake b/cmake/UseJavaSymlinks.cmake deleted file mode 100644 index c66ee1ea..00000000 --- a/cmake/UseJavaSymlinks.cmake +++ /dev/null @@ -1,32 +0,0 @@ -# -# Helper script for UseJava.cmake -# - -#============================================================================= -# Copyright 2010-2011 Andreas schneider -# -# Distributed under the OSI-approved BSD License (the "License"); -# see accompanying file Copyright.txt for details. -# -# This software is distributed WITHOUT ANY WARRANTY; without even the -# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. -# See the License for more information. -#============================================================================= -# (To distribute this file outside of CMake, substitute the full -# License text for the above reference.) - -if (UNIX AND _JAVA_TARGET_OUTPUT_LINK) - if (_JAVA_TARGET_OUTPUT_NAME) - find_program(LN_EXECUTABLE - NAMES - ln - ) - - execute_process( - COMMAND ${LN_EXECUTABLE} -sf "${_JAVA_TARGET_OUTPUT_NAME}" "${_JAVA_TARGET_OUTPUT_LINK}" - WORKING_DIRECTORY ${_JAVA_TARGET_DIR} - ) - else (_JAVA_TARGET_OUTPUT_NAME) - message(SEND_ERROR "FATAL: Can't find _JAVA_TARGET_OUTPUT_NAME") - endif (_JAVA_TARGET_OUTPUT_NAME) -endif (UNIX AND _JAVA_TARGET_OUTPUT_LINK) diff --git a/launcher/Application.cpp b/launcher/Application.cpp index 11109857..afb33a50 100644 --- a/launcher/Application.cpp +++ b/launcher/Application.cpp @@ -819,6 +819,7 @@ Application::Application(int &argc, char **argv) : QApplication(argc, argv) m_metacache->addBase("ModpacksCHPacks", QDir("cache/ModpacksCHPacks").absolutePath()); m_metacache->addBase("TechnicPacks", QDir("cache/TechnicPacks").absolutePath()); m_metacache->addBase("FlamePacks", QDir("cache/FlamePacks").absolutePath()); + m_metacache->addBase("ModrinthPacks", QDir("cache/ModrinthPacks").absolutePath()); m_metacache->addBase("root", QDir::currentPath()); m_metacache->addBase("translations", QDir("translations").absolutePath()); m_metacache->addBase("icons", QDir("cache/icons").absolutePath()); diff --git a/launcher/CMakeLists.txt b/launcher/CMakeLists.txt index b79f03c8..8e75be20 100644 --- a/launcher/CMakeLists.txt +++ b/launcher/CMakeLists.txt @@ -532,6 +532,8 @@ set(FLAME_SOURCES set(MODRINTH_SOURCES modplatform/modrinth/ModrinthPackIndex.cpp modplatform/modrinth/ModrinthPackIndex.h + modplatform/modrinth/ModrinthPackManifest.cpp + modplatform/modrinth/ModrinthPackManifest.h ) set(MODPACKSCH_SOURCES @@ -774,6 +776,11 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/flame/FlameModPage.cpp ui/pages/modplatform/flame/FlameModPage.h + ui/pages/modplatform/modrinth/ModrinthPage.cpp + ui/pages/modplatform/modrinth/ModrinthPage.h + ui/pages/modplatform/modrinth/ModrinthModel.cpp + ui/pages/modplatform/modrinth/ModrinthModel.h + ui/pages/modplatform/technic/TechnicModel.cpp ui/pages/modplatform/technic/TechnicModel.h ui/pages/modplatform/technic/TechnicPage.cpp @@ -782,10 +789,10 @@ SET(LAUNCHER_SOURCES ui/pages/modplatform/ImportPage.cpp ui/pages/modplatform/ImportPage.h - ui/pages/modplatform/modrinth/ModrinthModel.cpp - ui/pages/modplatform/modrinth/ModrinthModel.h - ui/pages/modplatform/modrinth/ModrinthPage.cpp - ui/pages/modplatform/modrinth/ModrinthPage.h + ui/pages/modplatform/modrinth/ModrinthModModel.cpp + ui/pages/modplatform/modrinth/ModrinthModModel.h + ui/pages/modplatform/modrinth/ModrinthModPage.cpp + ui/pages/modplatform/modrinth/ModrinthModPage.h # GUI - dialogs ui/dialogs/AboutDialog.cpp @@ -908,6 +915,7 @@ qt5_wrap_ui(LAUNCHER_UI ui/pages/modplatform/legacy_ftb/Page.ui ui/pages/modplatform/ImportPage.ui ui/pages/modplatform/ftb/FtbPage.ui + ui/pages/modplatform/modrinth/ModrinthPage.ui ui/pages/modplatform/technic/TechnicPage.ui ui/widgets/InstanceCardWidget.ui ui/widgets/CustomCommands.ui diff --git a/launcher/InstanceImportTask.cpp b/launcher/InstanceImportTask.cpp index ca7e0590..4bad7251 100644 --- a/launcher/InstanceImportTask.cpp +++ b/launcher/InstanceImportTask.cpp @@ -1,6 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * Copyright (c) 2022 flowln * * This program is free software: you can redistribute it and/or modify @@ -51,11 +52,22 @@ #include "minecraft/PackProfile.h" #include "modplatform/flame/FileResolvingTask.h" #include "modplatform/flame/PackManifest.h" +#include "modplatform/modrinth/ModrinthPackManifest.h" #include "modplatform/technic/TechnicPackProcessor.h" -InstanceImportTask::InstanceImportTask(const QUrl sourceUrl) +#include "Application.h" +#include "icons/IconList.h" +#include "net/ChecksumValidator.h" + +#include "ui/dialogs/CustomMessageBox.h" + +#include +#include + +InstanceImportTask::InstanceImportTask(const QUrl sourceUrl, QWidget* parent) { m_sourceUrl = sourceUrl; + m_parent = parent; } bool InstanceImportTask::abort() @@ -127,6 +139,7 @@ void InstanceImportTask::processZipPack() QString mmcFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "instance.cfg"); bool technicFound = QuaZipDir(m_packZip.get()).exists("/bin/modpack.jar") || QuaZipDir(m_packZip.get()).exists("/bin/version.json"); QString flameFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "manifest.json"); + QString modrinthFound = MMCZip::findFolderOfFileInZip(m_packZip.get(), "modrinth.index.json"); QString root; if(!mmcFound.isNull()) { @@ -150,6 +163,13 @@ void InstanceImportTask::processZipPack() root = flameFound; m_modpackType = ModpackType::Flame; } + else if(!modrinthFound.isNull()) + { + // process as Modrinth pack + qDebug() << "Modrinth:" << modrinthFound; + root = modrinthFound; + m_modpackType = ModpackType::Modrinth; + } if(m_modpackType == ModpackType::Unknown) { emitFailed(tr("Archive does not contain a recognized modpack type.")); @@ -206,15 +226,18 @@ void InstanceImportTask::extractFinished() switch(m_modpackType) { - case ModpackType::Flame: - processFlame(); - return; case ModpackType::MultiMC: processMultiMC(); return; case ModpackType::Technic: processTechnic(); return; + case ModpackType::Flame: + processFlame(); + return; + case ModpackType::Modrinth: + processModrinth(); + return; case ModpackType::Unknown: emitFailed(tr("Archive does not contain a recognized modpack type.")); return; @@ -457,25 +480,174 @@ void InstanceImportTask::processMultiMC() instance.setName(m_instName); // if the icon was specified by user, use that. otherwise pull icon from the pack + if (m_instIcon != "default") { + instance.setIconKey(m_instIcon); + } else { + m_instIcon = instance.iconKey(); + + auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon); + if (!importIconPath.isNull() && QFile::exists(importIconPath)) { + // import icon + auto iconList = APPLICATION->icons(); + if (iconList->iconFileExists(m_instIcon)) { + iconList->deleteIcon(m_instIcon); + } + iconList->installIcons({ importIconPath }); + } + } + emitSucceeded(); +} + +void InstanceImportTask::processModrinth() +{ + std::vector files; + QString minecraftVersion, fabricVersion, quiltVersion, forgeVersion; + try { + QString indexPath = FS::PathCombine(m_stagingPath, "modrinth.index.json"); + auto doc = Json::requireDocument(indexPath); + auto obj = Json::requireObject(doc, "modrinth.index.json"); + int formatVersion = Json::requireInteger(obj, "formatVersion", "modrinth.index.json"); + if (formatVersion == 1) { + auto game = Json::requireString(obj, "game", "modrinth.index.json"); + if (game != "minecraft") { + throw JSONValidationError("Unknown game: " + game); + } + + auto jsonFiles = Json::requireIsArrayOf(obj, "files", "modrinth.index.json"); + bool had_optional = false; + for (auto& obj : jsonFiles) { + Modrinth::File file; + file.path = Json::requireString(obj, "path"); + + auto env = Json::ensureObject(obj, "env"); + QString support = Json::ensureString(env, "client", "unsupported"); + if (support == "unsupported") { + continue; + } else if (support == "optional") { + // TODO: Make a review dialog for choosing which ones the user wants! + if (!had_optional) { + had_optional = true; + auto info = CustomMessageBox::selectable( + m_parent, tr("Optional mod detected!"), + tr("One or more mods from this modpack are optional. They will be downloaded, but disabled by default!"), QMessageBox::Information); + info->exec(); + } + + if (file.path.endsWith(".jar")) + file.path += ".disabled"; + } + + QJsonObject hashes = Json::requireObject(obj, "hashes"); + QString hash; + QCryptographicHash::Algorithm hashAlgorithm; + hash = Json::ensureString(hashes, "sha1"); + hashAlgorithm = QCryptographicHash::Sha1; + if (hash.isEmpty()) { + hash = Json::ensureString(hashes, "sha512"); + hashAlgorithm = QCryptographicHash::Sha512; + if (hash.isEmpty()) { + hash = Json::ensureString(hashes, "sha256"); + hashAlgorithm = QCryptographicHash::Sha256; + if (hash.isEmpty()) { + throw JSONValidationError("No hash found for: " + file.path); + } + } + } + file.hash = QByteArray::fromHex(hash.toLatin1()); + file.hashAlgorithm = hashAlgorithm; + // Do not use requireUrl, which uses StrictMode, instead use QUrl's default TolerantMode + // (as Modrinth seems to incorrectly handle spaces) + file.download = Json::requireString(Json::ensureArray(obj, "downloads").first(), "Download URL for " + file.path); + if (!file.download.isValid() || !Modrinth::validateDownloadUrl(file.download)) { + throw JSONValidationError("Download URL for " + file.path + " is not a correctly formatted URL"); + } + files.push_back(file); + } + + auto dependencies = Json::requireObject(obj, "dependencies", "modrinth.index.json"); + for (auto it = dependencies.begin(), end = dependencies.end(); it != end; ++it) { + QString name = it.key(); + if (name == "minecraft") { + minecraftVersion = Json::requireString(*it, "Minecraft version"); + } + else if (name == "fabric-loader") { + fabricVersion = Json::requireString(*it, "Fabric Loader version"); + } + else if (name == "quilt-loader") { + quiltVersion = Json::requireString(*it, "Quilt Loader version"); + } + else if (name == "forge") { + forgeVersion = Json::requireString(*it, "Forge version"); + } + else { + throw JSONValidationError("Unknown dependency type: " + name); + } + } + } else { + throw JSONValidationError(QStringLiteral("Unknown format version: %s").arg(formatVersion)); + } + QFile::remove(indexPath); + } catch (const JSONValidationError& e) { + emitFailed(tr("Could not understand pack index:\n") + e.cause()); + return; + } + + QString overridePath = FS::PathCombine(m_stagingPath, "overrides"); + if (QFile::exists(overridePath)) { + QString mcPath = FS::PathCombine(m_stagingPath, ".minecraft"); + if (!QFile::rename(overridePath, mcPath)) { + emitFailed(tr("Could not rename the overrides folder:\n") + "overrides"); + return; + } + } + + QString configPath = FS::PathCombine(m_stagingPath, "instance.cfg"); + auto instanceSettings = std::make_shared(configPath); + MinecraftInstance instance(m_globalSettings, instanceSettings, m_stagingPath); + auto components = instance.getPackProfile(); + components->buildingFromScratch(); + components->setComponentVersion("net.minecraft", minecraftVersion, true); + if (!fabricVersion.isEmpty()) + components->setComponentVersion("net.fabricmc.fabric-loader", fabricVersion, true); + if (!quiltVersion.isEmpty()) + components->setComponentVersion("org.quiltmc.quilt-loader", quiltVersion, true); + if (!forgeVersion.isEmpty()) + components->setComponentVersion("net.minecraftforge", forgeVersion, true); if (m_instIcon != "default") { instance.setIconKey(m_instIcon); } else { - m_instIcon = instance.iconKey(); - - auto importIconPath = IconUtils::findBestIconIn(instance.instanceRoot(), m_instIcon); - if (!importIconPath.isNull() && QFile::exists(importIconPath)) - { - // import icon - auto iconList = APPLICATION->icons(); - if (iconList->iconFileExists(m_instIcon)) - { - iconList->deleteIcon(m_instIcon); - } - iconList->installIcons({importIconPath}); - } + instance.setIconKey("modrinth"); } - emitSucceeded(); + instance.setName(m_instName); + instance.saveNow(); + + m_filesNetJob = new NetJob(tr("Mod download"), APPLICATION->network()); + for (auto &file : files) + { + auto path = FS::PathCombine(m_stagingPath, ".minecraft", file.path); + qDebug() << "Will download" << file.download << "to" << path; + auto dl = Net::Download::makeFile(file.download, path); + dl->addValidator(new Net::ChecksumValidator(file.hashAlgorithm, file.hash)); + m_filesNetJob->addNetAction(dl); + } + connect(m_filesNetJob.get(), &NetJob::succeeded, this, [&]() + { + m_filesNetJob.reset(); + emitSucceeded(); + } + ); + connect(m_filesNetJob.get(), &NetJob::failed, [&](const QString &reason) + { + m_filesNetJob.reset(); + emitFailed(reason); + }); + connect(m_filesNetJob.get(), &NetJob::progress, [&](qint64 current, qint64 total) + { + setProgress(current, total); + }); + setStatus(tr("Downloading mods...")); + m_filesNetJob->start(); } diff --git a/launcher/InstanceImportTask.h b/launcher/InstanceImportTask.h index 365c3dc4..5e4d3235 100644 --- a/launcher/InstanceImportTask.h +++ b/launcher/InstanceImportTask.h @@ -1,16 +1,36 @@ -/* Copyright 2013-2021 MultiMC Contributors +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. * - * http://www.apache.org/licenses/LICENSE-2.0 + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #pragma once @@ -35,7 +55,7 @@ class InstanceImportTask : public InstanceTask { Q_OBJECT public: - explicit InstanceImportTask(const QUrl sourceUrl); + explicit InstanceImportTask(const QUrl sourceUrl, QWidget* parent = nullptr); bool canAbort() const override { return true; } bool abort() override; @@ -47,8 +67,9 @@ protected: private: void processZipPack(); void processMultiMC(); - void processFlame(); void processTechnic(); + void processFlame(); + void processModrinth(); private slots: void downloadSucceeded(); @@ -69,7 +90,11 @@ private: /* data */ enum class ModpackType{ Unknown, MultiMC, + Technic, Flame, - Technic + Modrinth, } m_modpackType = ModpackType::Unknown; + + //FIXME: nuke + QWidget* m_parent; }; diff --git a/launcher/InstanceList.cpp b/launcher/InstanceList.cpp index 6e37e3d8..847d897e 100644 --- a/launcher/InstanceList.cpp +++ b/launcher/InstanceList.cpp @@ -38,6 +38,10 @@ #include "ExponentialSeries.h" #include "WatchLock.h" +#ifdef Q_OS_WIN32 +#include +#endif + const static int GROUP_FILE_FORMAT_VERSION = 1; InstanceList::InstanceList(SettingsObjectPtr settings, const QString & instDir, QObject *parent) @@ -851,13 +855,18 @@ Task * InstanceList::wrapInstanceTask(InstanceTask * task) QString InstanceList::getStagedInstancePath() { QString key = QUuid::createUuid().toString(); - QString relPath = FS::PathCombine("_LAUNCHER_TEMP/" , key); + QString tempDir = ".LAUNCHER_TEMP/"; + QString relPath = FS::PathCombine(tempDir, key); QDir rootPath(m_instDir); auto path = FS::PathCombine(m_instDir, relPath); if(!rootPath.mkpath(relPath)) { return QString(); } +#ifdef Q_OS_WIN32 + auto tempPath = FS::PathCombine(m_instDir, tempDir); + SetFileAttributesA(tempPath.toStdString().c_str(), FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_NOT_CONTENT_INDEXED); +#endif return path; } diff --git a/launcher/LaunchController.cpp b/launcher/LaunchController.cpp index 4cb62e69..002c08b9 100644 --- a/launcher/LaunchController.cpp +++ b/launcher/LaunchController.cpp @@ -93,7 +93,7 @@ void LaunchController::decideAccount() auto reply = CustomMessageBox::selectable( m_parentWidget, tr("No Accounts"), - tr("In order to play Minecraft, you must have at least one Mojang or Minecraft " + tr("In order to play Minecraft, you must have at least one Mojang or Microsoft " "account logged in. " "Would you like to open the account manager to add an account now?"), QMessageBox::Information, diff --git a/launcher/MMCZip.cpp b/launcher/MMCZip.cpp index b92f1781..8591fcc0 100644 --- a/launcher/MMCZip.cpp +++ b/launcher/MMCZip.cpp @@ -297,20 +297,40 @@ nonstd::optional MMCZip::extractSubDir(QuaZip *zip, const QString & { continue; } + name.remove(0, subdir.size()); - QString absFilePath = directory.absoluteFilePath(name); + auto original_name = name; + + // Fix weird "folders with a single file get squashed" thing + QString path; + if(name.contains('/') && !name.endsWith('/')){ + path = name.section('/', 0, -2) + "/"; + FS::ensureFolderPathExists(path); + + name = name.split('/').last(); + } + + QString absFilePath; if(name.isEmpty()) { - absFilePath += "/"; + absFilePath = directory.absoluteFilePath(name) + "/"; } + else + { + absFilePath = directory.absoluteFilePath(path + name); + } + if (!JlCompress::extractFile(zip, "", absFilePath)) { - qWarning() << "Failed to extract file" << name << "to" << absFilePath; + qWarning() << "Failed to extract file" << original_name << "to" << absFilePath; JlCompress::removeFile(extracted); return nonstd::nullopt; } + extracted.append(absFilePath); - qDebug() << "Extracted file" << name; + QFile::setPermissions(absFilePath, QFileDevice::Permission::ReadUser | QFileDevice::Permission::WriteUser | QFileDevice::Permission::ExeUser); + + qDebug() << "Extracted file" << name << "to" << absFilePath; } while (zip->goToNextFile()); return extracted; } diff --git a/launcher/icons/IconList.cpp b/launcher/icons/IconList.cpp index 584edd69..c269d10a 100644 --- a/launcher/icons/IconList.cpp +++ b/launcher/icons/IconList.cpp @@ -36,7 +36,7 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren auto file_info_list = instance_icons.entryInfoList(QDir::Files, QDir::Name); for (auto file_info : file_info_list) { - builtinNames.insert(file_info.baseName()); + builtinNames.insert(file_info.completeBaseName()); } } for(auto & builtinName : builtinNames) @@ -51,6 +51,9 @@ IconList::IconList(const QStringList &builtinPaths, QString path, QObject *paren connect(m_watcher.get(), SIGNAL(fileChanged(QString)), SLOT(fileChanged(QString))); directoryChanged(path); + + // Forces the UI to update, so that lengthy icon names are shown properly from the start + emit iconUpdated({}); } void IconList::directoryChanged(const QString &path) @@ -94,7 +97,13 @@ void IconList::directoryChanged(const QString &path) { qDebug() << "Removing " << remove; QFileInfo rmfile(remove); - QString key = rmfile.baseName(); + QString key = rmfile.completeBaseName(); + + QString suffix = rmfile.suffix(); + // The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well + if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif") + key = rmfile.fileName(); + int idx = getIconIndex(key); if (idx == -1) continue; @@ -117,8 +126,15 @@ void IconList::directoryChanged(const QString &path) for (auto add : to_add) { qDebug() << "Adding " << add; + QFileInfo addfile(add); - QString key = addfile.baseName(); + QString key = addfile.completeBaseName(); + + QString suffix = addfile.suffix(); + // The icon doesnt have a suffix, but it can have other .s in the name, so we account for those as well + if (suffix != "jpeg" && suffix != "png" && suffix != "jpg" && suffix != "ico" && suffix != "svg" && suffix != "gif") + key = addfile.fileName(); + if (addIcon(key, QString(), addfile.filePath(), IconType::FileBased)) { m_watcher->addPath(add); @@ -133,7 +149,7 @@ void IconList::fileChanged(const QString &path) QFileInfo checkfile(path); if (!checkfile.exists()) return; - QString key = checkfile.baseName(); + QString key = checkfile.completeBaseName(); int idx = getIconIndex(key); if (idx == -1) return; diff --git a/launcher/minecraft/MinecraftInstance.cpp b/launcher/minecraft/MinecraftInstance.cpp index 3ba79178..e20dc24c 100644 --- a/launcher/minecraft/MinecraftInstance.cpp +++ b/launcher/minecraft/MinecraftInstance.cpp @@ -162,6 +162,11 @@ MinecraftInstance::MinecraftInstance(SettingsObjectPtr globalSettings, SettingsO m_settings->registerSetting("JoinServerOnLaunch", false); m_settings->registerSetting("JoinServerOnLaunchAddress", ""); + // Miscellaneous + auto miscellaneousOverride = m_settings->registerSetting("OverrideMiscellaneous", false); + m_settings->registerOverride(globalSettings->getSetting("CloseAfterLaunch"), miscellaneousOverride); + m_settings->registerOverride(globalSettings->getSetting("QuitAfterGameStop"), miscellaneousOverride); + m_components.reset(new PackProfile(this)); } @@ -984,7 +989,7 @@ shared_qobject_ptr MinecraftInstance::createLaunchTask(AuthSessionPt { process->setCensorFilter(createCensorFilterFromSession(session)); } - if(APPLICATION->settings()->get("QuitAfterGameStop").toBool()) + if(m_settings->get("QuitAfterGameStop").toBool()) { auto step = new QuitAfterGameStop(pptr); process->appendStep(step); diff --git a/launcher/minecraft/launch/LauncherPartLaunch.cpp b/launcher/minecraft/launch/LauncherPartLaunch.cpp index 173f29b5..d7010355 100644 --- a/launcher/minecraft/launch/LauncherPartLaunch.cpp +++ b/launcher/minecraft/launch/LauncherPartLaunch.cpp @@ -25,7 +25,8 @@ LauncherPartLaunch::LauncherPartLaunch(LaunchTask *parent) : LaunchStep(parent) { - if (APPLICATION->settings()->get("CloseAfterLaunch").toBool()) + auto instance = parent->instance(); + if (instance->settings()->get("CloseAfterLaunch").toBool()) { std::shared_ptr connection{new QMetaObject::Connection}; *connection = connect(&m_process, &LoggedProcess::log, this, [=](QStringList lines, MessageLevel::Enum level) { @@ -168,7 +169,8 @@ void LauncherPartLaunch::on_state(LoggedProcess::State state) } case LoggedProcess::Finished: { - if (APPLICATION->settings()->get("CloseAfterLaunch").toBool()) + auto instance = m_parent->instance(); + if (instance->settings()->get("CloseAfterLaunch").toBool()) APPLICATION->showMainWindow(); m_parent->setPid(-1); diff --git a/launcher/minecraft/mod/LocalModParseTask.cpp b/launcher/minecraft/mod/LocalModParseTask.cpp index f01da8ae..631c3abb 100644 --- a/launcher/minecraft/mod/LocalModParseTask.cpp +++ b/launcher/minecraft/mod/LocalModParseTask.cpp @@ -8,6 +8,7 @@ #include #include +#include "Json.h" #include "settings/INIFile.h" #include "FileSystem.h" @@ -262,6 +263,44 @@ std::shared_ptr ReadFabricModInfo(QByteArray contents) return details; } +// https://github.com/QuiltMC/rfcs/blob/master/specification/0002-quilt.mod.json.md +std::shared_ptr ReadQuiltModInfo(QByteArray contents) +{ + QJsonParseError jsonError; + QJsonDocument jsonDoc = QJsonDocument::fromJson(contents, &jsonError); + auto object = Json::requireObject(jsonDoc, "quilt.mod.json"); + auto schemaVersion = Json::ensureInteger(object.value("schema_version"), 0, "Quilt schema_version"); + + std::shared_ptr details = std::make_shared(); + + // https://github.com/QuiltMC/rfcs/blob/be6ba280d785395fefa90a43db48e5bfc1d15eb4/specification/0002-quilt.mod.json.md + if (schemaVersion == 1) + { + auto modInfo = Json::requireObject(object.value("quilt_loader"), "Quilt mod info"); + + details->mod_id = Json::requireString(modInfo.value("id"), "Mod ID"); + details->version = Json::requireString(modInfo.value("version"), "Mod version"); + + auto modMetadata = Json::ensureObject(modInfo.value("metadata")); + + details->name = Json::ensureString(modMetadata.value("name"), details->mod_id); + details->description = Json::ensureString(modMetadata.value("description")); + + auto modContributors = Json::ensureObject(modMetadata.value("contributors")); + + // We don't really care about the role of a contributor here + details->authors += modContributors.keys(); + + auto modContact = Json::ensureObject(modMetadata.value("contact")); + + if (modContact.contains("homepage")) + { + details->homeurl = Json::requireString(modContact.value("homepage")); + } + } + return details; +} + std::shared_ptr ReadForgeInfo(QByteArray contents) { std::shared_ptr details = std::make_shared(); @@ -391,7 +430,7 @@ void LocalModParseTask::processAsZip() zip.close(); return; } - else if (zip.setCurrentFile("fabric.mod.json")) // TODO: Support quilt.mod.json + else if (zip.setCurrentFile("fabric.mod.json")) { if (!file.open(QIODevice::ReadOnly)) { @@ -404,6 +443,19 @@ void LocalModParseTask::processAsZip() zip.close(); return; } + else if (zip.setCurrentFile("quilt.mod.json")) + { + if (!file.open(QIODevice::ReadOnly)) + { + zip.close(); + return; + } + + m_result->details = ReadQuiltModInfo(file.readAll()); + file.close(); + zip.close(); + return; + } else if (zip.setCurrentFile("forgeversion.properties")) { if (!file.open(QIODevice::ReadOnly)) diff --git a/launcher/modplatform/flame/FlamePackIndex.cpp b/launcher/modplatform/flame/FlamePackIndex.cpp index 549cace6..ac24c647 100644 --- a/launcher/modplatform/flame/FlamePackIndex.cpp +++ b/launcher/modplatform/flame/FlamePackIndex.cpp @@ -6,7 +6,7 @@ void Flame::loadIndexedPack(Flame::IndexedPack& pack, QJsonObject& obj) { pack.addonId = Json::requireInteger(obj, "id"); pack.name = Json::requireString(obj, "name"); - pack.websiteUrl = Json::ensureString(obj, "websiteUrl", ""); + pack.websiteUrl = Json::ensureString(Json::ensureObject(obj, "links"), "websiteUrl", ""); pack.description = Json::ensureString(obj, "summary", ""); auto logo = Json::requireObject(obj, "logo"); diff --git a/launcher/modplatform/modrinth/ModrinthAPI.h b/launcher/modplatform/modrinth/ModrinthAPI.h index 86852c94..6d642b5e 100644 --- a/launcher/modplatform/modrinth/ModrinthAPI.h +++ b/launcher/modplatform/modrinth/ModrinthAPI.h @@ -1,5 +1,24 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #pragma once +#include "BuildConfig.h" #include "modplatform/ModAPI.h" #include "modplatform/helpers/NetworkModAPI.h" @@ -47,13 +66,13 @@ class ModrinthAPI : public NetworkModAPI { return ""; } - return QString( - "https://api.modrinth.com/v2/search?" - "offset=%1&" - "limit=25&" - "query=%2&" - "index=%3&" - "facets=[[%4],%5[\"project_type:mod\"]]") + return QString(BuildConfig.MODRINTH_PROD_URL + + "/search?" + "offset=%1&" + "limit=25&" + "query=%2&" + "index=%3&" + "facets=[[%4],%5[\"project_type:mod\"]]") .arg(args.offset) .arg(args.search) .arg(args.sorting) @@ -63,9 +82,10 @@ class ModrinthAPI : public NetworkModAPI { inline auto getVersionsURL(VersionSearchArgs& args) const -> QString override { - return QString("https://api.modrinth.com/v2/project/%1/version?" - "game_versions=[%2]" - "loaders=[\"%3\"]") + return QString(BuildConfig.MODRINTH_PROD_URL + + "/project/%1/version?" + "game_versions=[%2]" + "loaders=[\"%3\"]") .arg(args.addonId) .arg(getGameVersionsString(args.mcVersions)) .arg(getModLoaderStrings(args.loader).join("\",\"")); diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp index a3c2f166..f7fa9864 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.cpp +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.cpp @@ -1,3 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #include "ModrinthPackIndex.h" #include "ModrinthAPI.h" diff --git a/launcher/modplatform/modrinth/ModrinthPackIndex.h b/launcher/modplatform/modrinth/ModrinthPackIndex.h index fd17847a..7f306f25 100644 --- a/launcher/modplatform/modrinth/ModrinthPackIndex.h +++ b/launcher/modplatform/modrinth/ModrinthPackIndex.h @@ -1,3 +1,20 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + #pragma once #include "modplatform/ModIndex.h" diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.cpp b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp new file mode 100644 index 00000000..f1ad39ce --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.cpp @@ -0,0 +1,156 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * Copyright 2022 kb1000 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ModrinthPackManifest.h" +#include "Json.h" + +#include "modplatform/modrinth/ModrinthAPI.h" + +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" + +static ModrinthAPI api; + +namespace Modrinth { + +void loadIndexedPack(Modpack& pack, QJsonObject& obj) +{ + pack.id = Json::ensureString(obj, "project_id"); + + pack.name = Json::ensureString(obj, "title"); + pack.description = Json::ensureString(obj, "description"); + auto temp_author_name = Json::ensureString(obj, "author"); + pack.author = std::make_tuple(temp_author_name, api.getAuthorURL(temp_author_name)); + pack.iconName = QString("modrinth_%1").arg(Json::ensureString(obj, "slug")); + pack.iconUrl = Json::ensureString(obj, "icon_url"); +} + +void loadIndexedInfo(Modpack& pack, QJsonObject& obj) +{ + pack.extra.body = Json::ensureString(obj, "body"); + pack.extra.projectUrl = QString("https://modrinth.com/modpack/%1").arg(Json::ensureString(obj, "slug")); + pack.extra.sourceUrl = Json::ensureString(obj, "source_url"); + pack.extra.wikiUrl = Json::ensureString(obj, "wiki_url"); + + pack.extraInfoLoaded = true; +} + +void loadIndexedVersions(Modpack& pack, QJsonDocument& doc) +{ + QVector unsortedVersions; + + auto arr = Json::requireArray(doc); + + for (auto versionIter : arr) { + auto obj = Json::requireObject(versionIter); + auto file = loadIndexedVersion(obj); + + if(!file.id.isEmpty()) // Heuristic to check if the returned value is valid + unsortedVersions.append(file); + } + auto orderSortPredicate = [](const ModpackVersion& a, const ModpackVersion& b) -> bool { + // dates are in RFC 3339 format + return a.date > b.date; + }; + + std::sort(unsortedVersions.begin(), unsortedVersions.end(), orderSortPredicate); + + pack.versions.swap(unsortedVersions); + + pack.versionsLoaded = true; +} + +auto validateDownloadUrl(QUrl url) -> bool +{ + auto domain = url.host(); + if(domain == "cdn.modrinth.com") + return true; + if(domain == "edge.forgecdn.net") + return true; + if(domain == "media.forgecdn.net") + return true; + if(domain == "github.com") + return true; + if(domain == "raw.githubusercontent.com") + return true; + + return false; +} + +auto loadIndexedVersion(QJsonObject &obj) -> ModpackVersion +{ + ModpackVersion file; + + file.name = Json::requireString(obj, "name"); + file.version = Json::requireString(obj, "version_number"); + + file.id = Json::requireString(obj, "id"); + file.project_id = Json::requireString(obj, "project_id"); + + file.date = Json::requireString(obj, "date_published"); + + auto files = Json::requireArray(obj, "files"); + + + for (auto file_iter : files) { + File indexed_file; + auto parent = Json::requireObject(file_iter); + auto is_primary = Json::ensureBoolean(parent, "primary", false); + if (!is_primary) { + auto filename = Json::ensureString(parent, "filename"); + // Checking suffix here is fine because it's the response from Modrinth, + // so one would assume it will always be in English. + if(!filename.endsWith("mrpack") && !filename.endsWith("zip")) + continue; + } + + auto url = Json::requireString(parent, "url"); + + if(!validateDownloadUrl(url)) + continue; + + file.download_url = url; + if(is_primary) + break; + } + + if(file.download_url.isEmpty()) + return {}; + + return file; +} + +} // namespace Modrinth diff --git a/launcher/modplatform/modrinth/ModrinthPackManifest.h b/launcher/modplatform/modrinth/ModrinthPackManifest.h new file mode 100644 index 00000000..e5fc9a70 --- /dev/null +++ b/launcher/modplatform/modrinth/ModrinthPackManifest.h @@ -0,0 +1,107 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * Copyright 2022 kb1000 + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +#include +#include +#include +#include +#include + +class MinecraftInstance; + +namespace Modrinth { + +struct File +{ + QString path; + + QCryptographicHash::Algorithm hashAlgorithm; + QByteArray hash; + // TODO: should this support multiple download URLs, like the JSON does? + QUrl download; +}; + +struct ModpackExtra { + QString body; + + QString projectUrl; + QString sourceUrl; + QString wikiUrl; +}; + +struct ModpackVersion { + QString name; + QString version; + + QString id; + QString project_id; + + QString date; + + QString download_url; +}; + +struct Modpack { + QString id; + + QString name; + QString description; + std::tuple author; + QString iconName; + QUrl iconUrl; + + bool versionsLoaded = false; + bool extraInfoLoaded = false; + + ModpackExtra extra; + QVector versions; +}; + +void loadIndexedPack(Modpack&, QJsonObject&); +void loadIndexedInfo(Modpack&, QJsonObject&); +void loadIndexedVersions(Modpack&, QJsonDocument&); +auto loadIndexedVersion(QJsonObject&) -> ModpackVersion; + +auto validateDownloadUrl(QUrl) -> bool; + +} + +Q_DECLARE_METATYPE(Modrinth::Modpack) +Q_DECLARE_METATYPE(Modrinth::ModpackVersion) diff --git a/launcher/resources/multimc/128x128/instances/modrinth.png b/launcher/resources/multimc/128x128/instances/modrinth.png deleted file mode 100644 index 740bc8f0..00000000 Binary files a/launcher/resources/multimc/128x128/instances/modrinth.png and /dev/null differ diff --git a/launcher/resources/multimc/32x32/instances/modrinth.png b/launcher/resources/multimc/32x32/instances/modrinth.png deleted file mode 100644 index 025ed065..00000000 Binary files a/launcher/resources/multimc/32x32/instances/modrinth.png and /dev/null differ diff --git a/launcher/resources/multimc/multimc.qrc b/launcher/resources/multimc/multimc.qrc index 0fe673ff..e22fe7ee 100644 --- a/launcher/resources/multimc/multimc.qrc +++ b/launcher/resources/multimc/multimc.qrc @@ -20,6 +20,9 @@ scalable/atlauncher.svg scalable/atlauncher-placeholder.png + + scalable/instances/modrinth.svg + scalable/proxy.svg @@ -272,9 +275,6 @@ 32x32/instances/flame.png 128x128/instances/flame.png - 32x32/instances/modrinth.png - 128x128/instances/modrinth.png - 32x32/instances/gear.png 128x128/instances/gear.png diff --git a/launcher/resources/multimc/scalable/instances/modrinth.svg b/launcher/resources/multimc/scalable/instances/modrinth.svg new file mode 100644 index 00000000..a40f0e72 --- /dev/null +++ b/launcher/resources/multimc/scalable/instances/modrinth.svg @@ -0,0 +1,4 @@ + + + + diff --git a/launcher/ui/MainWindow.cpp b/launcher/ui/MainWindow.cpp index f34cf1ab..f016dc76 100644 --- a/launcher/ui/MainWindow.cpp +++ b/launcher/ui/MainWindow.cpp @@ -238,6 +238,9 @@ public: TranslatedAction actionREDDIT; TranslatedAction actionAbout; + TranslatedAction actionNoAccountsAdded; + TranslatedAction actionNoDefaultAccount; + QVector all_toolbuttons; QWidget *centralWidget = nullptr; @@ -746,6 +749,9 @@ public: // disabled until we have an instance selected instanceToolBar->setEnabled(false); instanceToolBar->setMovable(true); + // Qt doesn't like vertical moving toolbars, so we have to force them... + // See https://github.com/PolyMC/PolyMC/issues/493 + connect(instanceToolBar, &QToolBar::orientationChanged, [=](Qt::Orientation){ instanceToolBar->setOrientation(Qt::Vertical); }); instanceToolBar->setAllowedAreas(Qt::LeftToolBarArea | Qt::RightToolBarArea); instanceToolBar->setToolButtonStyle(Qt::ToolButtonTextOnly); instanceToolBar->setFloatable(false); @@ -828,7 +834,7 @@ public: QMetaObject::connectSlotsByName(MainWindow); } // setupUi - void retranslateUi(QMainWindow *MainWindow) + void retranslateUi(MainWindow *MainWindow) { QString winTitle = tr("%1 - Version %2", "Launcher - Version X").arg(BuildConfig.LAUNCHER_DISPLAYNAME, BuildConfig.printableVersionString()); MainWindow->setWindowTitle(winTitle); @@ -848,6 +854,12 @@ public: // submenu buttons foldersMenuButton->setText(tr("Folders")); helpMenuButton->setText(tr("Help")); + + // playtime counter + if (MainWindow->m_statusCenter) + { + MainWindow->updateStatusCenter(); + } } // retranslateUi }; @@ -950,6 +962,8 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new MainWindow ui->mainToolBar->addWidget(spacer); accountMenu = new QMenu(this); + // Use undocumented property... https://stackoverflow.com/questions/7121718/create-a-scrollbar-in-a-submenu-qt + accountMenu->setStyleSheet("QMenu { menu-scrollable: 1; }"); repopulateAccountsMenu(); @@ -1252,10 +1266,14 @@ void MainWindow::repopulateAccountsMenu() if (accounts->count() <= 0) { - QAction *action = new QAction(tr("No accounts added!"), this); - action->setEnabled(false); - accountMenu->addAction(action); - ui->profileMenu->addAction(action); + ui->all_actions.removeAll(&ui->actionNoAccountsAdded); + ui->actionNoAccountsAdded = TranslatedAction(this); + ui->actionNoAccountsAdded->setObjectName(QStringLiteral("actionNoAccountsAdded")); + ui->actionNoAccountsAdded.setTextId(QT_TRANSLATE_NOOP("MainWindow", "No accounts added!")); + ui->actionNoAccountsAdded->setEnabled(false); + accountMenu->addAction(ui->actionNoAccountsAdded); + ui->profileMenu->addAction(ui->actionNoAccountsAdded); + ui->all_actions.append(&ui->actionNoAccountsAdded); } else { @@ -1295,18 +1313,23 @@ void MainWindow::repopulateAccountsMenu() accountMenu->addSeparator(); ui->profileMenu->addSeparator(); - QAction *action = new QAction(tr("No Default Account"), this); - action->setCheckable(true); - action->setIcon(APPLICATION->getThemedIcon("noaccount")); - action->setData(-1); - action->setShortcut(QKeySequence(tr("Ctrl+0"))); + ui->all_actions.removeAll(&ui->actionNoDefaultAccount); + ui->actionNoDefaultAccount = TranslatedAction(this); + ui->actionNoDefaultAccount->setObjectName(QStringLiteral("actionNoDefaultAccount")); + ui->actionNoDefaultAccount.setTextId(QT_TRANSLATE_NOOP("MainWindow", "No Default Account")); + ui->actionNoDefaultAccount->setCheckable(true); + ui->actionNoDefaultAccount->setIcon(APPLICATION->getThemedIcon("noaccount")); + ui->actionNoDefaultAccount->setData(-1); + ui->actionNoDefaultAccount->setShortcut(QKeySequence(tr("Ctrl+0"))); if (!defaultAccount) { - action->setChecked(true); + ui->actionNoDefaultAccount->setChecked(true); } - accountMenu->addAction(action); - ui->profileMenu->addAction(action); - connect(action, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); + accountMenu->addAction(ui->actionNoDefaultAccount); + ui->profileMenu->addAction(ui->actionNoDefaultAccount); + connect(ui->actionNoDefaultAccount, SIGNAL(triggered(bool)), SLOT(changeActiveAccount())); + ui->all_actions.append(&ui->actionNoDefaultAccount); + ui->actionNoDefaultAccount.retranslate(); accountMenu->addSeparator(); ui->profileMenu->addSeparator(); diff --git a/launcher/ui/dialogs/ModDownloadDialog.cpp b/launcher/ui/dialogs/ModDownloadDialog.cpp index d02ea476..305e85c0 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.cpp +++ b/launcher/ui/dialogs/ModDownloadDialog.cpp @@ -13,7 +13,7 @@ #include #include "ui/widgets/PageContainer.h" -#include "ui/pages/modplatform/modrinth/ModrinthPage.h" +#include "ui/pages/modplatform/modrinth/ModrinthModPage.h" #include "ModDownloadTask.h" @@ -98,7 +98,7 @@ void ModDownloadDialog::accept() QList ModDownloadDialog::getPages() { - modrinthPage = new ModrinthPage(this, m_instance); + modrinthPage = new ModrinthModPage(this, m_instance); flameModPage = new FlameModPage(this, m_instance); return { diff --git a/launcher/ui/dialogs/ModDownloadDialog.h b/launcher/ui/dialogs/ModDownloadDialog.h index 309d89d0..782dc361 100644 --- a/launcher/ui/dialogs/ModDownloadDialog.h +++ b/launcher/ui/dialogs/ModDownloadDialog.h @@ -16,7 +16,7 @@ class ModDownloadDialog; class PageContainer; class QDialogButtonBox; -class ModrinthPage; +class ModrinthModPage; class ModDownloadDialog : public QDialog, public BasePageProvider { @@ -50,7 +50,7 @@ private: QVBoxLayout *m_verticalLayout = nullptr; - ModrinthPage *modrinthPage = nullptr; + ModrinthModPage *modrinthPage = nullptr; FlameModPage *flameModPage = nullptr; QHash modTask; BaseInstance *m_instance; diff --git a/launcher/ui/dialogs/NewInstanceDialog.cpp b/launcher/ui/dialogs/NewInstanceDialog.cpp index b402839c..05ea091d 100644 --- a/launcher/ui/dialogs/NewInstanceDialog.cpp +++ b/launcher/ui/dialogs/NewInstanceDialog.cpp @@ -39,6 +39,7 @@ #include "ui/pages/modplatform/legacy_ftb/Page.h" #include "ui/pages/modplatform/flame/FlamePage.h" #include "ui/pages/modplatform/ImportPage.h" +#include "ui/pages/modplatform/modrinth/ModrinthPage.h" #include "ui/pages/modplatform/technic/TechnicPage.h" @@ -134,6 +135,7 @@ QList NewInstanceDialog::getPages() flamePage, new FtbPage(this), new LegacyFTB::Page(this), + new ModrinthPage(this), technicPage }; } diff --git a/launcher/ui/dialogs/SkinUploadDialog.cpp b/launcher/ui/dialogs/SkinUploadDialog.cpp index 6a5a324f..8d137afc 100644 --- a/launcher/ui/dialogs/SkinUploadDialog.cpp +++ b/launcher/ui/dialogs/SkinUploadDialog.cpp @@ -100,7 +100,8 @@ void SkinUploadDialog::on_buttonBox_accepted() void SkinUploadDialog::on_skinBrowseBtn_clicked() { - QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), "*.png"); + auto filter = QMimeDatabase().mimeTypeForName("image/png").filterString(); + QString raw_path = QFileDialog::getOpenFileName(this, tr("Select Skin Texture"), QString(), filter); if (raw_path.isEmpty() || !QFileInfo::exists(raw_path)) { return; diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.cpp b/launcher/ui/pages/instance/InstanceSettingsPage.cpp index a48c4d69..b4562843 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.cpp +++ b/launcher/ui/pages/instance/InstanceSettingsPage.cpp @@ -101,6 +101,20 @@ void InstanceSettingsPage::applySettings() { SettingsObject::Lock lock(m_settings); + // Miscellaneous + bool miscellaneous = ui->miscellaneousSettingsBox->isChecked(); + m_settings->set("OverrideMiscellaneous", miscellaneous); + if (miscellaneous) + { + m_settings->set("CloseAfterLaunch", ui->closeAfterLaunchCheck->isChecked()); + m_settings->set("QuitAfterGameStop", ui->quitAfterGameStopCheck->isChecked()); + } + else + { + m_settings->reset("CloseAfterLaunch"); + m_settings->reset("QuitAfterGameStop"); + } + // Console bool console = ui->consoleSettingsBox->isChecked(); m_settings->set("OverrideConsole", console); @@ -247,6 +261,11 @@ void InstanceSettingsPage::applySettings() void InstanceSettingsPage::loadSettings() { + // Miscellaneous + ui->miscellaneousSettingsBox->setChecked(m_settings->get("OverrideMiscellaneous").toBool()); + ui->closeAfterLaunchCheck->setChecked(m_settings->get("CloseAfterLaunch").toBool()); + ui->quitAfterGameStopCheck->setChecked(m_settings->get("QuitAfterGameStop").toBool()); + // Console ui->consoleSettingsBox->setChecked(m_settings->get("OverrideConsole").toBool()); ui->showConsoleCheck->setChecked(m_settings->get("ShowConsole").toBool()); diff --git a/launcher/ui/pages/instance/InstanceSettingsPage.ui b/launcher/ui/pages/instance/InstanceSettingsPage.ui index 5db2d147..cb66b3ce 100644 --- a/launcher/ui/pages/instance/InstanceSettingsPage.ui +++ b/launcher/ui/pages/instance/InstanceSettingsPage.ui @@ -349,6 +349,35 @@
+ + + + Miscellaneous + + + true + + + false + + + + + + Close the launcher after game window opens + + + + + + + Quit the launcher after game window closes + + + + + + diff --git a/launcher/ui/pages/instance/ScreenshotsPage.cpp b/launcher/ui/pages/instance/ScreenshotsPage.cpp index e694ebe3..2cf17b32 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.cpp +++ b/launcher/ui/pages/instance/ScreenshotsPage.cpp @@ -251,7 +251,7 @@ ScreenshotsPage::ScreenshotsPage(QString path, QWidget *parent) m_model.reset(new QFileSystemModel()); m_filterModel.reset(new FilterModel()); m_filterModel->setSourceModel(m_model.get()); - m_model->setFilter(QDir::Files | QDir::Writable | QDir::Readable); + m_model->setFilter(QDir::Files); m_model->setReadOnly(false); m_model->setNameFilters({"*.png"}); m_model->setNameFilterDisables(false); @@ -343,6 +343,29 @@ void ScreenshotsPage::onItemActivated(QModelIndex index) DesktopServices::openFile(info.absoluteFilePath()); } +void ScreenshotsPage::onCurrentSelectionChanged(const QItemSelection &selected) +{ + bool allReadable = !selected.isEmpty(); + bool allWritable = !selected.isEmpty(); + + for (auto index : selected.indexes()) + { + if (!index.isValid()) + break; + auto info = m_model->fileInfo(index); + if (!info.isReadable()) + allReadable = false; + if (!info.isWritable()) + allWritable = false; + } + + ui->actionUpload->setEnabled(allReadable); + ui->actionCopy_Image->setEnabled(allReadable); + ui->actionCopy_File_s->setEnabled(allReadable); + ui->actionDelete->setEnabled(allWritable); + ui->actionRename->setEnabled(allWritable); +} + void ScreenshotsPage::on_actionView_Folder_triggered() { DesktopServices::openDirectory(m_folder, true); @@ -503,6 +526,8 @@ void ScreenshotsPage::openedImpl() if(idx.isValid()) { ui->listView->setModel(m_filterModel.get()); + connect(ui->listView->selectionModel(), &QItemSelectionModel::selectionChanged, this, &ScreenshotsPage::onCurrentSelectionChanged); + onCurrentSelectionChanged(ui->listView->selectionModel()->selection()); // set initial button enable states ui->listView->setRootIndex(m_filterModel->mapFromSource(idx)); } else diff --git a/launcher/ui/pages/instance/ScreenshotsPage.h b/launcher/ui/pages/instance/ScreenshotsPage.h index 50cf1a17..c22706af 100644 --- a/launcher/ui/pages/instance/ScreenshotsPage.h +++ b/launcher/ui/pages/instance/ScreenshotsPage.h @@ -100,6 +100,7 @@ private slots: void on_actionRename_triggered(); void on_actionView_Folder_triggered(); void onItemActivated(QModelIndex); + void onCurrentSelectionChanged(const QItemSelection &selected); void ShowContextMenu(const QPoint &pos); private: diff --git a/launcher/ui/pages/modplatform/ImportPage.cpp b/launcher/ui/pages/modplatform/ImportPage.cpp index 487bf77b..c7bc13d8 100644 --- a/launcher/ui/pages/modplatform/ImportPage.cpp +++ b/launcher/ui/pages/modplatform/ImportPage.cpp @@ -2,6 +2,7 @@ /* * PolyMC - Minecraft Launcher * Copyright (c) 2022 Jamie Mansfield + * Copyright (c) 2022 Sefa Eyeoglu * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -109,7 +110,11 @@ void ImportPage::updateState() { // FIXME: actually do some validation of what's inside here... this is fake AF QFileInfo fi(input); - if(fi.exists() && fi.suffix() == "zip") + // mrpack is a modrinth pack + + // Allow non-latin people to use ZIP files! + auto zip = QMimeDatabase().mimeTypeForUrl(url).suffixes().contains("zip"); + if(fi.exists() && (zip || fi.suffix() == "mrpack")) { QFileInfo fi(url.fileName()); dialog->setSuggestedPack(fi.completeBaseName(), new InstanceImportTask(url)); @@ -143,7 +148,9 @@ void ImportPage::setUrl(const QString& url) void ImportPage::on_modpackBtn_clicked() { - const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), tr("Zip (*.zip)")); + auto filter = QMimeDatabase().mimeTypeForName("application/zip").filterString(); + filter += ";;" + tr("Modrinth pack (*.mrpack)"); + const QUrl url = QFileDialog::getOpenFileUrl(this, tr("Choose modpack"), modpackUrl(), filter); if (url.isValid()) { if (url.isLocalFile()) diff --git a/launcher/ui/pages/modplatform/ImportPage.ui b/launcher/ui/pages/modplatform/ImportPage.ui index eb63cbe9..77bc5da5 100644 --- a/launcher/ui/pages/modplatform/ImportPage.ui +++ b/launcher/ui/pages/modplatform/ImportPage.ui @@ -11,28 +11,75 @@ - - - - Browse - - - - + http:// - - + + - Local file or link to a direct download: + Browse - + + + + + + The following file types are implemented (both for local files and URLs): + + + Qt::AlignCenter + + + + + + + - Curseforge modpacks (ZIP) + + + Qt::AlignCenter + + + + + + + - Modrinth modpacks (ZIP and mrpack) + + + Qt::AlignCenter + + + + + + + - PolyMC / MultiMC exported instances (ZIP) + + + Qt::AlignCenter + + + + + + + - Technic modpacks (ZIP) + + + Qt::AlignCenter + + + + + + Qt::Vertical @@ -45,6 +92,13 @@ + + + + Local file or link to a direct download: + + + diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp new file mode 100644 index 00000000..1d9f4d60 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "ModrinthModModel.h" + +#include "modplatform/modrinth/ModrinthPackIndex.h" + +namespace Modrinth { + +// NOLINTNEXTLINE(modernize-avoid-c-arrays) +const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; + +void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +{ + Modrinth::loadIndexedPack(m, obj); +} + +void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +{ + Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); +} + +auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +{ + return obj.object().value("hits").toArray(); +} + +} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h new file mode 100644 index 00000000..ae7b0bdd --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModModel.h @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (C) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include "ModrinthModPage.h" + +namespace Modrinth { + +class ListModel : public ModPlatform::ListModel { + Q_OBJECT + + public: + ListModel(ModrinthModPage* parent) : ModPlatform::ListModel(parent){}; + ~ListModel() override = default; + + private: + void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; + void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; + + auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; + + // NOLINTNEXTLINE(modernize-avoid-c-arrays) + static const char* sorts[5]; + inline auto getSorts() const -> const char** override { return sorts; }; +}; + +} // namespace Modrinth diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp new file mode 100644 index 00000000..d3a1f859 --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.cpp @@ -0,0 +1,82 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ModrinthModPage.h" +#include "modplatform/modrinth/ModrinthAPI.h" +#include "ui_ModPage.h" + +#include "ModrinthModModel.h" +#include "ui/dialogs/ModDownloadDialog.h" + +ModrinthModPage::ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance) + : ModPage(dialog, instance, new ModrinthAPI()) +{ + listModel = new Modrinth::ListModel(this); + ui->packView->setModel(listModel); + + // index is used to set the sorting with the modrinth api + ui->sortByBox->addItem(tr("Sort by Relevance")); + ui->sortByBox->addItem(tr("Sort by Downloads")); + ui->sortByBox->addItem(tr("Sort by Follows")); + ui->sortByBox->addItem(tr("Sort by Last Updated")); + ui->sortByBox->addItem(tr("Sort by Newest")); + + // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, + // so it's best not to connect them in the parent's constructor... + connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); + connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthModPage::onSelectionChanged); + connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthModPage::onVersionSelectionChanged); + connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthModPage::onModSelected); +} + +auto ModrinthModPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool +{ + auto loaderStrings = ModrinthAPI::getModLoaderStrings(loader); + + auto loaderCompatible = false; + for (auto remoteLoader : ver.loaders) + { + if (loaderStrings.contains(remoteLoader)) { + loaderCompatible = true; + break; + } + } + return ver.mcVersion.contains(mineVer) && loaderCompatible; +} + +// I don't know why, but doing this on the parent class makes it so that +// other mod providers start loading before being selected, at least with +// my Qt, so we need to implement this in every derived class... +auto ModrinthModPage::shouldDisplay() const -> bool { return true; } diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h new file mode 100644 index 00000000..b1e72bfe --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModPage.h @@ -0,0 +1,61 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 Sefa Eyeoglu + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include "modplatform/ModAPI.h" +#include "ui/pages/modplatform/ModPage.h" + +#include "modplatform/modrinth/ModrinthAPI.h" + +class ModrinthModPage : public ModPage { + Q_OBJECT + + public: + explicit ModrinthModPage(ModDownloadDialog* dialog, BaseInstance* instance); + ~ModrinthModPage() override = default; + + inline auto displayName() const -> QString override { return "Modrinth"; } + inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); } + inline auto id() const -> QString override { return "modrinth"; } + inline auto helpPage() const -> QString override { return "Mod-platform"; } + + inline auto debugName() const -> QString override { return "Modrinth"; } + inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; }; + + auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override; + + auto shouldDisplay() const -> bool override; +}; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp index b788860a..7cacf37a 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (C) 2022 Sefa Eyeoglu + * Copyright (c) 2022 flowln * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -14,30 +14,301 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. */ #include "ModrinthModel.h" -#include "modplatform/modrinth/ModrinthPackIndex.h" +#include "BuildConfig.h" +#include "Json.h" +#include "minecraft/MinecraftInstance.h" +#include "minecraft/PackProfile.h" +#include "ui/dialogs/ModDownloadDialog.h" + +#include namespace Modrinth { -// NOLINTNEXTLINE(modernize-avoid-c-arrays) -const char* ListModel::sorts[5]{ "relevance", "downloads", "follows", "updated", "newest" }; +ModpackListModel::ModpackListModel(ModrinthPage* parent) : QAbstractListModel(parent), m_parent(parent) {} -void ListModel::loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) +auto ModpackListModel::debugName() const -> QString { - Modrinth::loadIndexedPack(m, obj); + return m_parent->debugName(); } -void ListModel::loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) +/******** Make data requests ********/ + +void ModpackListModel::fetchMore(const QModelIndex& parent) { - Modrinth::loadIndexedPackVersions(m, arr, APPLICATION->network(), m_parent->m_instance); + if (parent.isValid()) + return; + if (nextSearchOffset == 0) { + qWarning() << "fetchMore with 0 offset is wrong..."; + return; + } + performPaginatedSearch(); } -auto ListModel::documentToArray(QJsonDocument& obj) const -> QJsonArray +auto ModpackListModel::data(const QModelIndex& index, int role) const -> QVariant { - return obj.object().value("hits").toArray(); + int pos = index.row(); + if (pos >= modpacks.size() || pos < 0 || !index.isValid()) { + return QString("INVALID INDEX %1").arg(pos); + } + + Modrinth::Modpack pack = modpacks.at(pos); + if (role == Qt::DisplayRole) { + return pack.name; + } else if (role == Qt::ToolTipRole) { + if (pack.description.length() > 100) { + // some magic to prevent to long tooltips and replace html linebreaks + QString edit = pack.description.left(97); + edit = edit.left(edit.lastIndexOf("
")).left(edit.lastIndexOf(" ")).append("..."); + return edit; + } + return pack.description; + } else if (role == Qt::DecorationRole) { + if (m_logoMap.contains(pack.iconName)) { + auto icon = m_logoMap.value(pack.iconName); + auto icon_scaled = QIcon(icon.pixmap(48, 48).scaledToWidth(48)); + + return icon_scaled; + } + QIcon icon = APPLICATION->getThemedIcon("screenshot-placeholder"); + ((ModpackListModel*)this)->requestLogo(pack.iconName, pack.iconUrl.toString()); + return icon; + } else if (role == Qt::UserRole) { + QVariant v; + v.setValue(pack); + return v; + } + + return {}; +} + +void ModpackListModel::performPaginatedSearch() +{ + // TODO: Move to standalone API + NetJob* netJob = new NetJob("Modrinth::SearchModpack", APPLICATION->network()); + auto searchAllUrl = QString(BuildConfig.MODRINTH_PROD_URL + + "/search?" + "offset=%1&" + "limit=%2&" + "query=%3&" + "index=%4&" + "facets=[[\"project_type:modpack\"]]") + .arg(nextSearchOffset) + .arg(m_modpacks_per_page) + .arg(currentSearchTerm) + .arg(currentSort); + + netJob->addNetAction(Net::Download::makeByteArray(QUrl(searchAllUrl), &m_all_response)); + + QObject::connect(netJob, &NetJob::succeeded, this, [this] { + QJsonParseError parse_error_all{}; + + QJsonDocument doc_all = QJsonDocument::fromJson(m_all_response, &parse_error_all); + if (parse_error_all.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from " << debugName() << " at " << parse_error_all.offset + << " reason: " << parse_error_all.errorString(); + qWarning() << m_all_response; + return; + } + + searchRequestFinished(doc_all); + }); + QObject::connect(netJob, &NetJob::failed, this, &ModpackListModel::searchRequestFailed); + + jobPtr = netJob; + jobPtr->start(); +} + +void ModpackListModel::refresh() +{ + if (jobPtr) { + jobPtr->abort(); + searchState = ResetRequested; + return; + } else { + beginResetModel(); + modpacks.clear(); + endResetModel(); + searchState = None; + } + nextSearchOffset = 0; + performPaginatedSearch(); +} + +static auto sortFromIndex(int index) -> QString +{ + switch(index){ + default: + case 1: + return "relevance"; + case 2: + return "downloads"; + case 3: + return "follows"; + case 4: + return "newest"; + case 5: + return "updated"; + } + + return {}; +} + +void ModpackListModel::searchWithTerm(const QString& term, const int sort) +{ + if(sort > 5 || sort < 0) + return; + + auto sort_str = sortFromIndex(sort); + + if (currentSearchTerm == term && currentSearchTerm.isNull() == term.isNull() && currentSort == sort_str) { + return; + } + + currentSearchTerm = term; + currentSort = sort_str; + + refresh(); +} + +void ModpackListModel::getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback) +{ + if (m_logoMap.contains(logo)) { + callback(APPLICATION->metacache() + ->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0))) + ->getFullPath()); + } else { + requestLogo(logo, logoUrl); + } +} + +void ModpackListModel::requestLogo(QString logo, QString url) +{ + if (m_loadingLogos.contains(logo) || m_failedLogos.contains(logo)) { + return; + } + + MetaEntryPtr entry = + APPLICATION->metacache()->resolveEntry("ModrinthPacks", QString("logos/%1").arg(logo.section(".", 0, 0))); + auto job = new NetJob(QString("%1 Icon Download %2").arg(m_parent->debugName()).arg(logo), APPLICATION->network()); + job->addNetAction(Net::Download::makeCached(QUrl(url), entry)); + + auto fullPath = entry->getFullPath(); + QObject::connect(job, &NetJob::succeeded, this, [this, logo, fullPath, job] { + job->deleteLater(); + emit logoLoaded(logo, QIcon(fullPath)); + if (waitingCallbacks.contains(logo)) { + waitingCallbacks.value(logo)(fullPath); + } + }); + + QObject::connect(job, &NetJob::failed, this, [this, logo, job] { + job->deleteLater(); + emit logoFailed(logo); + }); + + job->start(); + m_loadingLogos.append(logo); +} + +/******** Request callbacks ********/ + +void ModpackListModel::logoLoaded(QString logo, QIcon out) +{ + m_loadingLogos.removeAll(logo); + m_logoMap.insert(logo, out); + for (int i = 0; i < modpacks.size(); i++) { + if (modpacks[i].iconName == logo) { + emit dataChanged(createIndex(i, 0), createIndex(i, 0), { Qt::DecorationRole }); + } + } +} + +void ModpackListModel::logoFailed(QString logo) +{ + m_failedLogos.append(logo); + m_loadingLogos.removeAll(logo); +} + +void ModpackListModel::searchRequestFinished(QJsonDocument& doc_all) +{ + jobPtr.reset(); + + QList newList; + + auto packs_all = doc_all.object().value("hits").toArray(); + for (auto packRaw : packs_all) { + auto packObj = packRaw.toObject(); + + Modrinth::Modpack pack; + try { + Modrinth::loadIndexedPack(pack, packObj); + newList.append(pack); + } catch (const JSONValidationError& e) { + qWarning() << "Error while loading mod from " << m_parent->debugName() << ": " << e.cause(); + continue; + } + } + + if (packs_all.size() < m_modpacks_per_page) { + searchState = Finished; + } else { + nextSearchOffset += m_modpacks_per_page; + searchState = CanPossiblyFetchMore; + } + + beginInsertRows(QModelIndex(), modpacks.size(), modpacks.size() + newList.size() - 1); + modpacks.append(newList); + endInsertRows(); +} + +void ModpackListModel::searchRequestFailed(QString reason) +{ + if (!jobPtr->first()->m_reply) { + // Network error + QMessageBox::critical(nullptr, tr("Error"), tr("A network error occurred. Could not load modpacks.")); + } else if (jobPtr->first()->m_reply && jobPtr->first()->m_reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 409) { + // 409 Gone, notify user to update + QMessageBox::critical(nullptr, tr("Error"), + //: %1 refers to the launcher itself + QString("%1 %2") + .arg(m_parent->displayName()) + .arg(tr("API version too old!\nPlease update %1!").arg(BuildConfig.LAUNCHER_NAME))); + } + jobPtr.reset(); + + if (searchState == ResetRequested) { + beginResetModel(); + modpacks.clear(); + endResetModel(); + + nextSearchOffset = 0; + performPaginatedSearch(); + } else { + searchState = Finished; + } } } // namespace Modrinth + +/******** Helpers ********/ diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h index 45a6090a..14aa6747 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthModel.h @@ -1,25 +1,116 @@ +// SPDX-License-Identifier: GPL-3.0-only +/* + * PolyMC - Minecraft Launcher + * Copyright (c) 2022 flowln + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * This file incorporates work covered by the following copyright and + * permission notice: + * + * Copyright 2013-2021 MultiMC Contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + #pragma once -#include "ModrinthPage.h" +#include + +#include "modplatform/modrinth/ModrinthPackManifest.h" +#include "ui/pages/modplatform/modrinth/ModrinthPage.h" + +class ModPage; +class Version; namespace Modrinth { -class ListModel : public ModPlatform::ListModel { +using LogoMap = QMap; +using LogoCallback = std::function; + +class ModpackListModel : public QAbstractListModel { Q_OBJECT public: - ListModel(ModrinthPage* parent) : ModPlatform::ListModel(parent){}; - ~ListModel() override = default; + ModpackListModel(ModrinthPage* parent); + ~ModpackListModel() override = default; - private: - void loadIndexedPack(ModPlatform::IndexedPack& m, QJsonObject& obj) override; - void loadIndexedPackVersions(ModPlatform::IndexedPack& m, QJsonArray& arr) override; - - auto documentToArray(QJsonDocument& obj) const -> QJsonArray override; + inline auto rowCount(const QModelIndex& parent) const -> int override { return modpacks.size(); }; + inline auto columnCount(const QModelIndex& parent) const -> int override { return 1; }; + inline auto flags(const QModelIndex& index) const -> Qt::ItemFlags override { return QAbstractListModel::flags(index); }; - // NOLINTNEXTLINE(modernize-avoid-c-arrays) - static const char* sorts[5]; - inline auto getSorts() const -> const char** override { return sorts; }; + auto debugName() const -> QString; + + /* Retrieve information from the model at a given index with the given role */ + auto data(const QModelIndex& index, int role) const -> QVariant override; + + inline void setActiveJob(NetJob::Ptr ptr) { jobPtr = ptr; } + + /* Ask the API for more information */ + void fetchMore(const QModelIndex& parent) override; + void refresh(); + void searchWithTerm(const QString& term, const int sort); + + void getLogo(const QString& logo, const QString& logoUrl, LogoCallback callback); + + inline auto canFetchMore(const QModelIndex& parent) const -> bool override { return searchState == CanPossiblyFetchMore; }; + + public slots: + void searchRequestFinished(QJsonDocument& doc_all); + void searchRequestFailed(QString reason); + + protected slots: + + void logoFailed(QString logo); + void logoLoaded(QString logo, QIcon out); + + void performPaginatedSearch(); + + protected: + void requestLogo(QString file, QString url); + + inline auto getMineVersions() const -> std::list; + + protected: + ModrinthPage* m_parent; + + QList modpacks; + + LogoMap m_logoMap; + QMap waitingCallbacks; + QStringList m_failedLogos; + QStringList m_loadingLogos; + + QString currentSearchTerm; + QString currentSort; + int nextSearchOffset = 0; + enum SearchState { None, CanPossiblyFetchMore, ResetRequested, Finished } searchState = None; + + NetJob::Ptr jobPtr; + + QByteArray m_all_response; + QByteArray m_specific_response; + + int m_modpacks_per_page = 20; }; - -} // namespace Modrinth +} // namespace ModPlatform diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp index 98bde0ae..9bd24b57 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.cpp @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (c) 2022 flowln * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ * permission notice: * * Copyright 2013-2021 MultiMC Contributors + * Copyright 2021-2022 kb1000 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,49 +35,237 @@ */ #include "ModrinthPage.h" -#include "modplatform/modrinth/ModrinthAPI.h" -#include "ui_ModPage.h" +#include "ui_ModrinthPage.h" #include "ModrinthModel.h" -#include "ui/dialogs/ModDownloadDialog.h" -ModrinthPage::ModrinthPage(ModDownloadDialog* dialog, BaseInstance* instance) - : ModPage(dialog, instance, new ModrinthAPI()) +#include "BuildConfig.h" +#include "InstanceImportTask.h" +#include "Json.h" + +#include + +#include +#include +#include + +ModrinthPage::ModrinthPage(NewInstanceDialog* dialog, QWidget* parent) : QWidget(parent), ui(new Ui::ModrinthPage), dialog(dialog) { - listModel = new Modrinth::ListModel(this); - ui->packView->setModel(listModel); + ui->setupUi(this); + + connect(ui->searchButton, &QPushButton::clicked, this, &ModrinthPage::triggerSearch); + ui->searchEdit->installEventFilter(this); + m_model = new Modrinth::ModpackListModel(this); + ui->packView->setModel(m_model); + + ui->versionSelectionBox->view()->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded); + ui->versionSelectionBox->view()->parentWidget()->setMaximumHeight(300); - // index is used to set the sorting with the modrinth api ui->sortByBox->addItem(tr("Sort by Relevance")); - ui->sortByBox->addItem(tr("Sort by Downloads")); + ui->sortByBox->addItem(tr("Sort by Total Downloads")); ui->sortByBox->addItem(tr("Sort by Follows")); - ui->sortByBox->addItem(tr("Sort by Last Updated")); ui->sortByBox->addItem(tr("Sort by Newest")); + ui->sortByBox->addItem(tr("Sort by Last Updated")); - // sometimes Qt just ignores virtual slots and doesn't work as intended it seems, - // so it's best not to connect them in the parent's constructor... connect(ui->sortByBox, SIGNAL(currentIndexChanged(int)), this, SLOT(triggerSearch())); connect(ui->packView->selectionModel(), &QItemSelectionModel::currentChanged, this, &ModrinthPage::onSelectionChanged); connect(ui->versionSelectionBox, &QComboBox::currentTextChanged, this, &ModrinthPage::onVersionSelectionChanged); - connect(ui->modSelectionButton, &QPushButton::clicked, this, &ModrinthPage::onModSelected); } -auto ModrinthPage::validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader) const -> bool +ModrinthPage::~ModrinthPage() { - auto loaderStrings = ModrinthAPI::getModLoaderStrings(loader); + delete ui; +} + +void ModrinthPage::retranslate() +{ + ui->retranslateUi(this); +} + +void ModrinthPage::openedImpl() +{ + BasePage::openedImpl(); + triggerSearch(); +} + +bool ModrinthPage::eventFilter(QObject* watched, QEvent* event) +{ + if (watched == ui->searchEdit && event->type() == QEvent::KeyPress) { + auto* keyEvent = reinterpret_cast(event); + if (keyEvent->key() == Qt::Key_Return) { + this->triggerSearch(); + keyEvent->accept(); + return true; + } + } + return QObject::eventFilter(watched, event); +} + +void ModrinthPage::onSelectionChanged(QModelIndex first, QModelIndex second) +{ + ui->versionSelectionBox->clear(); + + if (!first.isValid()) { + if (isOpened) { + dialog->setSuggestedPack(); + } + return; + } + + current = m_model->data(first, Qt::UserRole).value(); + auto name = current.name; + + if (!current.extraInfoLoaded) { + qDebug() << "Loading modrinth modpack information"; + + auto netJob = new NetJob(QString("Modrinth::PackInformation(%1)").arg(current.name), APPLICATION->network()); + auto response = new QByteArray(); + + QString id = current.id; + + netJob->addNetAction(Net::Download::makeByteArray(QString("%1/project/%2").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); + + QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] { + if (id != current.id) { + return; // wrong request? + } + + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + auto obj = Json::requireObject(doc); + + try { + Modrinth::loadIndexedInfo(current, obj); + } catch (const JSONValidationError& e) { + qDebug() << *response; + qWarning() << "Error while reading modrinth modpack version: " << e.cause(); + } + + updateUI(); + suggestCurrent(); + }); + QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { + netJob->deleteLater(); + delete response; + }); + netJob->start(); + } else + updateUI(); + + if (!current.versionsLoaded) { + qDebug() << "Loading modrinth modpack versions"; + + auto netJob = new NetJob(QString("Modrinth::PackVersions(%1)").arg(current.name), APPLICATION->network()); + auto response = new QByteArray(); + + QString id = current.id; + + netJob->addNetAction( + Net::Download::makeByteArray(QString("%1/project/%2/version").arg(BuildConfig.MODRINTH_PROD_URL, id), response)); + + QObject::connect(netJob, &NetJob::succeeded, this, [this, response, id] { + if (id != current.id) { + return; // wrong request? + } + + QJsonParseError parse_error; + QJsonDocument doc = QJsonDocument::fromJson(*response, &parse_error); + if (parse_error.error != QJsonParseError::NoError) { + qWarning() << "Error while parsing JSON response from Modrinth at " << parse_error.offset + << " reason: " << parse_error.errorString(); + qWarning() << *response; + return; + } + + try { + Modrinth::loadIndexedVersions(current, doc); + } catch (const JSONValidationError& e) { + qDebug() << *response; + qWarning() << "Error while reading modrinth modpack version: " << e.cause(); + } + + for (auto version : current.versions) { + ui->versionSelectionBox->addItem(version.version, QVariant(version.id)); + } + + suggestCurrent(); + }); + QObject::connect(netJob, &NetJob::finished, this, [response, netJob] { + netJob->deleteLater(); + delete response; + }); + netJob->start(); + + } else { + for (auto version : current.versions) { + ui->versionSelectionBox->addItem(QString("%1 - %2").arg(version.name, version.version), QVariant(version.id)); + } + + suggestCurrent(); + } +} + +void ModrinthPage::updateUI() +{ + QString text = ""; + + if (current.extra.projectUrl.isEmpty()) + text = current.name; + else + text = "" + current.name + ""; + + // TODO: Implement multiple authors with links + text += "
" + tr(" by ") + QString("%2").arg(std::get<1>(current.author).toString(), std::get<0>(current.author)); + + text += "
"; + + HoeDown h; + text += h.process(current.extra.body.toUtf8()); + + ui->packDescription->setHtml(text + current.description); +} + +void ModrinthPage::suggestCurrent() +{ + if (!isOpened) { + return; + } + + if (selectedVersion.isEmpty()) { + dialog->setSuggestedPack(); + return; + } + + for (auto& ver : current.versions) { + if (ver.id == selectedVersion) { + dialog->setSuggestedPack(current.name + " " + ver.version, new InstanceImportTask(ver.download_url, this)); + auto iconName = current.iconName; + m_model->getLogo(iconName, current.iconUrl.toString(), + [this, iconName](QString logo) { dialog->setSuggestedIconFromFile(logo, iconName); }); - auto loaderCompatible = false; - for (auto remoteLoader : ver.loaders) - { - if (loaderStrings.contains(remoteLoader)) { - loaderCompatible = true; break; } } - return ver.mcVersion.contains(mineVer) && loaderCompatible; } -// I don't know why, but doing this on the parent class makes it so that -// other mod providers start loading before being selected, at least with -// my Qt, so we need to implement this in every derived class... -auto ModrinthPage::shouldDisplay() const -> bool { return true; } +void ModrinthPage::triggerSearch() +{ + m_model->searchWithTerm(ui->searchEdit->text(), ui->sortByBox->currentIndex()); +} + +void ModrinthPage::onVersionSelectionChanged(QString data) +{ + if (data.isNull() || data.isEmpty()) { + selectedVersion = ""; + return; + } + selectedVersion = ui->versionSelectionBox->currentData().toString(); + suggestCurrent(); +} diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h index e3a0e1f0..db5e1a3d 100644 --- a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.h @@ -1,7 +1,7 @@ // SPDX-License-Identifier: GPL-3.0-only /* * PolyMC - Minecraft Launcher - * Copyright (c) 2022 Sefa Eyeoglu + * Copyright (c) 2022 flowln * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,6 +19,7 @@ * permission notice: * * Copyright 2013-2021 MultiMC Contributors + * Copyright 2021-2022 kb1000 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,27 +36,56 @@ #pragma once -#include "modplatform/ModAPI.h" -#include "ui/pages/modplatform/ModPage.h" +#include "Application.h" +#include "ui/dialogs/NewInstanceDialog.h" +#include "ui/pages/BasePage.h" -#include "modplatform/modrinth/ModrinthAPI.h" +#include "modplatform/modrinth/ModrinthPackManifest.h" -class ModrinthPage : public ModPage { +#include + +namespace Ui { +class ModrinthPage; +} + +namespace Modrinth { +class ModpackListModel; +} + +class ModrinthPage : public QWidget, public BasePage { Q_OBJECT public: - explicit ModrinthPage(ModDownloadDialog* dialog, BaseInstance* instance); - ~ModrinthPage() override = default; + explicit ModrinthPage(NewInstanceDialog* dialog, QWidget* parent = nullptr); + ~ModrinthPage() override; - inline auto displayName() const -> QString override { return "Modrinth"; } - inline auto icon() const -> QIcon override { return APPLICATION->getThemedIcon("modrinth"); } - inline auto id() const -> QString override { return "modrinth"; } - inline auto helpPage() const -> QString override { return "Mod-platform"; } + QString displayName() const override { return tr("Modrinth"); } + QIcon icon() const override { return APPLICATION->getThemedIcon("modrinth"); } + QString id() const override { return "modrinth"; } + QString helpPage() const override { return "Modrinth-platform"; } - inline auto debugName() const -> QString override { return "Modrinth"; } - inline auto metaEntryBase() const -> QString override { return "ModrinthPacks"; }; + inline auto debugName() const -> QString { return "Modrinth"; } + inline auto metaEntryBase() const -> QString { return "ModrinthModpacks"; }; - auto validateVersion(ModPlatform::IndexedVersion& ver, QString mineVer, ModAPI::ModLoaderType loader = ModAPI::Unspecified) const -> bool override; + auto getCurrent() -> Modrinth::Modpack& { return current; } + void suggestCurrent(); - auto shouldDisplay() const -> bool override; + void updateUI(); + + void retranslate() override; + void openedImpl() override; + bool eventFilter(QObject* watched, QEvent* event) override; + + private slots: + void onSelectionChanged(QModelIndex first, QModelIndex second); + void onVersionSelectionChanged(QString data); + void triggerSearch(); + + private: + Ui::ModrinthPage* ui; + NewInstanceDialog* dialog; + Modrinth::ModpackListModel* m_model; + + Modrinth::Modpack current; + QString selectedVersion; }; diff --git a/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui new file mode 100644 index 00000000..4fb59cdf --- /dev/null +++ b/launcher/ui/pages/modplatform/modrinth/ModrinthPage.ui @@ -0,0 +1,112 @@ + + + ModrinthPage + + + + 0 + 0 + 837 + 685 + + + + + + + + true + + + + Note: Modrinth modpacks are still in alpha phase. Some things may be rough on the edges, or not working at all! Use it with caution. + + + Qt::AlignCenter + + + + + + + + + Search and filter ... + + + + + + + Search + + + + + + + + + + + Qt::ScrollBarAlwaysOff + + + true + + + + 48 + 48 + + + + true + + + + + + + true + + + true + + + + + + + + + + + + + + Version selected: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + + searchEdit + searchButton + packView + packDescription + sortByBox + versionSelectionBox + + + + diff --git a/program_info/App.entitlements b/program_info/App.entitlements index 1850b990..b46e8ff2 100644 --- a/program_info/App.entitlements +++ b/program_info/App.entitlements @@ -2,11 +2,13 @@ - com.apple.security.cs.disable-library-validation - - com.apple.security.device.audio-input - - com.apple.security.device.camera - + com.apple.security.cs.disable-library-validation + + com.apple.security.cs.allow-dyld-environment-variables + + com.apple.security.device.audio-input + + com.apple.security.device.camera +