Skip to content
Draft
16 changes: 13 additions & 3 deletions .github/workflows/ios.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,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/
Comment on lines +87 to +91
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/<config>/, 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/<config>/, not build/.
artifact-source-path: ${{ runner.temp }}/build/${{ matrix.build_type }}/${{ env.PACKAGE }}.ipa
package-name: ${{ env.PACKAGE }}
upload-aws: 'false'
2 changes: 2 additions & 0 deletions cmake/find-modules/FindQGCGStreamer.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
64 changes: 63 additions & 1 deletion cmake/platform/Apple.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Expand Down Expand Up @@ -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
"$<TARGET_FILE_DIR:GStreamerMobile>"
"$<TARGET_BUNDLE_DIR:${target}>/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
Expand Down Expand Up @@ -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}"
"$<TARGET_BUNDLE_DIR:${CMAKE_PROJECT_NAME}>/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()
67 changes: 67 additions & 0 deletions deploy/ios/iOSBundleInfo.plist.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundlePackageType</key>
<string>APPL</string>

<key>CFBundleName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleDisplayName</key>
<string>${MACOSX_BUNDLE_BUNDLE_NAME}</string>
<key>CFBundleIdentifier</key>
<string>${MACOSX_BUNDLE_GUI_IDENTIFIER}</string>
<key>CFBundleExecutable</key>
<string>${MACOSX_BUNDLE_EXECUTABLE_NAME}</string>

<key>CFBundleVersion</key>
<string>${MACOSX_BUNDLE_BUNDLE_VERSION}</string>
<key>CFBundleShortVersionString</key>
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>

<key>NSHumanReadableCopyright</key>
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>

<key>CFBundleDevelopmentRegion</key>
<string>en</string>

<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>${QGC_IOS_DEPLOYMENT_TARGET}</string>

<key>UIRequiresFullScreen</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>QGCLaunchScreen</string>

<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>

<key>UIFileSharingEnabled</key>
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>

<key>NSCameraUsageDescription</key>
<string>QGC uses UVC devices for video streaming.</string>
<key>NSMicrophoneUsageDescription</key>
<string>Qt Multimedia for iOS uses the camera and microphone.</string>
<key>NSLocationUsageDescription</key>
<string>Ground Station Location</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>Ground Station Location</string>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>Bluetooth is used to connect to vehicles</string>
</dict>
</plist>
4 changes: 4 additions & 0 deletions src/VideoManager/VideoReceiver/GStreamer/GStreamer.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand Down
Loading