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 # Android
/src/platform/android.rs @MarijnS95 /winit-android @MarijnS95
/src/platform_impl/android @MarijnS95
# Apple (AppKit + UIKit) # Apple (AppKit + UIKit)
/src/platform/ios.rs @madsmtm /src/platform/ios.rs @madsmtm

View file

@ -182,6 +182,10 @@ jobs:
- name: Test winit core - name: Test winit core
run: cargo test -p 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 - name: Test winit Orbital
if: contains(matrix.platform.target, 'redox') if: contains(matrix.platform.target, 'redox')
run: cargo test -p winit-orbital run: cargo test -p winit-orbital

View file

@ -1,5 +1,5 @@
[workspace] [workspace]
members = ["dpi", "winit-core", "winit-orbital"] members = ["dpi", "winit-*"]
resolver = "2" resolver = "2"
[workspace.package] [workspace.package]
@ -12,6 +12,7 @@ rust-version = "1.80"
# Workspace dependencies. # Workspace dependencies.
# `winit` has no version here to allow using it in dev deps for docs. # `winit` has no version here to allow using it in dev deps for docs.
winit = { path = "." } winit = { path = "." }
winit-android = { version = "0.0.0", path = "winit-android" }
winit-core = { version = "0.0.0", path = "winit-core" } winit-core = { version = "0.0.0", path = "winit-core" }
winit-orbital = { version = "0.0.0", path = "winit-orbital" } 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 are documented in either `lib.rs` or under `winit::platform`.
[features] [features]
android-game-activity = ["android-activity/game-activity"]
android-native-activity = ["android-activity/native-activity"]
default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] 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"] mint = ["dpi/mint"]
serde = [ serde = [
"dep:serde", "dep:serde",
@ -197,10 +199,8 @@ tracing-subscriber = { workspace = true, features = ["env-filter"] }
[target.'cfg(not(target_os = "android"))'.dev-dependencies] [target.'cfg(not(target_os = "android"))'.dev-dependencies]
softbuffer.workspace = true softbuffer.workspace = true
# Android
[target.'cfg(target_os = "android")'.dependencies] [target.'cfg(target_os = "android")'.dependencies]
android-activity.workspace = true winit-android.workspace = true
ndk.workspace = true
# AppKit or UIKit # AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies] [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 /// ```compile_error
/// use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand; /// use winit::event_loop::run_on_demand::EventLoopExtRunOnDemand;
/// use winit::event_loop::EventLoop; /// 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. //! Only the modules corresponding to the platform you're compiling to will be available.
#[cfg(android_platform)] #[cfg(android_platform)]
pub mod android; pub use winit_android as android;
#[cfg(ios_platform)] #[cfg(ios_platform)]
pub mod ios; pub mod ios;
#[cfg(macos_platform)] #[cfg(macos_platform)]

View file

@ -1,5 +1,5 @@
#[cfg(android_platform)] #[cfg(android_platform)]
mod android; pub(crate) use winit_android as platform;
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
mod apple; mod apple;
#[cfg(any(x11_platform, wayland_platform))] #[cfg(any(x11_platform, wayland_platform))]
@ -11,8 +11,6 @@ mod web;
#[cfg(windows_platform)] #[cfg(windows_platform)]
mod windows; mod windows;
#[cfg(android_platform)]
use self::android as platform;
#[cfg(target_vendor = "apple")] #[cfg(target_vendor = "apple")]
use self::apple as platform; use self::apple as platform;
#[cfg(any(x11_platform, wayland_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::cell::Cell;
use std::fmt;
use std::hash::Hash; use std::hash::Hash;
use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@ -26,7 +27,7 @@ use winit_core::window::{
WindowAttributes, WindowButtons, WindowId, WindowLevel, WindowAttributes, WindowButtons, WindowId, WindowLevel,
}; };
mod keycodes; use crate::keycodes;
static HAS_FOCUS: AtomicBool = AtomicBool::new(true); static HAS_FOCUS: AtomicBool = AtomicBool::new(true);
@ -42,7 +43,7 @@ struct SharedFlagSetter {
flag: Arc<AtomicBool>, flag: Arc<AtomicBool>,
} }
impl SharedFlagSetter { impl SharedFlagSetter {
pub fn set(&self) -> bool { fn set(&self) -> bool {
self.flag.compare_exchange(false, true, Ordering::AcqRel, Ordering::Relaxed).is_ok() 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 // 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) // was queued and be able to read and clear the state atomically)
impl SharedFlag { impl SharedFlag {
pub fn new() -> Self { fn new() -> Self {
Self { flag: Arc::new(AtomicBool::new(false)) } Self { flag: Arc::new(AtomicBool::new(false)) }
} }
pub fn setter(&self) -> SharedFlagSetter { fn setter(&self) -> SharedFlagSetter {
SharedFlagSetter { flag: self.flag.clone() } 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) self.flag.swap(false, std::sync::atomic::Ordering::AcqRel)
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct RedrawRequester { struct RedrawRequester {
flag: SharedFlagSetter, flag: SharedFlagSetter,
waker: AndroidAppWaker, waker: AndroidAppWaker,
} }
@ -87,7 +88,7 @@ impl RedrawRequester {
RedrawRequester { flag: flag.setter(), waker } RedrawRequester { flag: flag.setter(), waker }
} }
pub fn request_redraw(&self) { fn request_redraw(&self) {
if self.flag.set() { if self.flag.set() {
// Only explicitly try to wake up the main loop when the flag // Only explicitly try to wake up the main loop when the flag
// value changes // value changes
@ -98,7 +99,7 @@ impl RedrawRequester {
#[derive(Debug)] #[derive(Debug)]
pub struct EventLoop { pub struct EventLoop {
pub(crate) android_app: AndroidApp, pub android_app: AndroidApp,
window_target: ActiveEventLoop, window_target: ActiveEventLoop,
redraw_flag: SharedFlag, redraw_flag: SharedFlag,
loop_running: bool, // Dispatched `NewEvents<Init>` loop_running: bool, // Dispatched `NewEvents<Init>`
@ -111,9 +112,9 @@ pub struct EventLoop {
} }
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct PlatformSpecificEventLoopAttributes { pub struct PlatformSpecificEventLoopAttributes {
pub(crate) android_app: Option<AndroidApp>, pub android_app: Option<AndroidApp>,
pub(crate) ignore_volume_keys: bool, pub ignore_volume_keys: bool,
} }
impl Default for PlatformSpecificEventLoopAttributes { impl Default for PlatformSpecificEventLoopAttributes {
@ -126,9 +127,7 @@ impl Default for PlatformSpecificEventLoopAttributes {
const GLOBAL_WINDOW: WindowId = WindowId::from_raw(0); const GLOBAL_WINDOW: WindowId = WindowId::from_raw(0);
impl EventLoop { impl EventLoop {
pub(crate) fn new( pub fn new(attributes: &PlatformSpecificEventLoopAttributes) -> Result<Self, EventLoopError> {
attributes: &PlatformSpecificEventLoopAttributes,
) -> Result<Self, EventLoopError> {
let android_app = attributes.android_app.as_ref().expect( let android_app = attributes.android_app.as_ref().expect(
"An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \ "An `AndroidApp` as passed to android_main() is required to create an `EventLoop` on \
Android", 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 &self.window_target
} }
@ -751,7 +750,7 @@ impl rwh_06::HasDisplayHandle for OwnedDisplayHandle {
pub struct PlatformSpecificWindowAttributes; pub struct PlatformSpecificWindowAttributes;
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct Window { pub struct Window {
app: AndroidApp, app: AndroidApp,
redraw_requester: RedrawRequester, redraw_requester: RedrawRequester,
} }
@ -766,11 +765,11 @@ impl Window {
Ok(Self { app: el.app.clone(), redraw_requester: el.redraw_requester.clone() }) 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() self.app.config()
} }
pub fn content_rect(&self) -> Rect { pub(crate) fn content_rect(&self) -> Rect {
self.app.content_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> { fn screen_size(app: &AndroidApp) -> PhysicalSize<u32> {
if let Some(native_window) = app.native_window() { if let Some(native_window) = app.native_window() {
PhysicalSize::new(native_window.width() as _, native_window.height() as _) PhysicalSize::new(native_window.width() as _, native_window.height() as _)

View file

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