Move Android backend to winit-android (#4250)

This commit is contained in:
Mads Marquart 2025-05-24 13:29:53 +02:00 committed by GitHub
parent 04482d5a2e
commit b1f8d778a1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 102 additions and 76 deletions

3
.github/CODEOWNERS vendored
View file

@ -1,6 +1,5 @@
# Android
/src/platform/android.rs @MarijnS95
/src/platform_impl/android @MarijnS95
/winit-android @MarijnS95
# Apple (AppKit + UIKit)
/src/platform/ios.rs @madsmtm

View file

@ -182,6 +182,10 @@ jobs:
- name: Test winit core
run: cargo test -p winit-core
- name: Test winit Android
if: contains(matrix.platform.target, 'android')
run: cargo $CMD test -p winit-android --target=${{ matrix.platform.target }} --features native-activity --no-run
- name: Test winit Orbital
if: contains(matrix.platform.target, 'redox')
run: cargo test -p winit-orbital

View file

@ -1,5 +1,5 @@
[workspace]
members = ["dpi", "winit-core", "winit-orbital"]
members = ["dpi", "winit-*"]
resolver = "2"
[workspace.package]
@ -12,6 +12,7 @@ rust-version = "1.80"
# Workspace dependencies.
# `winit` has no version here to allow using it in dev deps for docs.
winit = { path = "." }
winit-android = { version = "0.0.0", path = "winit-android" }
winit-core = { version = "0.0.0", path = "winit-core" }
winit-orbital = { version = "0.0.0", path = "winit-orbital" }
@ -149,9 +150,10 @@ targets = [
# Features are documented in either `lib.rs` or under `winit::platform`.
[features]
android-game-activity = ["android-activity/game-activity"]
android-native-activity = ["android-activity/native-activity"]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"]
android-game-activity = ["winit-android/game-activity"]
android-native-activity = ["winit-android/native-activity"]
mint = ["dpi/mint"]
serde = [
"dep:serde",
@ -197,10 +199,8 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] }
[target.'cfg(not(target_os = "android"))'.dev-dependencies]
softbuffer.workspace = true
# Android
[target.'cfg(target_os = "android")'.dependencies]
android-activity.workspace = true
ndk.workspace = true
winit-android.workspace = true
# AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]

View file

@ -328,6 +328,26 @@ impl winit_core::event_loop::run_on_demand::EventLoopExtRunOnDemand for EventLoo
}
}
#[cfg(android_platform)]
impl winit_android::EventLoopExtAndroid for EventLoop {
fn android_app(&self) -> &winit_android::activity::AndroidApp {
&self.event_loop.android_app
}
}
#[cfg(android_platform)]
impl winit_android::EventLoopBuilderExtAndroid for EventLoopBuilder {
fn with_android_app(&mut self, app: winit_android::activity::AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
}
fn handle_volume_keys(&mut self) -> &mut Self {
self.platform_specific.ignore_volume_keys = false;
self
}
}
/// ```compile_error
/// use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand;
/// use winit::event_loop::EventLoop;

View file

@ -3,7 +3,7 @@
//! Only the modules corresponding to the platform you're compiling to will be available.
#[cfg(android_platform)]
pub mod android;
pub use winit_android as android;
#[cfg(ios_platform)]
pub mod ios;
#[cfg(macos_platform)]

View file

@ -1,5 +1,5 @@
#[cfg(android_platform)]
mod android;
pub(crate) use winit_android as platform;
#[cfg(target_vendor = "apple")]
mod apple;
#[cfg(any(x11_platform, wayland_platform))]
@ -11,8 +11,6 @@ mod web;
#[cfg(windows_platform)]
mod windows;
#[cfg(android_platform)]
use self::android as platform;
#[cfg(target_vendor = "apple")]
use self::apple as platform;
#[cfg(any(x11_platform, wayland_platform))]

35
winit-android/Cargo.toml Normal file
View file

@ -0,0 +1,35 @@
[package]
description = "Winit's Android backend"
documentation = "https://docs.rs/winit-android"
edition.workspace = true
license.workspace = true
name = "winit-android"
repository.workspace = true
rust-version.workspace = true
version = "0.0.0"
[features]
game-activity = ["android-activity/game-activity"]
native-activity = ["android-activity/native-activity"]
serde = ["dep:serde", "bitflags/serde", "smol_str/serde", "dpi/serde", "winit-core/serde"]
[dependencies]
bitflags.workspace = true
dpi.workspace = true
rwh_06.workspace = true
serde = { workspace = true, optional = true }
smol_str.workspace = true
tracing.workspace = true
winit-core.workspace = true
# Platform-specific
[target.'cfg(target_os = "android")'.dependencies]
android-activity.workspace = true
ndk.workspace = true
[dev-dependencies]
winit.workspace = true
[package.metadata.docs.rs]
features = ["serde", "native-activity"]
targets = ["aarch64-linux-android"]

1
winit-android/README.md Symbolic link
View file

@ -0,0 +1 @@
../README.md

View file

@ -1,4 +1,5 @@
use std::cell::Cell;
use std::fmt;
use std::hash::Hash;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex};
@ -26,7 +27,7 @@ use winit_core::window::{
WindowAttributes, WindowButtons, WindowId, WindowLevel,
};
mod keycodes;
use crate::keycodes;
static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
@ -42,7 +43,7 @@ struct SharedFlagSetter {
flag: Arc<AtomicBool>,
}
impl SharedFlagSetter {
pub fn set(&self) -> bool {
fn set(&self) -> bool {
self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok()
}
}
@ -57,21 +58,21 @@ struct SharedFlag {
// we just need to know at the start of a main loop iteration if a redraw
// was queued and be able to read and clear the state atomically)
impl SharedFlag {
pub fn new() -> Self {
fn new() -> Self {
Self { flag: Arc::new(AtomicBool::new(false)) }
}
pub fn setter(&self) -> SharedFlagSetter {
fn setter(&self) -> SharedFlagSetter {
SharedFlagSetter { flag: self.flag.clone() }
}
pub fn get_and_reset(&self) -> bool {
fn get_and_reset(&self) -> bool {
self.flag.swap(false, std::sync::atomic::Ordering::AcqRel)
}
}
#[derive(Clone)]
pub struct RedrawRequester {
struct RedrawRequester {
flag: SharedFlagSetter,
waker: AndroidAppWaker,
}
@ -87,7 +88,7 @@ impl RedrawRequester {
RedrawRequester { flag: flag.setter(), waker }
}
pub fn request_redraw(&self) {
fn request_redraw(&self) {
if self.flag.set() {
// Only explicitly try to wake up the main loop when the flag
// value changes
@ -98,7 +99,7 @@ impl RedrawRequester {
#[derive(Debug)]
pub struct EventLoop {
pub(crate) android_app: AndroidApp,
pub android_app: AndroidApp,
window_target: ActiveEventLoop,
redraw_flag: SharedFlag,
loop_running: bool, // Dispatched `NewEvents<Init>`
@ -111,9 +112,9 @@ pub struct EventLoop {
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes {
pub(crate) android_app: Option<AndroidApp>,
pub(crate) ignore_volume_keys: bool,
pub struct PlatformSpecificEventLoopAttributes {
pub android_app: Option<AndroidApp>,
pub ignore_volume_keys: bool,
}
impl Default for PlatformSpecificEventLoopAttributes {
@ -126,9 +127,7 @@ impl Default for PlatformSpecificEventLoopAttributes {
const GLOBAL_WINDOW: WindowId = WindowId::from_raw(0);
impl EventLoop {
pub(crate) fn new(
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
let android_app = attributes.android_app.as_ref().expect(
"An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
Android",
@ -158,7 +157,7 @@ impl EventLoop {
})
}
pub(crate) fn window_target(&self) -> &dyn RootActiveEventLoop {
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
&self.window_target
}
@ -751,7 +750,7 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
pub struct PlatformSpecificWindowAttributes;
#[derive(Debug)]
pub(crate) struct Window {
pub struct Window {
app: AndroidApp,
redraw_requester: RedrawRequester,
}
@ -766,11 +765,11 @@ impl Window {
Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() })
}
pub fn config(&self) -> ConfigurationRef {
pub(crate) fn config(&self) -> ConfigurationRef {
self.app.config()
}
pub fn content_rect(&self) -> Rect {
pub(crate) fn content_rect(&self) -> Rect {
self.app.content_rect()
}
@ -999,16 +998,6 @@ impl CoreWindow for Window {
}
}
#[derive(Default, Clone, Debug)]
pub struct OsError;
use std::fmt::{self, Display, Formatter};
impl Display for OsError {
fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> {
write!(fmt, "Android OS Error")
}
}
fn screen_size(app: &AndroidApp) -> PhysicalSize<u32> {
if let Some(native_window) = app.native_window() {
PhysicalSize::new(native_window.width() as _, native_window.height() as _)

View file

@ -69,10 +69,19 @@
//! logging as above).
//! 4. Pass a clone of the `AndroidApp` that your application receives to Winit when building your
//! event loop (as shown above).
#![cfg(target_os = "android")]
mod event_loop;
mod keycodes;
use winit_core::event_loop::ActiveEventLoop as CoreActiveEventLoop;
use winit_core::window::Window as CoreWindow;
use self::activity::{AndroidApp, ConfigurationRef, Rect};
use crate::event_loop::{ActiveEventLoop, EventLoop, EventLoopBuilder};
use crate::window::Window;
pub use crate::event_loop::{
ActiveEventLoop, EventLoop, EventLoopProxy, PlatformSpecificEventLoopAttributes,
PlatformSpecificWindowAttributes, Window,
};
/// Additional methods on [`EventLoop`] that are specific to Android.
pub trait EventLoopExtAndroid {
@ -80,12 +89,6 @@ pub trait EventLoopExtAndroid {
fn android_app(&self) -> &AndroidApp;
}
impl EventLoopExtAndroid for EventLoop {
fn android_app(&self) -> &AndroidApp {
&self.event_loop.android_app
}
}
/// Additional methods on [`ActiveEventLoop`] that are specific to Android.
pub trait ActiveEventLoopExtAndroid {
/// Get the [`AndroidApp`] which was used to create this event loop.
@ -99,21 +102,21 @@ pub trait WindowExtAndroid {
fn config(&self) -> ConfigurationRef;
}
impl WindowExtAndroid for dyn Window + '_ {
impl WindowExtAndroid for dyn CoreWindow + '_ {
fn content_rect(&self) -> Rect {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
let window = self.cast_ref::<Window>().unwrap();
window.content_rect()
}
fn config(&self) -> ConfigurationRef {
let window = self.cast_ref::<crate::platform_impl::Window>().unwrap();
let window = self.cast_ref::<Window>().unwrap();
window.config()
}
}
impl ActiveEventLoopExtAndroid for dyn ActiveEventLoop + '_ {
impl ActiveEventLoopExtAndroid for dyn CoreActiveEventLoop + '_ {
fn android_app(&self) -> &AndroidApp {
let event_loop = self.cast_ref::<crate::platform_impl::ActiveEventLoop>().unwrap();
let event_loop = self.cast_ref::<ActiveEventLoop>().unwrap();
&event_loop.app
}
}
@ -130,18 +133,6 @@ pub trait EventLoopBuilderExtAndroid {
fn handle_volume_keys(&mut self) -> &mut Self;
}
impl EventLoopBuilderExtAndroid for EventLoopBuilder {
fn with_android_app(&mut self, app: AndroidApp) -> &mut Self {
self.platform_specific.android_app = Some(app);
self
}
fn handle_volume_keys(&mut self) -> &mut Self {
self.platform_specific.ignore_volume_keys = false;
self
}
}
/// Re-export of the `android_activity` API
///
/// Winit re-exports the `android_activity` API for convenience so that most
@ -170,16 +161,5 @@ pub mod activity {
// feature enabled, so we avoid inlining it so that they're forced to view
// it on the crate's own docs.rs page.
#[doc(no_inline)]
#[cfg(android_platform)]
pub use android_activity::*;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct Rect;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct ConfigurationRef;
#[cfg(not(android_platform))]
#[doc(hidden)]
pub struct AndroidApp;
}