#![allow(unsafe_code)]
use super::{Bounds, DevicePixels, Pixels, Point, Size};
use anyhow::Result;
use gpui::{self, DisplayId, PlatformDisplay};
use std::fmt;
#[repr(C)]
pub struct ANativeWindow {
_priv: [u8; 0],
}
#[repr(C)]
pub struct AConfiguration {
_priv: [u8; 0],
}
unsafe extern "C" {
fn ANativeWindow_getWidth(window: *mut ANativeWindow) -> i32;
fn ANativeWindow_getHeight(window: *mut ANativeWindow) -> i32;
fn ANativeWindow_acquire(window: *mut ANativeWindow);
fn ANativeWindow_release(window: *mut ANativeWindow);
fn AConfiguration_new() -> *mut AConfiguration;
fn AConfiguration_delete(config: *mut AConfiguration);
fn AConfiguration_fromAssetManager(
config: *mut AConfiguration,
asset_manager: *mut std::ffi::c_void,
);
fn AConfiguration_getDensity(config: *mut AConfiguration) -> i32;
}
const DENSITY_DEFAULT: i32 = 160;
pub struct AndroidDisplay {
window: *mut ANativeWindow,
scale_factor: f32,
id: u64,
}
unsafe impl Send for AndroidDisplay {}
unsafe impl Sync for AndroidDisplay {}
impl AndroidDisplay {
pub unsafe fn from_window(window: *mut ANativeWindow, density_dpi: i32) -> Result<Self> {
anyhow::ensure!(!window.is_null(), "ANativeWindow pointer must not be null");
unsafe { ANativeWindow_acquire(window) };
let density = if density_dpi > 0 {
density_dpi
} else {
DENSITY_DEFAULT
};
let scale_factor = density as f32 / DENSITY_DEFAULT as f32;
let id = window as u64;
Ok(Self {
window,
scale_factor,
id,
})
}
pub unsafe fn from_activity(
window: *mut ANativeWindow,
asset_manager: *mut std::ffi::c_void,
) -> Result<Self> {
anyhow::ensure!(!window.is_null(), "ANativeWindow must not be null");
anyhow::ensure!(!asset_manager.is_null(), "AssetManager must not be null");
let density_dpi = unsafe {
let config = AConfiguration_new();
anyhow::ensure!(!config.is_null(), "AConfiguration_new() returned null");
AConfiguration_fromAssetManager(config, asset_manager);
let dpi = AConfiguration_getDensity(config);
AConfiguration_delete(config);
dpi
};
unsafe { Self::from_window(window, density_dpi) }
}
pub fn headless(width: i32, height: i32) -> Self {
Self {
window: std::ptr::null_mut(),
scale_factor: 1.0,
id: ((width as u64) << 32) | (height as u64),
}
}
pub fn physical_width(&self) -> DevicePixels {
if self.window.is_null() {
return DevicePixels(0);
}
DevicePixels(unsafe { ANativeWindow_getWidth(self.window) })
}
pub fn physical_height(&self) -> DevicePixels {
if self.window.is_null() {
return DevicePixels(0);
}
DevicePixels(unsafe { ANativeWindow_getHeight(self.window) })
}
pub fn physical_size(&self) -> Size<DevicePixels> {
Size {
width: self.physical_width(),
height: self.physical_height(),
}
}
pub fn logical_width(&self) -> Pixels {
Pixels(self.physical_width().0 as f32 / self.scale_factor)
}
pub fn logical_height(&self) -> Pixels {
Pixels(self.physical_height().0 as f32 / self.scale_factor)
}
pub fn logical_size(&self) -> Size<Pixels> {
Size {
width: self.logical_width(),
height: self.logical_height(),
}
}
pub fn bounds(&self) -> Bounds<DevicePixels> {
Bounds {
origin: Point {
x: DevicePixels(0),
y: DevicePixels(0),
},
size: self.physical_size(),
}
}
pub fn logical_bounds(&self) -> Bounds<Pixels> {
Bounds {
origin: Point {
x: Pixels(0.0),
y: Pixels(0.0),
},
size: self.logical_size(),
}
}
pub fn scale_factor(&self) -> f32 {
self.scale_factor
}
pub fn dpi(&self) -> i32 {
(self.scale_factor * DENSITY_DEFAULT as f32).round() as i32
}
pub fn id(&self) -> u64 {
self.id
}
pub fn is_real(&self) -> bool {
!self.window.is_null()
}
pub fn native_window(&self) -> *mut ANativeWindow {
self.window
}
}
impl Drop for AndroidDisplay {
fn drop(&mut self) {
if !self.window.is_null() {
unsafe { ANativeWindow_release(self.window) };
}
}
}
impl Clone for AndroidDisplay {
fn clone(&self) -> Self {
if !self.window.is_null() {
unsafe { ANativeWindow_acquire(self.window) };
}
Self {
window: self.window,
scale_factor: self.scale_factor,
id: self.id,
}
}
}
impl fmt::Debug for AndroidDisplay {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("AndroidDisplay")
.field("id", &format_args!("{:#x}", self.id))
.field("physical_size", &self.physical_size())
.field("scale_factor", &self.scale_factor)
.field("dpi", &self.dpi())
.finish()
}
}
impl PlatformDisplay for AndroidDisplay {
fn id(&self) -> DisplayId {
DisplayId::new(self.id as u32)
}
fn uuid(&self) -> Result<uuid::Uuid> {
let namespace = uuid::Uuid::NAMESPACE_OID;
let name = format!("android-display-{:#x}", self.id);
Ok(uuid::Uuid::new_v5(&namespace, name.as_bytes()))
}
fn bounds(&self) -> gpui::Bounds<gpui::Pixels> {
let logical = self.logical_size();
gpui::Bounds {
origin: gpui::point(gpui::px(0.0), gpui::px(0.0)),
size: gpui::size(gpui::px(logical.width.0), gpui::px(logical.height.0)),
}
}
}
impl fmt::Display for AndroidDisplay {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ps = self.physical_size();
write!(
f,
"AndroidDisplay({}×{}px, {:.1}×, {}dpi)",
ps.width.0,
ps.height.0,
self.scale_factor,
self.dpi(),
)
}
}
pub struct DisplayList {
displays: Vec<AndroidDisplay>,
}
impl DisplayList {
pub fn single(display: AndroidDisplay) -> Self {
Self {
displays: vec![display],
}
}
pub fn primary(&self) -> Option<&AndroidDisplay> {
self.displays.first()
}
pub fn all(&self) -> &[AndroidDisplay] {
&self.displays
}
pub fn len(&self) -> usize {
self.displays.len()
}
pub fn is_empty(&self) -> bool {
self.displays.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn headless_display_geometry() {
let d = AndroidDisplay::headless(1920, 1080);
assert_eq!(d.physical_width(), DevicePixels(1920));
assert_eq!(d.physical_height(), DevicePixels(1080));
assert!(!d.is_real());
}
#[test]
fn headless_scale_factor_is_one() {
let d = AndroidDisplay::headless(800, 600);
assert!((d.scale_factor() - 1.0).abs() < f32::EPSILON);
assert_eq!(d.dpi(), DENSITY_DEFAULT);
}
#[test]
fn logical_size_divides_by_scale() {
let d = AndroidDisplay::headless(1440, 2960);
let logical = d.logical_size();
assert!((logical.width.0 - 1440.0).abs() < f32::EPSILON);
assert!((logical.height.0 - 2960.0).abs() < f32::EPSILON);
}
#[test]
fn display_id_differs_for_different_sizes() {
let d1 = AndroidDisplay::headless(1080, 1920);
let d2 = AndroidDisplay::headless(1440, 2560);
assert_ne!(d1.id(), d2.id());
}
#[test]
fn bounds_origin_is_zero() {
let d = AndroidDisplay::headless(1080, 2400);
let b = d.bounds();
assert_eq!(b.origin.x, DevicePixels(0));
assert_eq!(b.origin.y, DevicePixels(0));
assert_eq!(b.size.width, DevicePixels(1080));
assert_eq!(b.size.height, DevicePixels(2400));
}
#[test]
fn display_list_primary() {
let list = DisplayList::single(AndroidDisplay::headless(1080, 1920));
assert!(!list.is_empty());
assert_eq!(list.len(), 1);
let primary = list.primary().expect("primary display");
assert_eq!(primary.physical_width(), DevicePixels(1080));
}
#[test]
fn clone_headless_is_independent() {
let d1 = AndroidDisplay::headless(720, 1280);
let d2 = d1.clone();
assert_eq!(d1.id(), d2.id());
assert_eq!(d1.physical_size(), d2.physical_size());
}
#[test]
fn debug_format_contains_dimensions() {
let d = AndroidDisplay::headless(1080, 1920);
let s = format!("{:?}", d);
assert!(s.contains("1080"));
assert!(s.contains("1920"));
}
#[test]
fn display_format_contains_scale() {
let d = AndroidDisplay::headless(1080, 1920);
let s = format!("{}", d);
assert!(s.contains("1.0"));
}
#[test]
fn dpi_scales_linearly_with_scale_factor() {
let d = AndroidDisplay::headless(0, 0);
assert_eq!(d.dpi(), 160);
}
}