diff --git a/.github/workflows/ios.yml b/.github/workflows/ios.yml index e108335a8084..47ef4ded9137 100644 --- a/.github/workflows/ios.yml +++ b/.github/workflows/ios.yml @@ -83,12 +83,22 @@ jobs: build-dir: ${{ runner.temp }}/build build-type: ${{ matrix.build_type }} + - name: Package IPA + if: matrix.build_type == 'Release' + run: | + APP_DIR="${{ runner.temp }}/build/${{ matrix.build_type }}" + cd "$APP_DIR" + mkdir -p Payload + cp -r "${{ env.PACKAGE }}.app" Payload/ + zip -r "${{ env.PACKAGE }}.ipa" Payload/ + rm -rf Payload + - name: Attest and Upload if: matrix.build_type == 'Release' uses: ./.github/actions/attest-and-upload with: - artifact-name: ${{ env.PACKAGE }}.app - # iOS uses Ninja Multi-Config; the .app lands in build//, not build/. - artifact-source-path: ${{ runner.temp }}/build/${{ matrix.build_type }}/${{ env.PACKAGE }}.app + artifact-name: ${{ env.PACKAGE }}.ipa + # iOS uses Ninja Multi-Config; the .ipa lands in build//, not build/. + artifact-source-path: ${{ runner.temp }}/build/${{ matrix.build_type }}/${{ env.PACKAGE }}.ipa package-name: ${{ env.PACKAGE }} upload-aws: 'false' diff --git a/cmake/find-modules/FindQGCGStreamer.cmake b/cmake/find-modules/FindQGCGStreamer.cmake index e3a75afc7fbc..0739109c0875 100644 --- a/cmake/find-modules/FindQGCGStreamer.cmake +++ b/cmake/find-modules/FindQGCGStreamer.cmake @@ -808,6 +808,8 @@ if(NOT DEFINED GSTREAMER_PLUGINS) # Deferred for all platforms — GStreamer_VERSION is populated after find_package(GStreamer) below. if(ANDROID) list(APPEND GSTREAMER_PLUGINS androidmedia dav1d) + elseif(IOS) + list(REMOVE_ITEM GSTREAMER_PLUGINS libav) elseif(APPLE) list(APPEND GSTREAMER_PLUGINS applemedia dav1d vulkan) elseif(WIN32) diff --git a/cmake/platform/Apple.cmake b/cmake/platform/Apple.cmake index 4abb2bb7d5a5..e278c5c665bd 100644 --- a/cmake/platform/Apple.cmake +++ b/cmake/platform/Apple.cmake @@ -29,9 +29,15 @@ endif() # ---------------------------------------------------------------------------- cmake_path(GET QGC_MACOS_ICON_PATH FILENAME MACOSX_BUNDLE_ICON_FILE) +if(IOS) + set(_qgc_bundle_plist "${CMAKE_SOURCE_DIR}/deploy/ios/iOSBundleInfo.plist.in") +else() + set(_qgc_bundle_plist "${QGC_MACOS_PLIST_PATH}") +endif() + set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES - MACOSX_BUNDLE_INFO_PLIST "${QGC_MACOS_PLIST_PATH}" + MACOSX_BUNDLE_INFO_PLIST "${_qgc_bundle_plist}" MACOSX_BUNDLE_BUNDLE_NAME "${CMAKE_PROJECT_NAME}" MACOSX_BUNDLE_BUNDLE_VERSION "${CMAKE_PROJECT_VERSION}" MACOSX_BUNDLE_COPYRIGHT "${QGC_APP_COPYRIGHT}" @@ -61,6 +67,19 @@ if(MACOS) message(STATUS "QGC: macOS platform configuration applied") elseif(IOS) + function(_qgc_ios_embed_gstreamer_mobile target) + if(NOT TARGET GStreamerMobile) + return() + endif() + add_custom_command(TARGET "${target}" POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "$" + "$/Frameworks/gstreamer_mobile.framework" + COMMENT "Embedding gstreamer_mobile.framework" + VERBATIM + ) + message(STATUS "QGC: GStreamerMobile will be embedded at build time (Ninja)") + endfunction() # iOS-specific configuration # set(CMAKE_XCODE_ATTRIBUTE_ARCHS @@ -90,5 +109,48 @@ elseif(IOS) qt_add_ios_ffmpeg_libraries(${CMAKE_PROJECT_NAME}) endif() + # With Ninja generator, Xcode's "Embed Frameworks" build phase doesn't run. + # Manually copy FFmpeg xcframeworks into the bundle and set rpath so dyld + # finds them on device instead of the CI runner's absolute Qt path. + if(NOT CMAKE_GENERATOR MATCHES "Xcode") + cmake_path(GET Qt6_DIR PARENT_PATH _qt_cmake_dir) + cmake_path(GET _qt_cmake_dir PARENT_PATH _qt_lib_dir) + set(_ffmpeg_xcfw_dir "${_qt_lib_dir}/ffmpeg") + + if(EXISTS "${_ffmpeg_xcfw_dir}") + file(GLOB _xcframeworks LIST_DIRECTORIES true "${_ffmpeg_xcfw_dir}/*.xcframework") + foreach(_xcfw IN LISTS _xcframeworks) + cmake_path(GET _xcfw STEM _fw_name) + foreach(_slice ios-arm64 ios-arm64_arm64e) + set(_fw_src "${_xcfw}/${_slice}/${_fw_name}.framework") + if(EXISTS "${_fw_src}") + add_custom_command(TARGET ${CMAKE_PROJECT_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E copy_directory + "${_fw_src}" + "$/Frameworks/${_fw_name}.framework" + COMMENT "Embedding ${_fw_name}.framework" + VERBATIM + ) + break() + endif() + endforeach() + endforeach() + message(STATUS "QGC: FFmpeg xcframeworks will be embedded at build time (Ninja)") + else() + message(STATUS "QGC: No FFmpeg xcframeworks found at ${_ffmpeg_xcfw_dir}") + endif() + + set_target_properties(${CMAKE_PROJECT_NAME} PROPERTIES + BUILD_RPATH "@executable_path/Frameworks" + ) + + # GStreamerMobile is created by find_package(GStreamerMobile) inside + # add_subdirectory(src), which hasn't run yet. Defer the post-build + # copy until after all subdirectories are processed. + set(_qgc_target "${CMAKE_PROJECT_NAME}") + cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}" + CALL _qgc_ios_embed_gstreamer_mobile "${_qgc_target}") + endif() + message(STATUS "QGC: iOS platform configuration applied") endif() diff --git a/deploy/ios/iOSBundleInfo.plist.in b/deploy/ios/iOSBundleInfo.plist.in new file mode 100644 index 000000000000..efb725882832 --- /dev/null +++ b/deploy/ios/iOSBundleInfo.plist.in @@ -0,0 +1,67 @@ + + + + + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleDisplayName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + + CFBundleDevelopmentRegion + en + + LSRequiresIPhoneOS + + MinimumOSVersion + ${QGC_IOS_DEPLOYMENT_TARGET} + + UIRequiresFullScreen + + UILaunchStoryboardName + QGCLaunchScreen + + UISupportedInterfaceOrientations + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + UISupportedInterfaceOrientations~ipad + + UIInterfaceOrientationLandscapeLeft + UIInterfaceOrientationLandscapeRight + + + UIFileSharingEnabled + + LSSupportsOpeningDocumentsInPlace + + + NSCameraUsageDescription + QGC uses UVC devices for video streaming. + NSMicrophoneUsageDescription + Qt Multimedia for iOS uses the camera and microphone. + NSLocationUsageDescription + Ground Station Location + NSLocationWhenInUseUsageDescription + Ground Station Location + NSBluetoothAlwaysUsageDescription + Bluetooth is used to connect to vehicles + + diff --git a/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc b/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc index a6d9ce2b407b..aa4ff3dd0753 100644 --- a/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc +++ b/src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc @@ -48,7 +48,9 @@ G_BEGIN_DECLS GST_PLUGIN_STATIC_DECLARE(app); GST_PLUGIN_STATIC_DECLARE(coreelements); GST_PLUGIN_STATIC_DECLARE(isomp4); +#ifndef Q_OS_IOS GST_PLUGIN_STATIC_DECLARE(libav); +#endif GST_PLUGIN_STATIC_DECLARE(matroska); GST_PLUGIN_STATIC_DECLARE(mpegtsdemux); GST_PLUGIN_STATIC_DECLARE(openh264); @@ -128,7 +130,9 @@ void _registerPlugins() GST_PLUGIN_STATIC_REGISTER(app); GST_PLUGIN_STATIC_REGISTER(coreelements); GST_PLUGIN_STATIC_REGISTER(isomp4); +#ifndef Q_OS_IOS GST_PLUGIN_STATIC_REGISTER(libav); +#endif GST_PLUGIN_STATIC_REGISTER(matroska); GST_PLUGIN_STATIC_REGISTER(mpegtsdemux); GST_PLUGIN_STATIC_REGISTER(openh264);