diff --git a/.cargo/config.toml b/.cargo/config.toml index 1323c94..482724a 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -1,9 +1,4 @@ -[target.aarch64-apple-darwin] -rustflags = [ - "-C", "link-arg=-Wl,-install_name,@rpath/libaccesskit.dylib", -] - -[target.x86_64-apple-darwin] +[target.'cfg(target_vendor = "apple")'] rustflags = [ "-C", "link-arg=-Wl,-install_name,@rpath/libaccesskit.dylib", ] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 31f6057..50213f8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,9 +40,22 @@ jobs: clippy: runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: - os: [macOS-latest, windows-2025, ubuntu-latest] - name: cargo clippy + include: + - os: windows-2025 + name: Windows + - os: macOS-latest + name: macOS + - os: ubuntu-latest + name: Linux + - os: ubuntu-latest + name: Android + target: aarch64-linux-android + - os: macOS-latest + name: iOS Simulator + target: aarch64-apple-ios-sim + name: cargo clippy (${{ matrix.name }}) steps: - uses: actions/checkout@v6 @@ -50,12 +63,13 @@ jobs: uses: dtolnay/rust-toolchain@stable with: components: clippy + targets: ${{ matrix.target }} - name: restore cache uses: Swatinem/rust-cache@v2 - name: cargo clippy - run: cargo clippy --all-targets -- -D warnings + run: cargo clippy ${{ matrix.target && format('--target {0}', matrix.target) }} --all-targets -- -D warnings find-msrv: runs-on: ubuntu-latest @@ -72,6 +86,7 @@ jobs: matrix: include: - target: aarch64-apple-darwin + - target: aarch64-apple-ios - target: i686-pc-windows-gnu - target: i686-pc-windows-msvc - target: i686-unknown-linux-gnu diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index e8fcac5..ca98578 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -19,6 +19,18 @@ jobs: target: x86_64-apple-darwin cmake-options: -DCMAKE_OSX_ARCHITECTURES=x86_64 path: macos/x86_64 + - os: macOS-latest + target: aarch64-apple-ios + cmake-options: -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_SYSROOT=iphoneos + path: ios/arm64 + - os: macOS-latest + target: aarch64-apple-ios-sim + cmake-options: -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=arm64 -DCMAKE_OSX_SYSROOT=iphonesimulator + path: ios-simulator/arm64 + - os: macOS-latest + target: x86_64-apple-ios + cmake-options: -DCMAKE_SYSTEM_NAME=iOS -DCMAKE_OSX_ARCHITECTURES=x86_64 -DCMAKE_OSX_SYSROOT=iphonesimulator + path: ios-simulator/x86_64 - os: windows-2025 target: aarch64-pc-windows-msvc setup-step: 'cmd.exe /k "C:\Program Files (x86)\Microsoft Visual Studio\2022\Enterprise\VC\Auxiliary\Build\vcvarsamd64_x86.bat" `& powershell' diff --git a/CMakeLists.txt b/CMakeLists.txt index 16946c9..60b7c83 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -12,7 +12,7 @@ if (ACCESSKIT_BUILD_LIBRARIES) FetchContent_Declare( Corrosion GIT_REPOSITORY https://github.com/corrosion-rs/corrosion.git - GIT_TAG v0.5.1 + GIT_TAG v0.6.1 ) FetchContent_MakeAvailable(Corrosion) diff --git a/Cargo.lock b/Cargo.lock index be340af..fcec5f9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,7 @@ version = "0.21.2" dependencies = [ "accesskit", "accesskit_android", + "accesskit_ios", "accesskit_macos", "accesskit_unix", "accesskit_windows", @@ -58,6 +59,20 @@ dependencies = [ "hashbrown", ] +[[package]] +name = "accesskit_ios" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f907f6793e18524b69a4530528f8a8fb6dc8b4e162f782b97a9618401e7f2e3" +dependencies = [ + "accesskit", + "accesskit_consumer", + "hashbrown", + "objc2", + "objc2-foundation", + "objc2-ui-kit", +] + [[package]] name = "accesskit_macos" version = "0.26.1" @@ -304,9 +319,9 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "block2" -version = "0.5.0" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43ff7d91d3c1d568065b06c899777d1e48dcf76103a672a0adbc238a7f247f1e" +checksum = "2c132eebf10f5cad5289222520a4a058514204aed6d791f1cf4fe8088b82d15f" dependencies = [ "objc2", ] @@ -609,15 +624,15 @@ dependencies = [ [[package]] name = "objc-sys" -version = "0.3.3" +version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da284c198fb9b7b0603f8635185e85fbd5b64ee154b1ed406d489077de2d6d60" +checksum = "cdb91bdd390c7ce1a8607f35f3ca7151b65afc0ff5ff3b34fa350f7d7c7e4310" [[package]] name = "objc2" -version = "0.5.1" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4b25e1034d0e636cd84707ccdaa9f81243d399196b8a773946dcffec0401659" +checksum = "46a785d4eeff09c14c487497c162e92766fbb3e4059a71840cecc03d9a50b804" dependencies = [ "objc-sys", "objc2-encode", @@ -625,41 +640,188 @@ dependencies = [ [[package]] name = "objc2-app-kit" -version = "0.2.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb79768a710a9a1798848179edb186d1af7e8a8679f369e4b8d201dd2a034047" +checksum = "e4e89ad9e3d7d297152b17d39ed92cd50ca8063a89a9fa569046d41568891eff" dependencies = [ + "bitflags", "block2", + "libc", "objc2", "objc2-core-data", + "objc2-core-image", + "objc2-foundation", + "objc2-quartz-core", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74dd3b56391c7a0596a295029734d3c1c5e7e510a4cb30245f8221ccea96b009" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", +] + +[[package]] +name = "objc2-contacts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5ff520e9c33812fd374d8deecef01d4a840e7b41862d849513de77e44aa4889" +dependencies = [ + "block2", + "objc2", "objc2-foundation", ] [[package]] name = "objc2-core-data" -version = "0.2.0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617fbf49e071c178c0b24c080767db52958f716d9eabdf0890523aeae54773ef" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-image" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e092bc42eaf30a08844e6a076938c60751225ec81431ab89f5d1ccd9f958d6c" +checksum = "55260963a527c99f1819c4f8e3b47fe04f9650694ef348ffd2227e8196d34c80" dependencies = [ "block2", "objc2", "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-core-location" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "000cfee34e683244f284252ee206a27953279d370e309649dc3ee317b37e5781" +dependencies = [ + "block2", + "objc2", + "objc2-contacts", + "objc2-foundation", ] [[package]] name = "objc2-encode" -version = "4.0.1" +version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88658da63e4cc2c8adb1262902cd6af51094df0488b760d6fd27194269c0950a" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" [[package]] name = "objc2-foundation" -version = "0.2.0" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ee638a5da3799329310ad4cfa62fbf045d5f56e3ef5ba4149e7452dcf89d5a8" +dependencies = [ + "bitflags", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "objc2-link-presentation" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a1ae721c5e35be65f01a03b6d2ac13a54cb4fa70d8a5da293d7b0020261398" +dependencies = [ + "block2", + "objc2", + "objc2-app-kit", + "objc2-foundation", +] + +[[package]] +name = "objc2-metal" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd0cba1276f6023976a406a14ffa85e1fdd19df6b0f737b063b95f6c8c7aadd6" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42bee7bff906b14b167da2bac5efe6b6a07e6f7c0a21a7308d40c960242dc7a" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-foundation", + "objc2-metal", +] + +[[package]] +name = "objc2-symbols" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cfaefe14254871ea16c7d88968c0ff14ba554712a20d76421eec52f0a7fb8904" +checksum = "0a684efe3dec1b305badae1a28f6555f6ddd3bb2c2267896782858d5a78404dc" dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8bb46798b20cd6b91cbd113524c490f1686f4c4e8f49502431415f3512e2b6f" +dependencies = [ + "bitflags", "block2", "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-image", + "objc2-core-location", + "objc2-foundation", + "objc2-link-presentation", + "objc2-quartz-core", + "objc2-symbols", + "objc2-uniform-type-identifiers", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-uniform-type-identifiers" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44fa5f9748dbfe1ca6c0b79ad20725a11eca7c2218bceb4b005cb1be26273bfe" +dependencies = [ + "block2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76cfcbf642358e8689af64cee815d139339f3ed8ad05103ed5eaf73db8d84cb3" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-core-location", + "objc2-foundation", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index d10a98f..6a1fe52 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -31,6 +31,9 @@ accesskit_unix = "0.21.1" [target.'cfg(target_os = "android")'.dependencies] accesskit_android = "0.7.3" +[target.'cfg(any(target_os = "ios", target_os = "tvos", target_os = "visionos", target_os = "watchos"))'.dependencies] +accesskit_ios = "0.1.0" + [profile.release] lto = true opt-level = "z" diff --git a/README.md b/README.md index 3d796f8..7919311 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ meson compile -C build If you modify the C bindings, you need to regenerate the header file and commit it. To do this, in addition to the above requirements, you will need: - A specific nightly Rust toolchain: `rustup install nightly-2025-03-27` -- [cbindgen](https://github.com/mozilla/cbindgen): `cargo install cbindgen` +- [cbindgen](https://github.com/mozilla/cbindgen): `cargo install cbindgen --version 0.28.0` - [clang-format](https://releases.llvm.org/14.0.0/tools/clang/docs/ClangFormat.html), version 14 or higher Once you have these requirements, the process of regenerating the header file is similar to building and installing from source with CMake, but using different configuration options: diff --git a/accesskit-config.cmake b/accesskit-config.cmake index 4adc635..938161c 100644 --- a/accesskit-config.cmake +++ b/accesskit-config.cmake @@ -14,6 +14,8 @@ set_property( ) if (_accesskit_os STREQUAL "macos") target_link_libraries(accesskit-static INTERFACE "-framework AppKit" "-framework Foundation" "-framework CoreFoundation" objc c++) +elseif (_accesskit_os STREQUAL "ios" OR _accesskit_os STREQUAL "ios-simulator") + target_link_libraries(accesskit-static INTERFACE "-framework UIKit" "-framework Foundation" "-framework CoreFoundation" objc c++) elseif (_accesskit_os STREQUAL "android") target_link_libraries(accesskit-static INTERFACE android log) elseif (_accesskit_os STREQUAL "linux") @@ -30,7 +32,7 @@ if (_accesskit_os STREQUAL "windows") PROPERTY IMPORTED_IMPLIB "${_accesskit_implib}" ) endif() -if (_accesskit_os STREQUAL "macos") +if (_accesskit_os STREQUAL "macos" OR _accesskit_os STREQUAL "ios" OR _accesskit_os STREQUAL "ios-simulator") set(_accesskit_shared_lib "libaccesskit.dylib") elseif (_accesskit_os STREQUAL "android" OR _accesskit_os STREQUAL "linux") set(_accesskit_shared_lib "libaccesskit.so") diff --git a/accesskit.cmake b/accesskit.cmake index f009d29..81d468a 100644 --- a/accesskit.cmake +++ b/accesskit.cmake @@ -2,7 +2,17 @@ set(ACCESSKIT_INCLUDE_DIR "${CMAKE_CURRENT_LIST_DIR}/include") set(_accesskit_toolchain "") if (APPLE) - set(_accesskit_os "macos") + if (CMAKE_SYSTEM_NAME STREQUAL "iOS") + string(TOLOWER "${CMAKE_OSX_SYSROOT}" _accesskit_sysroot) + if (_accesskit_sysroot MATCHES "iphonesimulator") + set(_accesskit_os "ios-simulator") + else() + set(_accesskit_os "ios") + endif() + unset(_accesskit_sysroot) + else() + set(_accesskit_os "macos") + endif() if (CMAKE_OSX_ARCHITECTURES MATCHES "(ARM64|arm64|aarch64)") set(_accesskit_arch "arm64") elseif (CMAKE_OSX_ARCHITECTURES MATCHES "(AMD64|amd64|x86_64)") diff --git a/cbindgen.toml b/cbindgen.toml index 6a3565b..f59060b 100644 --- a/cbindgen.toml +++ b/cbindgen.toml @@ -13,7 +13,45 @@ after_includes = """#ifdef _WIN32 #endif #ifdef __ANDROID__ #include -#endif""" +#endif +#ifdef __APPLE__ +#include +/* Older SDKs may not define all of these; force them to 0 so the + value-based checks below are safe. */ +#ifndef TARGET_OS_OSX +#define TARGET_OS_OSX 0 +#endif +#ifndef TARGET_OS_IOS +#define TARGET_OS_IOS 0 +#endif +#ifndef TARGET_OS_TV +#define TARGET_OS_TV 0 +#endif +#ifndef TARGET_OS_WATCH +#define TARGET_OS_WATCH 0 +#endif +#ifndef TARGET_OS_VISION +#define TARGET_OS_VISION 0 +#endif +#ifndef TARGET_OS_MACCATALYST +#define TARGET_OS_MACCATALYST 0 +#endif +#if TARGET_OS_OSX +#define ACCESSKIT_MACOS +#endif +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST +#define ACCESSKIT_IOS +#endif +#if TARGET_OS_TV +#define ACCESSKIT_TVOS +#endif +#if TARGET_OS_WATCH +#define ACCESSKIT_WATCHOS +#endif +#if TARGET_OS_VISION +#define ACCESSKIT_VISIONOS +#endif +#endif /* __APPLE__ */""" usize_is_size_t = true @@ -29,7 +67,11 @@ renaming_overrides_prefixing = true "target_os = freebsd" = "__FreeBSD__" "target_os = netbsd" = "__NetBSD__" "target_os = openbsd" = "__OpenBSD__" -"target_os = macos" = "__APPLE__" +"target_os = ios" = "ACCESSKIT_IOS" +"target_os = macos" = "ACCESSKIT_MACOS" +"target_os = tvos" = "ACCESSKIT_TVOS" +"target_os = visionos" = "ACCESSKIT_VISIONOS" +"target_os = watchos" = "ACCESSKIT_WATCHOS" "target_os = windows" = "_WIN32" [export.rename] diff --git a/include/accesskit.h b/include/accesskit.h index eab9e35..fb0af1d 100644 --- a/include/accesskit.h +++ b/include/accesskit.h @@ -19,6 +19,44 @@ #ifdef __ANDROID__ #include #endif +#ifdef __APPLE__ +#include +/* Older SDKs may not define all of these; force them to 0 so the + value-based checks below are safe. */ +#ifndef TARGET_OS_OSX +#define TARGET_OS_OSX 0 +#endif +#ifndef TARGET_OS_IOS +#define TARGET_OS_IOS 0 +#endif +#ifndef TARGET_OS_TV +#define TARGET_OS_TV 0 +#endif +#ifndef TARGET_OS_WATCH +#define TARGET_OS_WATCH 0 +#endif +#ifndef TARGET_OS_VISION +#define TARGET_OS_VISION 0 +#endif +#ifndef TARGET_OS_MACCATALYST +#define TARGET_OS_MACCATALYST 0 +#endif +#if TARGET_OS_OSX +#define ACCESSKIT_MACOS +#endif +#if TARGET_OS_IOS || TARGET_OS_MACCATALYST +#define ACCESSKIT_IOS +#endif +#if TARGET_OS_TV +#define ACCESSKIT_TVOS +#endif +#if TARGET_OS_WATCH +#define ACCESSKIT_WATCHOS +#endif +#if TARGET_OS_VISION +#define ACCESSKIT_VISIONOS +#endif +#endif /* __APPLE__ */ /** * An action to be taken on an accessibility node. @@ -592,15 +630,31 @@ typedef struct accesskit_android_queued_events accesskit_android_queued_events; typedef struct accesskit_custom_action accesskit_custom_action; -#if defined(__APPLE__) +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +typedef struct accesskit_ios_adapter accesskit_ios_adapter; +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +typedef struct accesskit_ios_queued_events accesskit_ios_queued_events; +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +typedef struct accesskit_ios_subclassing_adapter + accesskit_ios_subclassing_adapter; +#endif + +#if defined(ACCESSKIT_MACOS) typedef struct accesskit_macos_adapter accesskit_macos_adapter; #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) typedef struct accesskit_macos_queued_events accesskit_macos_queued_events; #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) typedef struct accesskit_macos_subclassing_adapter accesskit_macos_subclassing_adapter; #endif @@ -2561,7 +2615,167 @@ void accesskit_android_injecting_adapter_update_if_active( void *update_factory_userdata); #endif -#if defined(__APPLE__) +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +/** + * Memory is also freed when calling this function. + */ +void accesskit_ios_queued_events_raise( + struct accesskit_ios_queued_events *events); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +/** + * This function must be called on the main thread. + * All handlers will always be called on the main thread. + * + * # Safety + * + * `view` must be a valid, unreleased pointer to a `UIView`. + */ +struct accesskit_ios_adapter *accesskit_ios_adapter_new( + void *view, accesskit_activation_handler_callback activation_handler, + void *activation_handler_userdata, + accesskit_action_handler_callback action_handler, + void *action_handler_userdata, + accesskit_deactivation_handler_callback deactivation_handler, + void *deactivation_handler_userdata); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +void accesskit_ios_adapter_free(struct accesskit_ios_adapter *adapter); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +/** + * You must call `accesskit_ios_queued_events_raise` on the returned pointer. It + * can be null if the adapter is not active. + */ +struct accesskit_ios_queued_events *accesskit_ios_adapter_update_if_active( + struct accesskit_ios_adapter *adapter, + accesskit_tree_update_factory update_factory, + void *update_factory_userdata); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +/** + * Call this when the host view has just appeared on screen. If an + * assistive technology is running, this proactively builds the + * accessibility tree. + * + * You must call `accesskit_ios_queued_events_raise` on the returned pointer. It + * can be null if the adapter is not active. + */ +struct accesskit_ios_queued_events *accesskit_ios_adapter_view_did_appear( + struct accesskit_ios_adapter *adapter); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +/** + * Returns whether the view itself is an accessibility element. + * This corresponds to `isAccessibilityElement`. + */ +bool accesskit_ios_adapter_is_accessibility_element( + struct accesskit_ios_adapter *adapter); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +/** + * Returns a pointer to an `NSArray` of accessibility elements + * contained in the view. Ownership of the pointer is not transferred. + * This corresponds to `accessibilityElements`. + */ +void *accesskit_ios_adapter_accessibility_elements( + struct accesskit_ios_adapter *adapter); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +/** + * Returns a pointer to the accessibility element at the specified point, + * or null if none. Ownership of the pointer is not transferred. + * This corresponds to `accessibilityHitTest:`. + */ +void *accesskit_ios_adapter_hit_test(struct accesskit_ios_adapter *adapter, + double x, double y); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +/** + * Caller must call `accesskit_string_free` with the return value. + */ +char *accesskit_ios_adapter_debug(const struct accesskit_ios_adapter *adapter); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +/** + * All handlers will always be called on the main thread. + * + * # Safety + * + * `view` must be a valid, unreleased pointer to a `UIView`. + */ +struct accesskit_ios_subclassing_adapter *accesskit_ios_subclassing_adapter_new( + void *view, accesskit_activation_handler_callback activation_handler, + void *activation_handler_userdata, + accesskit_action_handler_callback action_handler, + void *action_handler_userdata, + accesskit_deactivation_handler_callback deactivation_handler, + void *deactivation_handler_userdata); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +/** + * All handlers will always be called on the main thread. + * + * # Safety + * + * `window` must be a valid, unreleased pointer to a `UIWindow`. + * + * # Panics + * + * This function panics if the specified window doesn't currently have + * a root view controller with a view. + */ +struct accesskit_ios_subclassing_adapter * +accesskit_ios_subclassing_adapter_for_window( + void *window, accesskit_activation_handler_callback activation_handler, + void *activation_handler_userdata, + accesskit_action_handler_callback action_handler, + void *action_handler_userdata, + accesskit_deactivation_handler_callback deactivation_handler, + void *deactivation_handler_userdata); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +void accesskit_ios_subclassing_adapter_free( + struct accesskit_ios_subclassing_adapter *adapter); +#endif + +#if (defined(ACCESSKIT_IOS) || defined(ACCESSKIT_TVOS) || \ + defined(ACCESSKIT_VISIONOS) || defined(ACCESSKIT_WATCHOS)) +/** + * You must call `accesskit_ios_queued_events_raise` on the returned pointer. It + * can be null if the adapter is not active. + */ +struct accesskit_ios_queued_events * +accesskit_ios_subclassing_adapter_update_if_active( + struct accesskit_ios_subclassing_adapter *adapter, + accesskit_tree_update_factory update_factory, + void *update_factory_userdata); +#endif + +#if defined(ACCESSKIT_MACOS) /** * Memory is also freed when calling this function. */ @@ -2569,7 +2783,7 @@ void accesskit_macos_queued_events_raise( struct accesskit_macos_queued_events *events); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * # Safety * @@ -2581,11 +2795,11 @@ struct accesskit_macos_adapter *accesskit_macos_adapter_new( void *action_handler_userdata); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) void accesskit_macos_adapter_free(struct accesskit_macos_adapter *adapter); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * You must call `accesskit_macos_queued_events_raise` on the returned pointer. * It can be null if the adapter is not active. @@ -2596,7 +2810,7 @@ struct accesskit_macos_queued_events *accesskit_macos_adapter_update_if_active( void *update_factory_userdata); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * Update the tree state based on whether the window is focused. * @@ -2608,7 +2822,7 @@ accesskit_macos_adapter_update_view_focus_state( struct accesskit_macos_adapter *adapter, bool is_focused); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * Returns a pointer to an `NSArray`. Ownership of the pointer is not * transferred. @@ -2619,7 +2833,7 @@ void *accesskit_macos_adapter_view_children( void *activation_handler_userdata); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * Returns a pointer to an `NSObject`. Ownership of the pointer is not * transferred. @@ -2630,7 +2844,7 @@ void *accesskit_macos_adapter_focus( void *activation_handler_userdata); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * Returns a pointer to an `NSObject`. Ownership of the pointer is not * transferred. @@ -2641,7 +2855,7 @@ void *accesskit_macos_adapter_hit_test( void *activation_handler_userdata); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * Caller must call `accesskit_string_free` with the return value. */ @@ -2649,7 +2863,7 @@ char *accesskit_macos_adapter_debug( const struct accesskit_macos_adapter *adapter); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * # Safety * @@ -2663,7 +2877,7 @@ accesskit_macos_subclassing_adapter_new( void *action_handler_userdata); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * # Safety * @@ -2682,12 +2896,12 @@ accesskit_macos_subclassing_adapter_for_window( void *action_handler_userdata); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) void accesskit_macos_subclassing_adapter_free( struct accesskit_macos_subclassing_adapter *adapter); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * You must call `accesskit_macos_queued_events_raise` on the returned pointer. * It can be null if the adapter is not active. @@ -2699,7 +2913,7 @@ accesskit_macos_subclassing_adapter_update_if_active( void *update_factory_userdata); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * Update the tree state based on whether the window is focused. * @@ -2711,7 +2925,7 @@ accesskit_macos_subclassing_adapter_update_view_focus_state( struct accesskit_macos_subclassing_adapter *adapter, bool is_focused); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * Modifies the specified class, which must be a subclass of `NSWindow`, * to include an `accessibilityFocusedUIElement` method that calls @@ -2732,7 +2946,7 @@ void accesskit_macos_add_focus_forwarder_to_window_class( const char *class_name); #endif -#if defined(__APPLE__) +#if defined(ACCESSKIT_MACOS) /** * Modifies the specified class, which must be a subclass of `NSWindow`, * to include an `accessibilityFocusedUIElement` method that calls diff --git a/meson.build b/meson.build index 310f68d..59d124e 100644 --- a/meson.build +++ b/meson.build @@ -23,6 +23,8 @@ project('accesskit-c', ) host_system = host_machine.system() +apple_systems = ['darwin', 'ios', 'tvos', 'visionos', 'watchos'] +non_macos_apple_systems = ['ios', 'tvos', 'visionos', 'watchos'] cc = meson.get_compiler('c') # MSRV - Minimum Supported Rust Version @@ -41,6 +43,10 @@ python = py.find_installation() appkit_dep = dependency('appleframeworks', modules: 'AppKit', required: host_system == 'darwin') +uikit_dep = dependency('appleframeworks', modules: 'UIKit', + required: host_system in non_macos_apple_systems) +objc_dep = cc.find_library('objc', required: host_system in apple_systems) +cxx_dep = cc.find_library('c++', required: host_system in apple_systems) m_dep = cc.find_library('m', required: false) uiautomationcore_dep = cc.find_library('uiautomationcore', required: host_system == 'windows') @@ -197,7 +203,7 @@ if host_system == 'windows' endforeach endif -private_dependencies += [m_dep, appkit_dep, uiautomationcore_dep, runtimeobject_dep, propsys_dep] +private_dependencies += [m_dep, appkit_dep, uikit_dep, objc_dep, cxx_dep, uiautomationcore_dep, runtimeobject_dep, propsys_dep] library_dependencies += other_library_dependencies library_dependencies += private_dependencies @@ -230,7 +236,7 @@ if host_system == 'windows' ext_dynamic = 'dll' ext_static = is_msvc_style ? 'lib' : 'a' ext_exe = '.exe' -elif host_system in ['darwin', 'ios'] +elif host_system in apple_systems lib_prefix = 'lib' ext_dynamic = 'dylib' ext_static = 'a' @@ -269,6 +275,7 @@ library_sources = files( 'src/android.rs', 'src/common.rs', 'src/geometry.rs', + 'src/ios.rs', 'src/lib.rs', 'src/macos.rs', 'src/unix.rs', @@ -341,7 +348,7 @@ makedef_args = [ '^accesskit_.', ] -if host_system in ['darwin', 'ios'] +if host_system in apple_systems makedef_args += ['--os', 'darwin'] elif host_system in ['windows', 'cygwin'] makedef_args += ['--os', 'win'] @@ -363,7 +370,7 @@ if is_msvc_style else nm = find_program('llvm-nm', required: false) if not nm.found() - if host_system in ['darwin', 'ios'] + if host_system in apple_systems warning('llvm-nm not found, you may experience problems when creating the shared libaccesskit-c.') warning('Please install the llvm-tools component through rustup, or point Meson to an existing LLVM installation.') endif @@ -382,7 +389,7 @@ symbol_list_target = custom_target( symbol_list = symbol_list_target[0] -if host_system in ['darwin', 'ios'] +if host_system in apple_systems vflags = ['-Wl,-exported_symbols_list,@0@'.format(symbol_list_target.full_path())] elif host_system == 'windows' vflags = [] diff --git a/src/ios.rs b/src/ios.rs new file mode 100644 index 0000000..32d1299 --- /dev/null +++ b/src/ios.rs @@ -0,0 +1,246 @@ +// Copyright 2026 The AccessKit Authors. All rights reserved. +// Licensed under the Apache License, Version 2.0 (found in +// the LICENSE-APACHE file) or the MIT license (found in +// the LICENSE-MIT file), at your option. + +use accesskit_ios::{Adapter, CGPoint, QueuedEvents, SubclassingAdapter}; +use std::ffi::{c_char, c_void}; + +use crate::{ + box_from_ptr, debug_repr_from_ptr, mut_from_ptr, tree_update_factory, + tree_update_factory_userdata, ActionHandlerCallback, ActivationHandlerCallback, BoxCastPtr, + CastPtr, DeactivationHandlerCallback, FfiActionHandler, FfiActivationHandler, + FfiDeactivationHandler, +}; + +pub struct ios_queued_events { + _private: [u8; 0], +} + +impl CastPtr for ios_queued_events { + type RustType = QueuedEvents; +} + +impl BoxCastPtr for ios_queued_events {} + +impl ios_queued_events { + /// Memory is also freed when calling this function. + #[no_mangle] + pub extern "C" fn accesskit_ios_queued_events_raise(events: *mut ios_queued_events) { + let events = box_from_ptr(events); + events.raise(); + } +} + +pub struct ios_adapter { + _private: [u8; 0], +} + +impl CastPtr for ios_adapter { + type RustType = Adapter; +} + +impl BoxCastPtr for ios_adapter {} + +impl ios_adapter { + /// This function must be called on the main thread. + /// All handlers will always be called on the main thread. + /// + /// # Safety + /// + /// `view` must be a valid, unreleased pointer to a `UIView`. + #[no_mangle] + pub unsafe extern "C" fn accesskit_ios_adapter_new( + view: *mut c_void, + activation_handler: ActivationHandlerCallback, + activation_handler_userdata: *mut c_void, + action_handler: ActionHandlerCallback, + action_handler_userdata: *mut c_void, + deactivation_handler: DeactivationHandlerCallback, + deactivation_handler_userdata: *mut c_void, + ) -> *mut ios_adapter { + let activation_handler = + FfiActivationHandler::new(activation_handler, activation_handler_userdata); + let action_handler = FfiActionHandler::new(action_handler, action_handler_userdata); + let deactivation_handler = + FfiDeactivationHandler::new(deactivation_handler, deactivation_handler_userdata); + let adapter = Adapter::new( + view, + activation_handler, + action_handler, + deactivation_handler, + ); + BoxCastPtr::to_mut_ptr(adapter) + } + + #[no_mangle] + pub extern "C" fn accesskit_ios_adapter_free(adapter: *mut ios_adapter) { + drop(box_from_ptr(adapter)); + } + + /// You must call `accesskit_ios_queued_events_raise` on the returned pointer. It can be null if the adapter is not active. + #[no_mangle] + pub extern "C" fn accesskit_ios_adapter_update_if_active( + adapter: *mut ios_adapter, + update_factory: tree_update_factory, + update_factory_userdata: *mut c_void, + ) -> *mut ios_queued_events { + let update_factory = update_factory.unwrap(); + let update_factory_userdata = tree_update_factory_userdata(update_factory_userdata); + let adapter = mut_from_ptr(adapter); + let events = + adapter.update_if_active(|| *box_from_ptr(update_factory(update_factory_userdata))); + BoxCastPtr::to_nullable_mut_ptr(events) + } + + /// Call this when the host view has just appeared on screen. If an + /// assistive technology is running, this proactively builds the + /// accessibility tree. + /// + /// You must call `accesskit_ios_queued_events_raise` on the returned pointer. It can be null if the adapter is not active. + #[no_mangle] + pub extern "C" fn accesskit_ios_adapter_view_did_appear( + adapter: *mut ios_adapter, + ) -> *mut ios_queued_events { + let adapter = mut_from_ptr(adapter); + let events = adapter.view_did_appear(); + BoxCastPtr::to_nullable_mut_ptr(events) + } + + /// Returns whether the view itself is an accessibility element. + /// This corresponds to `isAccessibilityElement`. + #[no_mangle] + pub extern "C" fn accesskit_ios_adapter_is_accessibility_element( + adapter: *mut ios_adapter, + ) -> bool { + let adapter = mut_from_ptr(adapter); + adapter.is_accessibility_element() + } + + /// Returns a pointer to an `NSArray` of accessibility elements + /// contained in the view. Ownership of the pointer is not transferred. + /// This corresponds to `accessibilityElements`. + #[no_mangle] + pub extern "C" fn accesskit_ios_adapter_accessibility_elements( + adapter: *mut ios_adapter, + ) -> *mut c_void { + let adapter = mut_from_ptr(adapter); + adapter.accessibility_elements() as *mut _ + } + + /// Returns a pointer to the accessibility element at the specified point, + /// or null if none. Ownership of the pointer is not transferred. + /// This corresponds to `accessibilityHitTest:`. + #[no_mangle] + pub extern "C" fn accesskit_ios_adapter_hit_test( + adapter: *mut ios_adapter, + x: f64, + y: f64, + ) -> *mut c_void { + let adapter = mut_from_ptr(adapter); + adapter.hit_test(CGPoint::new(x, y)) as *mut _ + } + + /// Caller must call `accesskit_string_free` with the return value. + #[no_mangle] + pub extern "C" fn accesskit_ios_adapter_debug(adapter: *const ios_adapter) -> *mut c_char { + debug_repr_from_ptr(adapter) + } +} + +pub struct ios_subclassing_adapter { + _private: [u8; 0], +} + +impl CastPtr for ios_subclassing_adapter { + type RustType = SubclassingAdapter; +} + +impl BoxCastPtr for ios_subclassing_adapter {} + +impl ios_subclassing_adapter { + /// All handlers will always be called on the main thread. + /// + /// # Safety + /// + /// `view` must be a valid, unreleased pointer to a `UIView`. + #[no_mangle] + pub unsafe extern "C" fn accesskit_ios_subclassing_adapter_new( + view: *mut c_void, + activation_handler: ActivationHandlerCallback, + activation_handler_userdata: *mut c_void, + action_handler: ActionHandlerCallback, + action_handler_userdata: *mut c_void, + deactivation_handler: DeactivationHandlerCallback, + deactivation_handler_userdata: *mut c_void, + ) -> *mut ios_subclassing_adapter { + let activation_handler = + FfiActivationHandler::new(activation_handler, activation_handler_userdata); + let action_handler = FfiActionHandler::new(action_handler, action_handler_userdata); + let deactivation_handler = + FfiDeactivationHandler::new(deactivation_handler, deactivation_handler_userdata); + let adapter = SubclassingAdapter::new( + view, + activation_handler, + action_handler, + deactivation_handler, + ); + BoxCastPtr::to_mut_ptr(adapter) + } + + /// All handlers will always be called on the main thread. + /// + /// # Safety + /// + /// `window` must be a valid, unreleased pointer to a `UIWindow`. + /// + /// # Panics + /// + /// This function panics if the specified window doesn't currently have + /// a root view controller with a view. + #[no_mangle] + pub unsafe extern "C" fn accesskit_ios_subclassing_adapter_for_window( + window: *mut c_void, + activation_handler: ActivationHandlerCallback, + activation_handler_userdata: *mut c_void, + action_handler: ActionHandlerCallback, + action_handler_userdata: *mut c_void, + deactivation_handler: DeactivationHandlerCallback, + deactivation_handler_userdata: *mut c_void, + ) -> *mut ios_subclassing_adapter { + let activation_handler = + FfiActivationHandler::new(activation_handler, activation_handler_userdata); + let action_handler = FfiActionHandler::new(action_handler, action_handler_userdata); + let deactivation_handler = + FfiDeactivationHandler::new(deactivation_handler, deactivation_handler_userdata); + let adapter = SubclassingAdapter::for_window( + window, + activation_handler, + action_handler, + deactivation_handler, + ); + BoxCastPtr::to_mut_ptr(adapter) + } + + #[no_mangle] + pub extern "C" fn accesskit_ios_subclassing_adapter_free( + adapter: *mut ios_subclassing_adapter, + ) { + drop(box_from_ptr(adapter)); + } + + /// You must call `accesskit_ios_queued_events_raise` on the returned pointer. It can be null if the adapter is not active. + #[no_mangle] + pub extern "C" fn accesskit_ios_subclassing_adapter_update_if_active( + adapter: *mut ios_subclassing_adapter, + update_factory: tree_update_factory, + update_factory_userdata: *mut c_void, + ) -> *mut ios_queued_events { + let update_factory = update_factory.unwrap(); + let update_factory_userdata = tree_update_factory_userdata(update_factory_userdata); + let adapter = mut_from_ptr(adapter); + let events = + adapter.update_if_active(|| *box_from_ptr(update_factory(update_factory_userdata))); + BoxCastPtr::to_nullable_mut_ptr(events) + } +} diff --git a/src/lib.rs b/src/lib.rs index 0708e9f..6bf80a7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,14 @@ mod geometry; #[cfg(any(target_os = "android", feature = "cbindgen"))] mod android; +#[cfg(any( + target_os = "ios", + target_os = "tvos", + target_os = "visionos", + target_os = "watchos", + feature = "cbindgen" +))] +mod ios; #[cfg(any(target_os = "macos", feature = "cbindgen"))] mod macos; #[cfg(any( @@ -42,6 +50,14 @@ use std::{ pub use android::*; pub use common::*; pub use geometry::*; +#[cfg(any( + target_os = "ios", + target_os = "tvos", + target_os = "visionos", + target_os = "watchos", + feature = "cbindgen" +))] +pub use ios::*; #[cfg(any(target_os = "macos", feature = "cbindgen"))] pub use macos::*; #[cfg(any(