summaryrefslogtreecommitdiffstats
path: root/src/plugins/platforms/ios
diff options
context:
space:
mode:
Diffstat (limited to 'src/plugins/platforms/ios')
-rw-r--r--src/plugins/platforms/ios/CMakeLists.txt19
-rw-r--r--src/plugins/platforms/ios/SwiftIntegration.cmake78
-rw-r--r--src/plugins/platforms/ios/module.modulemap4
-rw-r--r--src/plugins/platforms/ios/qiosapplication.swift198
-rw-r--r--src/plugins/platforms/ios/qiosapplicationdelegate.h5
-rw-r--r--src/plugins/platforms/ios/qioseventdispatcher.h2
-rw-r--r--src/plugins/platforms/ios/qioseventdispatcher.mm21
-rw-r--r--src/plugins/platforms/ios/qiosglobal.mm20
-rw-r--r--src/plugins/platforms/ios/qiosintegration.h31
-rw-r--r--src/plugins/platforms/ios/qiosintegration.mm50
-rw-r--r--src/plugins/platforms/ios/qiosscreen.mm5
-rw-r--r--src/plugins/platforms/ios/qioswindow.h3
-rw-r--r--src/plugins/platforms/ios/qioswindow.mm43
-rw-r--r--src/plugins/platforms/ios/quiaccessibilityelement.mm13
-rw-r--r--src/plugins/platforms/ios/quiview.mm65
15 files changed, 483 insertions, 74 deletions
diff --git a/src/plugins/platforms/ios/CMakeLists.txt b/src/plugins/platforms/ios/CMakeLists.txt
index 4cc3efc91e..51c1b52cf3 100644
--- a/src/plugins/platforms/ios/CMakeLists.txt
+++ b/src/plugins/platforms/ios/CMakeLists.txt
@@ -5,10 +5,23 @@
## QIOSIntegrationPlugin Plugin:
#####################################################################
+if(VISIONOS)
+ include(SwiftIntegration.cmake)
+
+ qt_install(TARGETS QIOSIntegrationPluginSwift
+ EXPORT "${INSTALL_CMAKE_NAMESPACE}QIOSIntegrationPluginTargets"
+ DESTINATION "${INSTALL_LIBDIR}"
+ )
+ qt_internal_add_targets_to_additional_targets_export_file(
+ TARGETS QIOSIntegrationPluginSwift
+ EXPORT_NAME_PREFIX "${INSTALL_CMAKE_NAMESPACE}QIOSIntegrationPlugin"
+ )
+endif()
+
qt_internal_add_plugin(QIOSIntegrationPlugin
OUTPUT_NAME qios
STATIC # Force static, even in shared builds
- DEFAULT_IF ${QT_QPA_DEFAULT_PLATFORM} MATCHES ios
+ DEFAULT_IF "ios" IN_LIST QT_QPA_PLATFORMS
PLUGIN_TYPE platforms
SOURCES
plugin.mm
@@ -86,3 +99,7 @@ qt_internal_extend_target(QIOSIntegrationPlugin CONDITION NOT (TVOS OR VISIONOS)
)
add_subdirectory(optional)
+
+if(VISIONOS)
+ target_link_libraries(QIOSIntegrationPlugin PRIVATE QIOSIntegrationPluginSwift)
+endif()
diff --git a/src/plugins/platforms/ios/SwiftIntegration.cmake b/src/plugins/platforms/ios/SwiftIntegration.cmake
new file mode 100644
index 0000000000..d52edb3ad2
--- /dev/null
+++ b/src/plugins/platforms/ios/SwiftIntegration.cmake
@@ -0,0 +1,78 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+set(CMAKE_Swift_COMPILER_TARGET arm64-apple-xros)
+if($CACHE{CMAKE_OSX_SYSROOT} MATCHES "^[a-z]+simulator$")
+ set(CMAKE_Swift_COMPILER_TARGET "${CMAKE_Swift_COMPILER_TARGET}-simulator")
+endif()
+
+cmake_policy(SET CMP0157 NEW)
+enable_language(Swift)
+
+# Verify that we have a new enough compiler
+if("${CMAKE_Swift_COMPILER_VERSION}" VERSION_LESS 5.9)
+ message(FATAL_ERROR "Swift 5.9 required for C++ interoperability")
+endif()
+
+get_target_property(QT_CORE_INCLUDES Qt6::Core INTERFACE_INCLUDE_DIRECTORIES)
+get_target_property(QT_GUI_INCLUDES Qt6::Gui INTERFACE_INCLUDE_DIRECTORIES)
+get_target_property(QT_CORE_PRIVATE_INCLUDES Qt6::CorePrivate INTERFACE_INCLUDE_DIRECTORIES)
+get_target_property(QT_GUI_PRIVATE_INCLUDES Qt6::GuiPrivate INTERFACE_INCLUDE_DIRECTORIES)
+
+set(target QIOSIntegrationPluginSwift)
+# Swift library
+set(SWIFT_SOURCES
+ "${CMAKE_CURRENT_SOURCE_DIR}/qiosapplication.swift"
+)
+add_library(${target} STATIC ${SWIFT_SOURCES})
+set_target_properties(${target} PROPERTIES
+ Swift_MODULE_NAME ${target})
+target_include_directories(${target} PUBLIC
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+ "${QT_CORE_INCLUDES}"
+ "${QT_GUI_INCLUDES}"
+ "${QT_CORE_PRIVATE_INCLUDES}"
+ "${QT_GUI_PRIVATE_INCLUDES}"
+
+)
+target_compile_options(${target} PUBLIC
+ $<$<COMPILE_LANGUAGE:Swift>:-cxx-interoperability-mode=default>
+ $<$<COMPILE_LANGUAGE:Swift>:-Xcc -std=c++17>)
+
+# Swift to C++ bridging header
+set(SWIFT_BRIDGING_HEADER "${CMAKE_CURRENT_BINARY_DIR}/qiosswiftintegration.h")
+list(TRANSFORM QT_CORE_INCLUDES PREPEND "-I")
+list(TRANSFORM QT_GUI_INCLUDES PREPEND "-I")
+list(TRANSFORM QT_CORE_PRIVATE_INCLUDES PREPEND "-I")
+list(TRANSFORM QT_GUI_PRIVATE_INCLUDES PREPEND "-I")
+add_custom_command(
+ COMMAND
+ ${CMAKE_Swift_COMPILER} -frontend -typecheck
+ ${SWIFT_SOURCES}
+ -I ${CMAKE_CURRENT_SOURCE_DIR}
+ ${QT_CORE_INCLUDES}
+ ${QT_GUI_INCLUDES}
+ ${QT_CORE_PRIVATE_INCLUDES}
+ ${QT_GUI_PRIVATE_INCLUDES}
+ -sdk ${CMAKE_OSX_SYSROOT}
+ -module-name ${target}
+ -cxx-interoperability-mode=default
+ -Xcc -std=c++17
+ -emit-clang-header-path "${SWIFT_BRIDGING_HEADER}"
+ -target ${CMAKE_Swift_COMPILER_TARGET}
+ OUTPUT
+ "${SWIFT_BRIDGING_HEADER}"
+ DEPENDS
+ ${SWIFT_SOURCES}
+ )
+
+set(header_target "${target}Header")
+add_custom_target(${header_target}
+ DEPENDS "${SWIFT_BRIDGING_HEADER}"
+)
+# Make sure the "'__bridge_transfer' casts have no effect when not using ARC"
+# warning doesn't break warnings-are-error builds.
+target_compile_options(${target} INTERFACE
+ -Wno-error=arc-bridge-casts-disallowed-in-nonarc)
+
+add_dependencies(${target} ${header_target})
diff --git a/src/plugins/platforms/ios/module.modulemap b/src/plugins/platforms/ios/module.modulemap
new file mode 100644
index 0000000000..af42b3e1f5
--- /dev/null
+++ b/src/plugins/platforms/ios/module.modulemap
@@ -0,0 +1,4 @@
+module QIOSIntegrationPlugin {
+ header "qiosapplicationdelegate.h"
+ header "qiosintegration.h"
+}
diff --git a/src/plugins/platforms/ios/qiosapplication.swift b/src/plugins/platforms/ios/qiosapplication.swift
new file mode 100644
index 0000000000..9eb172a896
--- /dev/null
+++ b/src/plugins/platforms/ios/qiosapplication.swift
@@ -0,0 +1,198 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+import SwiftUI
+import CompositorServices
+import QIOSIntegrationPlugin
+import RealityKit
+
+struct QIOSSwiftApplication: App {
+ @UIApplicationDelegateAdaptor private var appDelegate: QIOSApplicationDelegate
+
+ var body: some SwiftUI.Scene {
+ WindowGroup() {
+ ImmersiveSpaceControlView()
+ }
+
+ ImmersiveSpace(id: "QIOSImmersiveSpace") {
+ CompositorLayer(configuration: QIOSLayerConfiguration()) { layerRenderer in
+ QIOSIntegration.instance().renderCompositorLayer(layerRenderer)
+
+ // Handle any events in the scene.
+ layerRenderer.onSpatialEvent = { eventCollection in
+ QIOSIntegration.instance().handleSpatialEvents(jsonStringFromEventCollection(eventCollection))
+ }
+ }
+ }
+ // CompositorLayer immersive spaces are always full, and should not need
+ // to set the immersion style, but lacking this we get a warning in the
+ // console about not being able to "configure an immersive space with
+ // selected style 'AutomaticImmersionStyle' since it is not in the list
+ // of supported styles for this type of content: 'FullImmersionStyle'."
+ .immersionStyle(selection: .constant(.full), in: .full)
+ }
+}
+
+public struct QIOSLayerConfiguration: CompositorLayerConfiguration {
+ public func makeConfiguration(capabilities: LayerRenderer.Capabilities,
+ configuration: inout LayerRenderer.Configuration) {
+ // Use reflection to pull out underlying C handles
+ // FIXME: Use proper bridging APIs when available
+ let capabilitiesMirror = Mirror(reflecting: capabilities)
+ let configurationMirror = Mirror(reflecting: configuration)
+ QIOSIntegration.instance().configureCompositorLayer(
+ capabilitiesMirror.descendant("c_capabilities") as? cp_layer_renderer_capabilities_t,
+ configurationMirror.descendant("box", "value") as? cp_layer_renderer_configuration_t
+ )
+ }
+}
+
+public func runSwiftAppMain() {
+ QIOSSwiftApplication.main()
+}
+
+public class ImmersiveState: ObservableObject {
+ static let shared = ImmersiveState()
+ @Published var showImmersiveSpace: Bool = false
+}
+
+struct ImmersiveSpaceControlView: View {
+ @ObservedObject private var immersiveState = ImmersiveState.shared
+
+ @Environment(\.openImmersiveSpace) var openImmersiveSpace
+ @Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
+
+ var body: some View {
+ VStack {}
+ .onChange(of: immersiveState.showImmersiveSpace) { _, newValue in
+ Task {
+ if newValue {
+ await openImmersiveSpace(id: "QIOSImmersiveSpace")
+ } else {
+ await dismissImmersiveSpace()
+ }
+ }
+ }
+ }
+}
+
+public class ImmersiveSpaceManager : NSObject {
+ @objc public static func openImmersiveSpace() {
+ ImmersiveState.shared.showImmersiveSpace = true
+ }
+
+ @objc public static func dismissImmersiveSpace() {
+ ImmersiveState.shared.showImmersiveSpace = false
+ }
+}
+
+extension SpatialEventCollection.Event.Kind: Encodable {
+ enum CodingKeys: String, CodingKey {
+ case touch
+ case directPinch
+ case indirectPinch
+ case pointer
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ switch self {
+ case .touch:
+ try container.encode("touch")
+ case .directPinch:
+ try container.encode("directPinch")
+ case .indirectPinch:
+ try container.encode("indirectPinch")
+ case .pointer:
+ try container.encode("pointer")
+ @unknown default:
+ try container.encode("unknown")
+ }
+ }
+}
+extension SpatialEventCollection.Event.Phase: Encodable {
+ enum CodingKeys: String, CodingKey {
+ case active
+ case ending
+ case cancled
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.singleValueContainer()
+ switch self {
+ case .active:
+ try container.encode("active")
+ case .ended:
+ try container.encode("ended")
+ case .cancelled:
+ try container.encode("canceled")
+ @unknown default:
+ try container.encode("unknown")
+ }
+ }
+}
+extension SpatialEventCollection.Event.InputDevicePose: Encodable {
+ enum CodingKeys: String, CodingKey {
+ case altitude
+ case azimuth
+ case pose3D
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(altitude.radians, forKey: .altitude)
+ try container.encode(azimuth.radians, forKey: .azimuth)
+ try container.encode(pose3D, forKey: .pose3D)
+ }
+}
+
+extension SpatialEventCollection.Event: Encodable {
+ enum CodingKeys: String, CodingKey {
+ case id
+ case timestamp
+ case kind
+ case location
+ case phase
+ case modifierKeys
+ case inputDevicePose
+ case location3D
+ case selectionRay
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(id.hashValue, forKey: .id)
+ try container.encode(timestamp, forKey: .timestamp)
+ try container.encode(kind, forKey: .kind)
+ try container.encode(location, forKey: .location)
+ try container.encode(phase, forKey: .phase)
+ try container.encode(modifierKeys.rawValue, forKey: .modifierKeys)
+ try container.encode(inputDevicePose, forKey: .inputDevicePose)
+ try container.encode(location3D, forKey: .location3D)
+ try container.encode(selectionRay, forKey: .selectionRay)
+ }
+}
+
+extension SpatialEventCollection: Encodable {
+ enum CodingKeys: String, CodingKey {
+ case events
+ }
+
+ public func encode(to encoder: Encoder) throws {
+ var container = encoder.container(keyedBy: CodingKeys.self)
+ try container.encode(Array(self), forKey: .events)
+ }
+}
+
+func jsonStringFromEventCollection(_ eventCollection: SpatialEventCollection) -> String {
+ let encoder = JSONEncoder()
+ encoder.dateEncodingStrategy = .iso8601
+
+ do {
+ let jsonData = try encoder.encode(eventCollection)
+ return String(data: jsonData, encoding: .utf8) ?? "{}"
+ } catch {
+ print("Failed to encode event collection: \(error)")
+ return "{}"
+ }
+}
diff --git a/src/plugins/platforms/ios/qiosapplicationdelegate.h b/src/plugins/platforms/ios/qiosapplicationdelegate.h
index 39bb9fdedb..7e12d64cbf 100644
--- a/src/plugins/platforms/ios/qiosapplicationdelegate.h
+++ b/src/plugins/platforms/ios/qiosapplicationdelegate.h
@@ -1,6 +1,9 @@
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+#ifndef QIOSAPPLICATIONDELEGATE_H
+#define QIOSAPPLICATIONDELEGATE_H
+
#import <UIKit/UIKit.h>
#import <QtGui/QtGui>
@@ -8,3 +11,5 @@
@interface QIOSApplicationDelegate : UIResponder <UIApplicationDelegate>
@end
+
+#endif // QIOSAPPLICATIONDELEGATE_H
diff --git a/src/plugins/platforms/ios/qioseventdispatcher.h b/src/plugins/platforms/ios/qioseventdispatcher.h
index b40024ec19..5eee0556f5 100644
--- a/src/plugins/platforms/ios/qioseventdispatcher.h
+++ b/src/plugins/platforms/ios/qioseventdispatcher.h
@@ -16,6 +16,8 @@ public:
static QIOSEventDispatcher* create();
bool processPostedEvents() override;
+ static bool isQtApplication();
+
protected:
explicit QIOSEventDispatcher(QObject *parent = nullptr);
};
diff --git a/src/plugins/platforms/ios/qioseventdispatcher.mm b/src/plugins/platforms/ios/qioseventdispatcher.mm
index 24d9d88294..710a834bfd 100644
--- a/src/plugins/platforms/ios/qioseventdispatcher.mm
+++ b/src/plugins/platforms/ios/qioseventdispatcher.mm
@@ -5,6 +5,10 @@
#include "qiosapplicationdelegate.h"
#include "qiosglobal.h"
+#if defined(Q_OS_VISIONOS)
+#include "qiosswiftintegration.h"
+#endif
+
#include <QtCore/qprocessordetection.h>
#include <QtCore/private/qcoreapplication_p.h>
#include <QtCore/private/qthread_p.h>
@@ -173,12 +177,16 @@ namespace
QAppleLogActivity UIApplicationMain;
QAppleLogActivity applicationDidFinishLaunching;
} logActivity;
+
+ static bool s_isQtApplication = false;
}
using namespace QT_PREPEND_NAMESPACE(QtPrivate);
extern "C" int qt_main_wrapper(int argc, char *argv[])
{
+ s_isQtApplication = true;
+
@autoreleasepool {
size_t defaultStackSize = 512 * kBytesPerKiloByte; // Same as secondary threads
@@ -202,8 +210,16 @@ extern "C" int qt_main_wrapper(int argc, char *argv[])
logActivity.UIApplicationMain = QT_APPLE_LOG_ACTIVITY(
lcEventDispatcher().isDebugEnabled(), "UIApplicationMain").enter();
+#if defined(Q_OS_VISIONOS)
+ Q_UNUSED(argc);
+ Q_UNUSED(argv);
+ qCDebug(lcEventDispatcher) << "Starting Swift app";
+ QIOSIntegrationPluginSwift::runSwiftAppMain();
+ Q_UNREACHABLE();
+#else
qCDebug(lcEventDispatcher) << "Running UIApplicationMain";
return UIApplicationMain(argc, argv, nil, NSStringFromClass([QIOSApplicationDelegate class]));
+#endif
}
}
@@ -424,6 +440,11 @@ QIOSEventDispatcher::QIOSEventDispatcher(QObject *parent)
QWindowSystemInterface::setSynchronousWindowSystemEvents(true);
}
+bool QIOSEventDispatcher::isQtApplication()
+{
+ return s_isQtApplication;
+}
+
/*!
Override of the CoreFoundation posted events runloop source callback
so that we can send window system (QPA) events in addition to sending
diff --git a/src/plugins/platforms/ios/qiosglobal.mm b/src/plugins/platforms/ios/qiosglobal.mm
index 25ccf2961b..1722e09aaa 100644
--- a/src/plugins/platforms/ios/qiosglobal.mm
+++ b/src/plugins/platforms/ios/qiosglobal.mm
@@ -5,6 +5,8 @@
#include "qiosapplicationdelegate.h"
#include "qiosviewcontroller.h"
#include "qiosscreen.h"
+#include "quiwindow.h"
+#include "qioseventdispatcher.h"
#include <QtCore/private/qcore_mac_p.h>
@@ -17,17 +19,13 @@ Q_LOGGING_CATEGORY(lcQpaWindowScene, "qt.qpa.window.scene");
bool isQtApplication()
{
- if (qt_apple_isApplicationExtension())
- return false;
-
// Returns \c true if the plugin is in full control of the whole application. This means
// that we control the application delegate and the top view controller, and can take
// actions that impacts all parts of the application. The opposite means that we are
// embedded inside a native iOS application, and should be more focused on playing along
// with native UIControls, and less inclined to change structures that lies outside the
// scope of our QWindows/UIViews.
- static bool isQt = ([qt_apple_sharedApplication().delegate isKindOfClass:[QIOSApplicationDelegate class]]);
- return isQt;
+ return QIOSEventDispatcher::isQtApplication();
}
bool isRunningOnVisionOS()
@@ -126,9 +124,15 @@ UIView *rootViewForScreen(QScreen *screen)
Q_UNUSED(iosScreen);
#endif
- UIWindow *uiWindow = windowScene.keyWindow;
- if (!uiWindow && windowScene.windows.count)
- uiWindow = windowScene.windows[0];
+ UIWindow *uiWindow = qt_objc_cast<QUIWindow*>(windowScene.keyWindow);
+ if (!uiWindow) {
+ for (UIWindow *win in windowScene.windows) {
+ if (qt_objc_cast<QUIWindow*>(win)) {
+ uiWindow = win;
+ break;
+ }
+ }
+ }
return uiWindow.rootViewController.view;
}
diff --git a/src/plugins/platforms/ios/qiosintegration.h b/src/plugins/platforms/ios/qiosintegration.h
index 2c7d33cc94..6c2014d048 100644
--- a/src/plugins/platforms/ios/qiosintegration.h
+++ b/src/plugins/platforms/ios/qiosintegration.h
@@ -16,11 +16,24 @@
#include "qiostextinputoverlay.h"
#endif
+#if defined(Q_OS_VISIONOS)
+#include <swift/bridging>
+#endif
+
QT_BEGIN_NAMESPACE
+using namespace QNativeInterface;
+
class QIOSServices;
-class QIOSIntegration : public QPlatformNativeInterface, public QPlatformIntegration
+class
+#if defined(Q_OS_VISIONOS)
+ SWIFT_IMMORTAL_REFERENCE
+#endif
+QIOSIntegration : public QPlatformNativeInterface, public QPlatformIntegration
+#if defined(Q_OS_VISIONOS)
+ , public QVisionOSApplication
+#endif
{
Q_OBJECT
public:
@@ -77,6 +90,18 @@ public:
QIOSApplicationState applicationState;
+#if defined(Q_OS_VISIONOS)
+ void openImmersiveSpace() override;
+ void dismissImmersiveSpace() override;
+
+ using CompositorLayer = QVisionOSApplication::ImmersiveSpaceCompositorLayer;
+ void setImmersiveSpaceCompositorLayer(CompositorLayer *layer) override;
+
+ void configureCompositorLayer(cp_layer_renderer_capabilities_t, cp_layer_renderer_configuration_t);
+ void renderCompositorLayer(cp_layer_renderer_t);
+ void handleSpatialEvents(const char *jsonString);
+#endif
+
private:
QPlatformFontDatabase *m_fontDatabase;
#if QT_CONFIG(clipboard)
@@ -90,6 +115,10 @@ private:
#if !defined(Q_OS_TVOS) && !defined(Q_OS_VISIONOS)
QIOSTextInputOverlay m_textInputOverlay;
#endif
+
+#if defined(Q_OS_VISIONOS)
+ CompositorLayer *m_immersiveSpaceCompositorLayer = nullptr;
+#endif
};
QT_END_NAMESPACE
diff --git a/src/plugins/platforms/ios/qiosintegration.mm b/src/plugins/platforms/ios/qiosintegration.mm
index 7cd21f83f6..76173ce830 100644
--- a/src/plugins/platforms/ios/qiosintegration.mm
+++ b/src/plugins/platforms/ios/qiosintegration.mm
@@ -17,6 +17,10 @@
#include "qiosservices.h"
#include "qiosoptionalplugininterface.h"
+#if defined(Q_OS_VISIONOS)
+#include "qiosswiftintegration.h"
+#endif
+
#include <QtGui/qpointingdevice.h>
#include <QtGui/private/qguiapplication_p.h>
#include <QtGui/private/qrhibackingstore_p.h>
@@ -296,6 +300,52 @@ void QIOSIntegration::setApplicationBadge(qint64 number)
// ---------------------------------------------------------
+#if defined(Q_OS_VISIONOS)
+void QIOSIntegration::openImmersiveSpace()
+{
+ [ImmersiveSpaceManager openImmersiveSpace];
+}
+
+void QIOSIntegration::dismissImmersiveSpace()
+{
+ [ImmersiveSpaceManager dismissImmersiveSpace];
+}
+
+void QIOSIntegration::setImmersiveSpaceCompositorLayer(CompositorLayer *layer)
+{
+ m_immersiveSpaceCompositorLayer = layer;
+}
+
+void QIOSIntegration::configureCompositorLayer(cp_layer_renderer_capabilities_t capabilities,
+ cp_layer_renderer_configuration_t configuration)
+{
+ if (m_immersiveSpaceCompositorLayer)
+ m_immersiveSpaceCompositorLayer->configure(capabilities, configuration);
+}
+
+void QIOSIntegration::renderCompositorLayer(cp_layer_renderer_t renderer)
+{
+ if (m_immersiveSpaceCompositorLayer)
+ m_immersiveSpaceCompositorLayer->render(renderer);
+}
+
+void QIOSIntegration::handleSpatialEvents(const char *jsonString)
+{
+ if (m_immersiveSpaceCompositorLayer) {
+ QJsonParseError error;
+ QJsonDocument doc = QJsonDocument::fromJson(QByteArray(jsonString), &error);
+ if (error.error != QJsonParseError::NoError) {
+ qWarning() << "Error parsing JSON: " << error.errorString();
+ return;
+ }
+ m_immersiveSpaceCompositorLayer->handleSpatialEvents(doc.object());
+ }
+}
+
+#endif
+
+// ---------------------------------------------------------
+
void *QIOSIntegration::nativeResourceForWindow(const QByteArray &resource, QWindow *window)
{
if (!window || !window->handle())
diff --git a/src/plugins/platforms/ios/qiosscreen.mm b/src/plugins/platforms/ios/qiosscreen.mm
index 7559979f33..8f3081e276 100644
--- a/src/plugins/platforms/ios/qiosscreen.mm
+++ b/src/plugins/platforms/ios/qiosscreen.mm
@@ -322,7 +322,10 @@ QDpi QIOSScreen::logicalBaseDpi() const
qreal QIOSScreen::devicePixelRatio() const
{
#if defined(Q_OS_VISIONOS)
- return 2.0; // Based on what iPad app reports
+ // Based on what iPad app reports, and what Apple
+ // documents to be the default scale factor on
+ // visionOS, and the minimum scale for assets.
+ return 2.0;
#else
return [m_uiScreen scale];
#endif
diff --git a/src/plugins/platforms/ios/qioswindow.h b/src/plugins/platforms/ios/qioswindow.h
index 88afee80c3..86bcc111d3 100644
--- a/src/plugins/platforms/ios/qioswindow.h
+++ b/src/plugins/platforms/ios/qioswindow.h
@@ -71,10 +71,9 @@ private:
UIView *m_view;
QRect m_normalGeometry;
- int m_windowLevel;
void raiseOrLower(bool raise);
- void updateWindowLevel();
+ int windowLevel() const;
bool blockedByModal();
friend class QIOSScreen;
diff --git a/src/plugins/platforms/ios/qioswindow.mm b/src/plugins/platforms/ios/qioswindow.mm
index f461a5f55b..7cd3d5f0b0 100644
--- a/src/plugins/platforms/ios/qioswindow.mm
+++ b/src/plugins/platforms/ios/qioswindow.mm
@@ -36,7 +36,6 @@ enum {
QIOSWindow::QIOSWindow(QWindow *window, WId nativeHandle)
: QPlatformWindow(window)
- , m_windowLevel(0)
{
if (nativeHandle) {
m_view = reinterpret_cast<UIView *>(nativeHandle);
@@ -127,11 +126,6 @@ void QIOSWindow::setVisible(bool visible)
if (!isQtApplication() || !window()->isTopLevel())
return;
- // Since iOS doesn't do window management the way a Qt application
- // expects, we need to raise and activate windows ourselves:
- if (visible)
- updateWindowLevel();
-
if (blockedByModal()) {
if (visible)
raise();
@@ -343,8 +337,8 @@ void QIOSWindow::raiseOrLower(bool raise)
UIView *view = static_cast<UIView *>([subviews objectAtIndex:i]);
if (view.hidden || view == m_view || !view.qwindow)
continue;
- int level = static_cast<QIOSWindow *>(view.qwindow->handle())->m_windowLevel;
- if (m_windowLevel > level || (raise && m_windowLevel == level)) {
+ int level = static_cast<QIOSWindow *>(view.qwindow->handle())->windowLevel();
+ if (windowLevel() > level || (raise && windowLevel() == level)) {
[m_view.superview insertSubview:m_view aboveSubview:view];
return;
}
@@ -359,30 +353,34 @@ void QIOSWindow::raiseOrLower(bool raise)
}
}
-void QIOSWindow::updateWindowLevel()
+int QIOSWindow::windowLevel() const
{
Qt::WindowType type = window()->type();
+ int level = 0;
+
if (type == Qt::ToolTip)
- m_windowLevel = 120;
+ level = 120;
else if (window()->flags() & Qt::WindowStaysOnTopHint)
- m_windowLevel = 100;
+ level = 100;
else if (window()->isModal())
- m_windowLevel = 40;
+ level = 40;
else if (type == Qt::Popup)
- m_windowLevel = 30;
+ level = 30;
else if (type == Qt::SplashScreen)
- m_windowLevel = 20;
+ level = 20;
else if (type == Qt::Tool)
- m_windowLevel = 10;
+ level = 10;
else
- m_windowLevel = 0;
+ level = 0;
- // A window should be in at least the same m_windowLevel as its parent:
+ // A window should be in at least the same window level as its parent
QWindow *transientParent = window()->transientParent();
QIOSWindow *transientParentWindow = transientParent ? static_cast<QIOSWindow *>(transientParent->handle()) : 0;
if (transientParentWindow)
- m_windowLevel = qMax(transientParentWindow->m_windowLevel, m_windowLevel);
+ level = qMax(transientParentWindow->windowLevel(), level);
+
+ return level;
}
void QIOSWindow::applicationStateChanged(Qt::ApplicationState)
@@ -396,6 +394,15 @@ void QIOSWindow::applicationStateChanged(Qt::ApplicationState)
qreal QIOSWindow::devicePixelRatio() const
{
+#if !defined(Q_OS_VISIONOS)
+ // If the view has not yet been added to a screen, it will not
+ // pick up its device pixel ratio, so we need to do so manually
+ // based on the screen we think the window will be added to.
+ if (!m_view.window.windowScene.screen)
+ return screen()->devicePixelRatio();
+#endif
+
+ // Otherwise we can rely on the content scale factor
return m_view.contentScaleFactor;
}
diff --git a/src/plugins/platforms/ios/quiaccessibilityelement.mm b/src/plugins/platforms/ios/quiaccessibilityelement.mm
index 39b2cb8a50..fa54f61967 100644
--- a/src/plugins/platforms/ios/quiaccessibilityelement.mm
+++ b/src/plugins/platforms/ios/quiaccessibilityelement.mm
@@ -10,6 +10,8 @@
#include "uistrings_p.h"
#include "qioswindow.h"
+#include <QtGui/private/qaccessiblebridgeutils_p.h>
+
QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement);
@implementation QMacAccessibilityElement
@@ -70,6 +72,17 @@ QT_NAMESPACE_ALIAS_OBJC_CLASS(QMacAccessibilityElement);
return iface->text(QAccessible::Name).toNSString();
}
+
+- (NSString*)accessibilityIdentifier
+{
+ QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid);
+ if (!iface) {
+ qWarning() << "invalid accessible interface for: " << self.axid;
+ return @"";
+ }
+ return QAccessibleBridgeUtils::accessibleId(iface).toNSString();
+}
+
- (NSString*)accessibilityHint
{
QAccessibleInterface *iface = QAccessible::accessibleInterface(self.axid);
diff --git a/src/plugins/platforms/ios/quiview.mm b/src/plugins/platforms/ios/quiview.mm
index d5808db305..b31e1a043d 100644
--- a/src/plugins/platforms/ios/quiview.mm
+++ b/src/plugins/platforms/ios/quiview.mm
@@ -92,7 +92,16 @@ inline ulong getTimeStamp(UIEvent *event)
{
if (self = [self initWithFrame:window->geometry().toCGRect()]) {
self.platformWindow = window;
+
+ if (isQtApplication())
+ self.hidden = YES;
+
m_accessibleElements = [[NSMutableArray<UIAccessibilityElement *> alloc] init];
+
+#ifndef Q_OS_TVOS
+ self.multipleTouchEnabled = YES;
+#endif
+
m_scrollGestureRecognizer = [[UIPanGestureRecognizer alloc]
initWithTarget:self
action:@selector(handleScroll:)];
@@ -109,6 +118,7 @@ inline ulong getTimeStamp(UIEvent *event)
m_lastScrollCursorPos = CGPointZero;
[self addGestureRecognizer:m_scrollGestureRecognizer];
+ // Set up layer
if ([self.layer isKindOfClass:CAMetalLayer.class]) {
QWindow *window = self.platformWindow->window();
if (QColorSpace colorSpace = window->format().colorSpace(); colorSpace.isValid()) {
@@ -119,17 +129,8 @@ inline ulong getTimeStamp(UIEvent *event)
qCDebug(lcQpaWindow) << "Set" << self << "color space to" << metalLayer.colorspace;
}
}
- }
-
- return self;
-}
-
-- (instancetype)initWithFrame:(CGRect)frame
-{
- if ((self = [super initWithFrame:frame])) {
#if QT_CONFIG(opengl)
- if ([self.layer isKindOfClass:[CAEAGLLayer class]]) {
- // Set up EAGL layer
+ else if ([self.layer isKindOfClass:[CAEAGLLayer class]]) {
CAEAGLLayer *eaglLayer = static_cast<CAEAGLLayer *>(self.layer);
eaglLayer.opaque = TRUE;
eaglLayer.drawableProperties = @{
@@ -139,37 +140,13 @@ inline ulong getTimeStamp(UIEvent *event)
}
#endif
- if (isQtApplication())
- self.hidden = YES;
-
-#ifndef Q_OS_TVOS
- self.multipleTouchEnabled = YES;
+#if defined(Q_OS_VISIONOS)
+ // Although the "Drawing sharp layer-based content in visionOS" docs
+ // claim that by default a CALayer rasterizes at a 2x scale this does
+ // not seem to be the case in practice. So we explicitly set the view's
+ // scale factor based on the screen, where we hard-code it to 2.0.
+ self.contentScaleFactor = self.platformWindow->screen()->devicePixelRatio();
#endif
-
- if (qEnvironmentVariableIntValue("QT_IOS_DEBUG_WINDOW_MANAGEMENT")) {
- static CGFloat hue = 0.0;
- CGFloat lastHue = hue;
- for (CGFloat diff = 0; diff < 0.1 || diff > 0.9; diff = fabs(hue - lastHue))
- hue = drand48();
-
- #define colorWithBrightness(br) \
- [UIColor colorWithHue:hue saturation:0.5 brightness:br alpha:1.0].CGColor
-
- self.layer.borderColor = colorWithBrightness(1.0);
- self.layer.borderWidth = 1.0;
- }
-
- if (qEnvironmentVariableIsSet("QT_IOS_DEBUG_WINDOW_SAFE_AREAS")) {
- UIView *safeAreaOverlay = [[UIView alloc] initWithFrame:CGRectZero];
- [safeAreaOverlay setBackgroundColor:[UIColor colorWithRed:0.3 green:0.7 blue:0.9 alpha:0.3]];
- [self addSubview:safeAreaOverlay];
-
- safeAreaOverlay.translatesAutoresizingMaskIntoConstraints = NO;
- [safeAreaOverlay.topAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.topAnchor].active = YES;
- [safeAreaOverlay.leftAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.leftAnchor].active = YES;
- [safeAreaOverlay.rightAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.rightAnchor].active = YES;
- [safeAreaOverlay.bottomAnchor constraintEqualToAnchor:self.safeAreaLayoutGuide.bottomAnchor].active = YES;
- }
}
return self;
@@ -356,9 +333,11 @@ inline ulong getTimeStamp(UIEvent *event)
qImDebug() << self << "resigned first responder";
- UIResponder *newResponder = FirstResponderCandidate::currentCandidate();
- if ([self responderShouldTriggerWindowDeactivation:newResponder])
- QWindowSystemInterface::handleFocusWindowChanged(nullptr, Qt::ActiveWindowFocusReason);
+ if (qGuiApp) {
+ UIResponder *newResponder = FirstResponderCandidate::currentCandidate();
+ if ([self responderShouldTriggerWindowDeactivation:newResponder])
+ QWindowSystemInterface::handleFocusWindowChanged(nullptr, Qt::ActiveWindowFocusReason);
+ }
return YES;
}