Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support listing available video modes for a monitor #896

Merged
merged 5 commits into from Jun 12, 2019
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
- Rename several functions to improve both internal consistency and compliance with Rust API guidelines.
- Remove `WindowBuilder::multitouch` field, since it was only implemented on a few platforms. Multitouch is always enabled now.
- **Breaking:** On macOS, change `ns` identifiers to use snake_case for consistency with iOS's `ui` identifiers.
- Add `MonitorHandle::video_modes` method for retrieving supported video modes for the given monitor.

# Version 0.19.1 (2019-04-08)

Expand Down
2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ lazy_static = "1"
libc = "0.2"
log = "0.4"
serde = { version = "1", optional = true, features = ["serde_derive"] }
derivative = "1.0.2"

[dev-dependencies]
image = "0.21"
Expand All @@ -33,6 +34,7 @@ objc = "0.2.3"
cocoa = "0.18.4"
core-foundation = "0.6"
core-graphics = "0.17.3"
core-video-sys = "0.1.2"
dispatch = "0.1.4"
objc = "0.2.3"

Expand Down
8 changes: 5 additions & 3 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ If your PR makes notable changes to Winit's features, please update this section

### System Information
- **Monitor list**: Retrieve the list of monitors and their metadata, including which one is primary.
- **Video mode query**: Monitors can be queried for their supported fullscreen video modes (consisting of resolution, refresh rate, and bit depth).

### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
Expand Down Expand Up @@ -160,9 +161,10 @@ Legend:
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ |

### System information
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|------------ | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Feature |Windows|MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
|---------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Monitor list |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Video mode query |✔️ |✔️ |✔️ |✔️ |❌ |✔️ |❌ |

### Input handling
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |Emscripten|
Expand Down
14 changes: 14 additions & 0 deletions examples/video_modes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
extern crate winit;

use winit::event_loop::EventLoop;

fn main() {
let event_loop = EventLoop::new();
let monitor = event_loop.primary_monitor();

println!("Listing available video modes:");

for mode in monitor.video_modes() {
println!("{:?}", mode);
}
}
4 changes: 4 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ extern crate log;
#[cfg(feature = "serde")]
#[macro_use]
extern crate serde;
#[macro_use]
extern crate derivative;

#[cfg(target_os = "windows")]
extern crate winapi;
Expand All @@ -101,6 +103,8 @@ extern crate dispatch;
extern crate core_foundation;
#[cfg(target_os = "macos")]
extern crate core_graphics;
#[cfg(target_os = "macos")]
extern crate core_video_sys;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd"))]
extern crate x11_dl;
#[cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "netbsd", target_os = "openbsd", target_os = "windows"))]
Expand Down
27 changes: 27 additions & 0 deletions src/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,27 @@ impl Iterator for AvailableMonitorsIter {
}
}

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub struct VideoMode {
pub(crate) dimensions: (u32, u32),
pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16,
}

impl VideoMode {
pub fn dimensions(&self) -> PhysicalSize {
self.dimensions.into()
}

pub fn bit_depth(&self) -> u16 {
self.bit_depth
}

pub fn refresh_rate(&self) -> u16 {
self.refresh_rate
}
}

/// Handle to a monitor.
///
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
Expand Down Expand Up @@ -88,4 +109,10 @@ impl MonitorHandle {
pub fn hidpi_factor(&self) -> f64 {
self.inner.hidpi_factor()
}

/// Returns all fullscreen video modes supported by this monitor.
#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.inner.video_modes()
}
}
32 changes: 24 additions & 8 deletions src/platform_impl/ios/monitor.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
use std::{
collections::VecDeque,
collections::{HashSet, VecDeque},
fmt,
ops::{Deref, DerefMut},
};

use dpi::{PhysicalPosition, PhysicalSize};
use monitor::VideoMode;

use platform_impl::platform::ffi::{
id,
nil,
CGFloat,
CGRect,
NSUInteger,
};
use platform_impl::platform::ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger};

pub struct Inner {
uiscreen: id,
Expand Down Expand Up @@ -134,6 +129,27 @@ impl Inner {
scale as f64
}
}

pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] };

let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] };
let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] };

let mut modes = HashSet::with_capacity(available_mode_count);

for i in 0..available_mode_count {
let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] };
let size: CGSize = unsafe { msg_send![mode, size] };
modes.insert(VideoMode {
dimensions: (size.width as u32, size.height as u32),
bit_depth: 32,
goddessfreya marked this conversation as resolved.
Show resolved Hide resolved
refresh_rate: refresh_rate as u16,
});
}

modes.into_iter()
}
}

// MonitorHandleExtIOS
Expand Down
10 changes: 9 additions & 1 deletion src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use icon::Icon;
use error::{ExternalError, NotSupportedError, OsError as RootOsError};
use event::Event;
use event_loop::{EventLoopClosed, ControlFlow, EventLoopWindowTarget as RootELW};
use monitor::MonitorHandle as RootMonitorHandle;
use monitor::{MonitorHandle as RootMonitorHandle, VideoMode};
use window::{WindowAttributes, CursorIcon};
use self::x11::{XConnection, XError};
use self::x11::ffi::XVisualInfo;
Expand Down Expand Up @@ -142,6 +142,14 @@ impl MonitorHandle {
&MonitorHandle::Wayland(ref m) => m.hidpi_factor() as f64,
}
}

#[inline]
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
match self {
MonitorHandle::X(m) => Box::new(m.video_modes()),
MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
}
}
}

impl Window {
Expand Down
15 changes: 15 additions & 0 deletions src/platform_impl/linux/wayland/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}
use event::ModifiersState;
use dpi::{PhysicalPosition, PhysicalSize};
use platform_impl::platform::sticky_exit_callback;
use monitor::VideoMode;

use super::window::WindowStore;
use super::WindowId;
Expand Down Expand Up @@ -584,6 +585,20 @@ impl MonitorHandle {
.with_info(&self.proxy, |_, info| info.scale_factor)
.unwrap_or(1)
}

#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode>
{
self.mgr
.with_info(&self.proxy, |_, info| info.modes.clone())
.unwrap_or(vec![])
.into_iter()
.map(|x| VideoMode {
dimensions: (x.dimensions.0 as u32, x.dimensions.1 as u32),
refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16,
bit_depth: 32
})
}
}

pub fn primary_monitor(outputs: &OutputMgr) -> MonitorHandle {
Expand Down
11 changes: 10 additions & 1 deletion src/platform_impl/linux/x11/monitor.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::os::raw::*;
use parking_lot::Mutex;

use dpi::{PhysicalPosition, PhysicalSize};
use monitor::VideoMode;
use super::{util, XConnection, XError};
use super::ffi::{
RRCrtcChangeNotifyMask,
Expand Down Expand Up @@ -56,6 +57,8 @@ pub struct MonitorHandle {
pub(crate) hidpi_factor: f64,
/// Used to determine which windows are on this monitor
pub(crate) rect: util::AaRect,
/// Supported video modes on this monitor
video_modes: Vec<VideoMode>,
}

impl MonitorHandle {
Expand All @@ -66,7 +69,7 @@ impl MonitorHandle {
repr: util::MonitorRepr,
primary: bool,
) -> Option<Self> {
let (name, hidpi_factor) = unsafe { xconn.get_output_info(resources, &repr)? };
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, &repr)? };
let (dimensions, position) = unsafe { (repr.dimensions(), repr.position()) };
let rect = util::AaRect::new(position, dimensions);
Some(MonitorHandle {
Expand All @@ -77,6 +80,7 @@ impl MonitorHandle {
position,
primary,
rect,
video_modes,
})
}

Expand All @@ -101,6 +105,11 @@ impl MonitorHandle {
pub fn hidpi_factor(&self) -> f64 {
self.hidpi_factor
}

#[inline]
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
self.video_modes.clone().into_iter()
}
}

impl XConnection {
Expand Down
32 changes: 30 additions & 2 deletions src/platform_impl/linux/x11/util/randr.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use std::{env, slice};
use std::str::FromStr;

use monitor::VideoMode;
use dpi::validate_hidpi_factor;
use super::*;

Expand Down Expand Up @@ -101,7 +102,7 @@ impl XConnection {
&self,
resources: *mut ffi::XRRScreenResources,
repr: &MonitorRepr,
) -> Option<(String, f64)> {
) -> Option<(String, f64, Vec<VideoMode>)> {
let output_info = (self.xrandr.XRRGetOutputInfo)(
self.display,
resources,
Expand All @@ -114,6 +115,33 @@ impl XConnection {
let _ = self.check_errors(); // discard `BadRROutput` error
return None;
}

let screen = (self.xlib.XDefaultScreen)(self.display);
let bit_depth = (self.xlib.XDefaultDepth)(self.display, screen);

let output_modes =
slice::from_raw_parts((*output_info).modes, (*output_info).nmode as usize);
let resource_modes = slice::from_raw_parts((*resources).modes, (*resources).nmode as usize);

let modes = resource_modes
.iter()
// XRROutputInfo contains an array of mode ids that correspond to
// modes in the array in XRRScreenResources
.filter(|x| output_modes.iter().any(|id| x.id == *id))
.map(|x| {
let refresh_rate = if x.dotClock > 0 && x.hTotal > 0 && x.vTotal > 0 {
x.dotClock as u64 * 1000 / (x.hTotal as u64 * x.vTotal as u64)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this math?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what's going on here, but this is the same line of code that SDL, GTK and GLFW use to calculate the display refresh rate. I couldn't find any documentation for this.

} else {
0
};

VideoMode {
dimensions: (x.width, x.height),
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
bit_depth: bit_depth as u16,
}
});

let name_slice = slice::from_raw_parts(
(*output_info).name as *mut u8,
(*output_info).nameLen as usize,
Expand All @@ -129,6 +157,6 @@ impl XConnection {
};

(self.xrandr.XRRFreeOutputInfo)(output_info);
Some((name, hidpi_factor))
Some((name, hidpi_factor, modes.collect()))
}
}
51 changes: 49 additions & 2 deletions src/platform_impl/macos/monitor.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,18 @@
use std::{collections::VecDeque, fmt};

use cocoa::{appkit::NSScreen, base::{id, nil}, foundation::{NSString, NSUInteger}};
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
use cocoa::{
appkit::NSScreen,
base::{id, nil},
foundation::{NSString, NSUInteger},
};
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayMode};
use core_video_sys::{
kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay,
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease,
};

use dpi::{PhysicalPosition, PhysicalSize};
use monitor::VideoMode;
use platform_impl::platform::util::IdRef;

#[derive(Clone, PartialEq)]
Expand Down Expand Up @@ -93,6 +102,44 @@ impl MonitorHandle {
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
}

pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
let cv_refresh_rate = unsafe {
let mut display_link = std::ptr::null_mut();
assert_eq!(
CVDisplayLinkCreateWithCGDisplay(self.0, &mut display_link),
kCVReturnSuccess
);
let time = CVDisplayLinkGetNominalOutputVideoRefreshPeriod(display_link);
CVDisplayLinkRelease(display_link);

// This value is indefinite if an invalid display link was specified
assert!(time.flags & kCVTimeIsIndefinite == 0);

time.timeScale as i64 / time.timeValue
};

CGDisplayMode::all_display_modes(self.0, std::ptr::null())
.expect("failed to obtain list of display modes")
.into_iter()
.map(move |mode| {
let cg_refresh_rate = mode.refresh_rate().round() as i64;

// CGDisplayModeGetRefreshRate returns 0.0 for any display that
// isn't a CRT
let refresh_rate = if cg_refresh_rate > 0 {
cg_refresh_rate
} else {
cv_refresh_rate
};

VideoMode {
dimensions: (mode.width() as u32, mode.height() as u32),
refresh_rate: refresh_rate as u16,
bit_depth: mode.bit_depth() as u16,
}
})
}

pub(crate) fn ns_screen(&self) -> Option<id> {
unsafe {
let native_id = self.native_identifier();
Expand Down
Loading