2019-05-26 11:10:41 +10:00
|
|
|
use std::{
|
2019-07-30 04:16:14 +10:00
|
|
|
collections::{BTreeSet, VecDeque},
|
2019-05-26 11:10:41 +10:00
|
|
|
fmt,
|
|
|
|
ops::{Deref, DerefMut},
|
|
|
|
};
|
|
|
|
|
2019-06-22 01:33:15 +10:00
|
|
|
use crate::{
|
|
|
|
dpi::{PhysicalPosition, PhysicalSize},
|
2019-07-30 04:16:14 +10:00
|
|
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
2019-08-27 08:47:23 +10:00
|
|
|
platform_impl::platform::{
|
|
|
|
app_state,
|
|
|
|
ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
|
|
|
|
},
|
2019-06-22 01:33:15 +10:00
|
|
|
};
|
2019-05-26 11:10:41 +10:00
|
|
|
|
2019-07-31 16:57:31 +10:00
|
|
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
2019-07-30 04:16:14 +10:00
|
|
|
pub struct VideoMode {
|
|
|
|
pub(crate) size: (u32, u32),
|
|
|
|
pub(crate) bit_depth: u16,
|
|
|
|
pub(crate) refresh_rate: u16,
|
2019-07-31 16:57:31 +10:00
|
|
|
pub(crate) screen_mode: id,
|
2019-07-30 04:16:14 +10:00
|
|
|
pub(crate) monitor: MonitorHandle,
|
|
|
|
}
|
|
|
|
|
2019-07-31 16:57:31 +10:00
|
|
|
impl Clone for VideoMode {
|
|
|
|
fn clone(&self) -> VideoMode {
|
|
|
|
VideoMode {
|
|
|
|
size: self.size,
|
|
|
|
bit_depth: self.bit_depth,
|
|
|
|
refresh_rate: self.refresh_rate,
|
|
|
|
screen_mode: unsafe { msg_send![self.screen_mode, retain] },
|
|
|
|
monitor: self.monitor.clone(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for VideoMode {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
assert_main_thread!("`VideoMode` can only be dropped on the main thread on iOS");
|
2019-08-27 08:47:23 +10:00
|
|
|
let () = msg_send![self.screen_mode, release];
|
2019-07-31 16:57:31 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-30 04:16:14 +10:00
|
|
|
impl VideoMode {
|
2019-07-31 16:57:31 +10:00
|
|
|
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
|
|
|
|
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
|
2019-08-27 08:47:23 +10:00
|
|
|
let os_capabilities = app_state::os_capabilities();
|
|
|
|
let refresh_rate: NSInteger = if os_capabilities.maximum_frames_per_second {
|
|
|
|
msg_send![uiscreen, maximumFramesPerSecond]
|
|
|
|
} else {
|
|
|
|
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
|
|
|
|
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
|
|
|
|
//
|
|
|
|
// All iOS devices support 60 fps, and on devices where `maximumFramesPerSecond` is not
|
|
|
|
// supported, they are all guaranteed to have 60hz refresh rates. This does not
|
|
|
|
// correctly handle external displays. ProMotion displays support 120fps, but they were
|
|
|
|
// introduced at the same time as the `maximumFramesPerSecond` API.
|
|
|
|
//
|
|
|
|
// FIXME: earlier OSs could calculate the refresh rate using
|
|
|
|
// `-[CADisplayLink duration]`.
|
|
|
|
os_capabilities.maximum_frames_per_second_err_msg("defaulting to 60 fps");
|
|
|
|
60
|
|
|
|
};
|
2019-07-31 16:57:31 +10:00
|
|
|
let size: CGSize = msg_send![screen_mode, size];
|
|
|
|
VideoMode {
|
|
|
|
size: (size.width as u32, size.height as u32),
|
|
|
|
bit_depth: 32,
|
|
|
|
refresh_rate: refresh_rate as u16,
|
|
|
|
screen_mode: msg_send![screen_mode, retain],
|
|
|
|
monitor: MonitorHandle::retained_new(uiscreen),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-30 04:16:14 +10:00
|
|
|
pub fn size(&self) -> PhysicalSize {
|
|
|
|
self.size.into()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn bit_depth(&self) -> u16 {
|
|
|
|
self.bit_depth
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn refresh_rate(&self) -> u16 {
|
|
|
|
self.refresh_rate
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn monitor(&self) -> RootMonitorHandle {
|
|
|
|
RootMonitorHandle {
|
|
|
|
inner: self.monitor.clone(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-26 11:10:41 +10:00
|
|
|
|
2019-07-30 04:16:14 +10:00
|
|
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
2019-05-26 11:10:41 +10:00
|
|
|
pub struct Inner {
|
|
|
|
uiscreen: id,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for Inner {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
let () = msg_send![self.uiscreen, release];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-07-30 04:16:14 +10:00
|
|
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
2019-05-26 11:10:41 +10:00
|
|
|
pub struct MonitorHandle {
|
|
|
|
inner: Inner,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Deref for MonitorHandle {
|
|
|
|
type Target = Inner;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Inner {
|
|
|
|
unsafe {
|
2019-06-22 01:33:15 +10:00
|
|
|
assert_main_thread!(
|
|
|
|
"`MonitorHandle` methods can only be run on the main thread on iOS"
|
|
|
|
);
|
2019-05-26 11:10:41 +10:00
|
|
|
}
|
|
|
|
&self.inner
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl DerefMut for MonitorHandle {
|
|
|
|
fn deref_mut(&mut self) -> &mut Inner {
|
|
|
|
unsafe {
|
2019-06-22 01:33:15 +10:00
|
|
|
assert_main_thread!(
|
|
|
|
"`MonitorHandle` methods can only be run on the main thread on iOS"
|
|
|
|
);
|
2019-05-26 11:10:41 +10:00
|
|
|
}
|
|
|
|
&mut self.inner
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsafe impl Send for MonitorHandle {}
|
|
|
|
unsafe impl Sync for MonitorHandle {}
|
|
|
|
|
|
|
|
impl Clone for MonitorHandle {
|
|
|
|
fn clone(&self) -> MonitorHandle {
|
|
|
|
MonitorHandle::retained_new(self.uiscreen)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Drop for MonitorHandle {
|
|
|
|
fn drop(&mut self) {
|
|
|
|
unsafe {
|
|
|
|
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl fmt::Debug for MonitorHandle {
|
2019-06-18 04:27:00 +10:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2019-05-26 11:10:41 +10:00
|
|
|
#[derive(Debug)]
|
|
|
|
struct MonitorHandle {
|
|
|
|
name: Option<String>,
|
2019-06-13 16:33:44 +10:00
|
|
|
size: PhysicalSize,
|
2019-05-26 11:10:41 +10:00
|
|
|
position: PhysicalPosition,
|
|
|
|
hidpi_factor: f64,
|
|
|
|
}
|
|
|
|
|
|
|
|
let monitor_id_proxy = MonitorHandle {
|
2019-05-30 11:29:54 +10:00
|
|
|
name: self.name(),
|
2019-06-13 16:33:44 +10:00
|
|
|
size: self.size(),
|
2019-05-30 11:29:54 +10:00
|
|
|
position: self.position(),
|
|
|
|
hidpi_factor: self.hidpi_factor(),
|
2019-05-26 11:10:41 +10:00
|
|
|
};
|
|
|
|
|
|
|
|
monitor_id_proxy.fmt(f)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl MonitorHandle {
|
|
|
|
pub fn retained_new(uiscreen: id) -> MonitorHandle {
|
|
|
|
unsafe {
|
|
|
|
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS");
|
|
|
|
let () = msg_send![uiscreen, retain];
|
|
|
|
}
|
2019-06-22 01:33:15 +10:00
|
|
|
MonitorHandle {
|
|
|
|
inner: Inner { uiscreen },
|
|
|
|
}
|
2019-05-26 11:10:41 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Inner {
|
2019-05-30 11:29:54 +10:00
|
|
|
pub fn name(&self) -> Option<String> {
|
2019-05-26 11:10:41 +10:00
|
|
|
unsafe {
|
2019-07-31 16:57:31 +10:00
|
|
|
let main = main_uiscreen();
|
|
|
|
if self.uiscreen == main.uiscreen {
|
2019-05-26 11:10:41 +10:00
|
|
|
Some("Primary".to_string())
|
2019-07-31 16:57:31 +10:00
|
|
|
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
|
2019-05-26 11:10:41 +10:00
|
|
|
Some("Mirrored".to_string())
|
|
|
|
} else {
|
|
|
|
uiscreens()
|
|
|
|
.iter()
|
|
|
|
.position(|rhs| rhs.uiscreen == self.uiscreen)
|
|
|
|
.map(|idx| idx.to_string())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-30 11:29:54 +10:00
|
|
|
|
2019-06-13 16:33:44 +10:00
|
|
|
pub fn size(&self) -> PhysicalSize {
|
2019-05-26 11:10:41 +10:00
|
|
|
unsafe {
|
2019-05-30 11:29:54 +10:00
|
|
|
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
|
2019-05-26 11:10:41 +10:00
|
|
|
(bounds.size.width as f64, bounds.size.height as f64).into()
|
|
|
|
}
|
|
|
|
}
|
2019-05-30 11:29:54 +10:00
|
|
|
|
|
|
|
pub fn position(&self) -> PhysicalPosition {
|
2019-05-26 11:10:41 +10:00
|
|
|
unsafe {
|
2019-05-30 11:29:54 +10:00
|
|
|
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
|
2019-05-26 11:10:41 +10:00
|
|
|
(bounds.origin.x as f64, bounds.origin.y as f64).into()
|
|
|
|
}
|
|
|
|
}
|
2019-05-30 11:29:54 +10:00
|
|
|
|
|
|
|
pub fn hidpi_factor(&self) -> f64 {
|
2019-05-26 11:10:41 +10:00
|
|
|
unsafe {
|
2019-05-30 11:29:54 +10:00
|
|
|
let scale: CGFloat = msg_send![self.ui_screen(), nativeScale];
|
2019-05-26 11:10:41 +10:00
|
|
|
scale as f64
|
|
|
|
}
|
|
|
|
}
|
2019-06-13 04:07:25 +10:00
|
|
|
|
2019-07-30 04:16:14 +10:00
|
|
|
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
|
|
|
let mut modes = BTreeSet::new();
|
2019-07-31 16:57:31 +10:00
|
|
|
unsafe {
|
|
|
|
let available_modes: id = msg_send![self.uiscreen, availableModes];
|
|
|
|
let available_mode_count: NSUInteger = msg_send![available_modes, count];
|
|
|
|
|
|
|
|
for i in 0..available_mode_count {
|
|
|
|
let mode: id = msg_send![available_modes, objectAtIndex: i];
|
|
|
|
modes.insert(RootVideoMode {
|
|
|
|
video_mode: VideoMode::retained_new(self.uiscreen, mode),
|
|
|
|
});
|
|
|
|
}
|
2019-06-13 04:07:25 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
modes.into_iter()
|
|
|
|
}
|
2019-05-26 11:10:41 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// MonitorHandleExtIOS
|
|
|
|
impl Inner {
|
2019-05-30 11:29:54 +10:00
|
|
|
pub fn ui_screen(&self) -> id {
|
2019-05-26 11:10:41 +10:00
|
|
|
self.uiscreen
|
|
|
|
}
|
2019-07-31 16:57:31 +10:00
|
|
|
|
|
|
|
pub fn preferred_video_mode(&self) -> RootVideoMode {
|
|
|
|
unsafe {
|
|
|
|
let mode: id = msg_send![self.uiscreen, preferredMode];
|
|
|
|
RootVideoMode {
|
|
|
|
video_mode: VideoMode::retained_new(self.uiscreen, mode),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-05-26 11:10:41 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// requires being run on main thread
|
|
|
|
pub unsafe fn main_uiscreen() -> MonitorHandle {
|
|
|
|
let uiscreen: id = msg_send![class!(UIScreen), mainScreen];
|
|
|
|
MonitorHandle::retained_new(uiscreen)
|
|
|
|
}
|
|
|
|
|
|
|
|
// requires being run on main thread
|
2019-07-31 16:57:31 +10:00
|
|
|
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
|
|
|
|
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
|
2019-05-26 11:10:41 +10:00
|
|
|
MonitorHandle::retained_new(uiscreen)
|
|
|
|
}
|
|
|
|
|
|
|
|
// requires being run on main thread
|
|
|
|
pub unsafe fn uiscreens() -> VecDeque<MonitorHandle> {
|
|
|
|
let screens: id = msg_send![class!(UIScreen), screens];
|
|
|
|
let count: NSUInteger = msg_send![screens, count];
|
|
|
|
let mut result = VecDeque::with_capacity(count as _);
|
|
|
|
let screens_enum: id = msg_send![screens, objectEnumerator];
|
|
|
|
loop {
|
|
|
|
let screen: id = msg_send![screens_enum, nextObject];
|
|
|
|
if screen == nil {
|
2019-06-22 01:33:15 +10:00
|
|
|
break result;
|
2019-05-26 11:10:41 +10:00
|
|
|
}
|
|
|
|
result.push_back(MonitorHandle::retained_new(screen));
|
|
|
|
}
|
|
|
|
}
|