Skip to content

Commit

Permalink
Android: implement Monitor/VideoModeHandle
Browse files Browse the repository at this point in the history
We were now able to revert `VideoModeHandle::size()` returning `Option`.
However Orbital is still missing an implementation and will return `Default`.
  • Loading branch information
daxpedda committed Jul 25, 2024
1 parent fb4384e commit dd70949
Show file tree
Hide file tree
Showing 14 changed files with 351 additions and 75 deletions.
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,9 @@ softbuffer = { version = "0.4.0", default-features = false, features = [
# Android
[target.'cfg(target_os = "android")'.dependencies]
android-activity = "0.6.0"
jni = "0.21"
ndk = { version = "0.9.0", default-features = false }
ndk-context = "0.1"

# AppKit or UIKit
[target.'cfg(target_vendor = "apple")'.dependencies]
Expand Down
36 changes: 15 additions & 21 deletions examples/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -285,17 +285,14 @@ impl Application {
}

if let Some(current_mode) = monitor.current_video_mode() {
if let Some(PhysicalSize { width, height }) = current_mode.size() {
let bits =
current_mode.bit_depth().map(|bits| format!("x{bits}")).unwrap_or_default();
let m_hz = current_mode
.refresh_rate_millihertz()
.map(|m_hz| format!(" @ {}.{} Hz", m_hz.get() / 1000, m_hz.get() % 1000))
.unwrap_or_default();
info!(" {width}x{height}{bits}{m_hz}");
} else {
info!(" {current_mode:?}");
}
let PhysicalSize { width, height } = current_mode.size();
let bits =
current_mode.bit_depth().map(|bits| format!("x{bits}")).unwrap_or_default();
let m_hz = current_mode
.refresh_rate_millihertz()
.map(|m_hz| format!(" @ {}.{} Hz", m_hz.get() / 1000, m_hz.get() % 1000))
.unwrap_or_default();
info!(" {width}x{height}{bits}{m_hz}");
}

if let Some(PhysicalPosition { x, y }) = monitor.position() {
Expand All @@ -306,16 +303,13 @@ impl Application {

info!(" Available modes (width x height x bit-depth):");
for mode in monitor.video_modes() {
if let Some(PhysicalSize { width, height }) = mode.size() {
let bits = mode.bit_depth().map(|bits| format!("x{bits}")).unwrap_or_default();
let m_hz = mode
.refresh_rate_millihertz()
.map(|m_hz| format!(" @ {}.{} Hz", m_hz.get() / 1000, m_hz.get() % 1000))
.unwrap_or_default();
info!(" {width}x{height}{bits}{m_hz}");
} else {
info!(" {mode:?}");
}
let PhysicalSize { width, height } = mode.size();
let bits = mode.bit_depth().map(|bits| format!("x{bits}")).unwrap_or_default();
let m_hz = mode
.refresh_rate_millihertz()
.map(|m_hz| format!(" @ {}.{} Hz", m_hz.get() / 1000, m_hz.get() % 1000))
.unwrap_or_default();
info!(" {width}x{height}{bits}{m_hz}");
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ changelog entry.
information is available. This "detailed monitors" can be used in `Window::set_fullscreen()` as
well.
- Add `MonitorHandle::current_video_mode()`.
- On Android, fully implement `MonitorHandle` and `VideoModeHandle`.

### Changed

Expand Down Expand Up @@ -85,7 +86,7 @@ changelog entry.
- On Web, `CursorGrabMode::Locked` now lets `DeviceEvent::MouseMotion` return raw data, not OS
accelerated, if the browser supports it.
- `VideoModeHandle::refresh_rate_millihertz()` now returns an `Option<NonZeroU32>`.
- `VideoModeHandle::size()` and `bit_depth()` now return an `Option`.
- `VideoModeHandle::bit_depth()` now returns an `Option`.

### Removed

Expand Down
15 changes: 14 additions & 1 deletion src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ impl VideoModeHandle {
///
/// [`Window::inner_size()`]: crate::window::Window::inner_size
#[inline]
pub fn size(&self) -> Option<PhysicalSize<u32>> {
pub fn size(&self) -> PhysicalSize<u32> {
self.video_mode.size()
}

Expand All @@ -81,6 +81,19 @@ impl VideoModeHandle {
}
}

impl std::fmt::Display for VideoModeHandle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{}x{} {}{}",
self.size().width,
self.size().height,
self.refresh_rate_millihertz().map(|rate| format!("@ {rate} mHz ")).unwrap_or_default(),
self.bit_depth().map(|bit_depth| format!("({bit_depth} bpp)")).unwrap_or_default(),
)
}
}

/// Handle to a monitor.
///
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
Expand Down
167 changes: 167 additions & 0 deletions src/platform_impl/android/ffi.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
use android_activity::AndroidApp;
use jni::objects::{JObject, JObjectArray, JString, JValueOwned};
use jni::{AttachGuard, JavaVM};

pub fn jvm(app: &AndroidApp) -> JavaVM {
unsafe { JavaVM::from_raw(app.vm_as_ptr() as _) }.expect("unexpected null pointer")
}

pub fn env(jvm: &JavaVM) -> AttachGuard<'_> {
jvm.attach_current_thread().expect("unexpected failure accessing `JNIEnv`")
}

pub fn activity<'e>(app: &AndroidApp, _: &AttachGuard<'e>) -> Activity<'e> {
Activity(unsafe { JObject::from_raw(app.activity_as_ptr() as _) })
}

pub struct Activity<'e>(JObject<'e>);

impl Activity<'_> {
pub fn window_manager<'e>(&self, env: &mut AttachGuard<'e>) -> WindowManager<'e> {
WindowManager(
env.call_method(&self.0, "getWindowManager", "()Landroid/view/WindowManager;", &[])
.and_then(JValueOwned::l)
.expect("`Activity.getWindowManager()` is not expected to fail"),
)
}
}

pub fn context<'e>(_: &mut AttachGuard<'e>) -> Context<'e> {
Context(unsafe { JObject::from_raw(ndk_context::android_context().context() as _) })
}

pub struct Context<'e>(JObject<'e>);

impl Context<'_> {
pub fn display_manager<'e>(&self, env: &mut AttachGuard<'e>) -> DisplayManager<'e> {
let class = env.get_object_class(&self.0).expect("failed to find `Context` class");
let display_service = env
.get_static_field(&class, "DISPLAY_SERVICE", "Ljava/lang/String;")
.expect("failed to find `Context.DISPLAY_SERVICE");
let display_manager = env
.call_method(&self.0, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;", &[
(&display_service).into(),
])
.and_then(JValueOwned::l)
.expect(
"`Context.getSystemService()` is not expected to fail with \
`Context.DISPLAY_SERVICE`",
);

DisplayManager(display_manager)
}
}

pub struct DisplayManager<'e>(JObject<'e>);

impl DisplayManager<'_> {
pub fn display<'e>(&self, env: &mut AttachGuard<'e>, display_id: i32) -> Option<Display<'e>> {
env.call_method(&self.0, "getDisplay", "(I)Landroid/view/Display;", &[display_id.into()])
.and_then(JValueOwned::l)
.map(Display)
.ok()
}

pub fn displays<'e>(&self, env: &mut AttachGuard<'e>) -> Vec<Display<'e>> {
let displays = JObjectArray::from(
env.call_method(&self.0, "getDisplays", "()[Landroid/view/Display;", &[])
.and_then(JValueOwned::l)
.expect("`DisplayManager.getDisplays()` is not expected to fail"),
);

let length = env.get_array_length(&displays).unwrap();

(0..length)
.map(|index| env.get_object_array_element(&displays, index).map(Display).unwrap())
.collect()
}
}

pub struct WindowManager<'e>(JObject<'e>);

impl WindowManager<'_> {
pub fn default_display<'e>(&self, env: &mut AttachGuard<'e>) -> Display<'e> {
Display(
env.call_method(&self.0, "getDefaultDisplay", "()Landroid/view/Display;", &[])
.and_then(JValueOwned::l)
.expect("`WindowManager.getDefaultDisplay()` is not expected to fail"),
)
}
}

pub struct Display<'e>(JObject<'e>);

impl Display<'_> {
pub fn default_display(env: &mut AttachGuard<'_>) -> i32 {
let class = env.find_class("android/view/Display").expect("failed to find `Display`");
env.get_static_field(&class, "DEFAULT_DISPLAY", "I;")
.and_then(JValueOwned::i)
.expect("failed to find `Display.DEFAULT_DISPLAY")
}

pub fn display_id(&self, env: &mut AttachGuard<'_>) -> i32 {
env.call_method(&self.0, "getDisplayId", "()I", &[])
.and_then(JValueOwned::i)
.expect("`Display.getDisplayId()` is not expected to fail")
}

pub fn name(&self, env: &mut AttachGuard<'_>) -> String {
let name = JString::from(
env.call_method(&self.0, "getName", "()Ljava/lang/String;", &[])
.and_then(JValueOwned::l)
.expect("`Display.name()` is not expected to fail"),
);
let name = env.get_string(&name).unwrap();
name.to_string_lossy().to_string()
}

pub fn mode<'e>(&self, env: &mut AttachGuard<'e>) -> Mode<'e> {
Mode(
env.call_method(&self.0, "getMode", "()Landroid/view/Display$Mode;", &[])
.and_then(JValueOwned::l)
.expect("`Display.getMode()` is not expected to fail"),
)
}

pub fn supported_modes<'e>(&self, env: &mut AttachGuard<'e>) -> Vec<Mode<'e>> {
let modes = JObjectArray::from(
env.call_method(&self.0, "getSupportedModes", "()[Landroid/view/Display$Mode;", &[])
.and_then(JValueOwned::l)
.expect("`Display.getSupportedModes()` is not expected to fail"),
);

let length = env.get_array_length(&modes).unwrap();

(0..length)
.map(|index| env.get_object_array_element(&modes, index).map(Mode).unwrap())
.collect()
}
}

pub struct Mode<'e>(JObject<'e>);

impl Mode<'_> {
pub fn mode_id(&self, env: &mut AttachGuard<'_>) -> i32 {
env.call_method(&self.0, "getModeId", "()I", &[])
.and_then(JValueOwned::i)
.expect("`Display.Mode.getModeId()` is not expected to fail")
}

pub fn physical_width(&self, env: &mut AttachGuard<'_>) -> i32 {
env.call_method(&self.0, "getPhysicalWidth", "()I", &[])
.and_then(JValueOwned::i)
.expect("`Display.Mode.getPhysicalWidth()` is not expected to fail")
}

pub fn physical_height(&self, env: &mut AttachGuard<'_>) -> i32 {
env.call_method(&self.0, "getPhysicalHeight", "()I", &[])
.and_then(JValueOwned::i)
.expect("`Display.Mode.getPhysicalHeight()` is not expected to fail")
}

pub fn refresh_rate(&self, env: &mut AttachGuard<'_>) -> f32 {
env.call_method(&self.0, "getRefreshRate", "()F", &[])
.and_then(JValueOwned::f)
.expect("`Display.Mode.getRefreshRate()` is not expected to fail")
}
}
Loading

0 comments on commit dd70949

Please sign in to comment.