mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 22:01:31 +11:00
Support listing available video modes for a monitor (#896)
* Support listing available video modes for a monitor * Use derivative for Windows `MonitorHandle` * Update FEATURES.md * Fix multiline if statement * Add documentation for `VideoMode` type
This commit is contained in:
parent
2b89ddec15
commit
47b5dfa034
|
@ -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)
|
||||
|
||||
|
|
|
@ -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"
|
||||
|
@ -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"
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
@ -161,8 +162,9 @@ Legend:
|
|||
|
||||
### System information
|
||||
|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|
|
||||
|
|
14
examples/video_modes.rs
Normal file
14
examples/video_modes.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
@ -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"))]
|
||||
|
|
|
@ -44,6 +44,45 @@ impl Iterator for AvailableMonitorsIter {
|
|||
}
|
||||
}
|
||||
|
||||
/// Describes a fullscreen video mode of a monitor.
|
||||
///
|
||||
/// Can be acquired with:
|
||||
/// - [`MonitorHandle::video_modes`][monitor_get].
|
||||
///
|
||||
/// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes
|
||||
#[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 {
|
||||
/// Returns the resolution of this video mode.
|
||||
pub fn dimensions(&self) -> PhysicalSize {
|
||||
self.dimensions.into()
|
||||
}
|
||||
|
||||
/// Returns the bit depth of this video mode, as in how many bits you have
|
||||
/// available per color. This is generally 24 bits or 32 bits on modern
|
||||
/// systems, depending on whether the alpha channel is counted or not.
|
||||
///
|
||||
/// ## Platform-specific
|
||||
///
|
||||
/// - **Wayland:** Always returns 32.
|
||||
/// - **iOS:** Always returns 32.
|
||||
pub fn bit_depth(&self) -> u16 {
|
||||
self.bit_depth
|
||||
}
|
||||
|
||||
/// Returns the refresh rate of this video mode. **Note**: the returned
|
||||
/// refresh rate is an integer approximation, and you shouldn't rely on this
|
||||
/// value to be exact.
|
||||
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.
|
||||
|
@ -88,4 +127,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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -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,
|
||||
refresh_rate: refresh_rate as u16,
|
||||
});
|
||||
}
|
||||
|
||||
modes.into_iter()
|
||||
}
|
||||
}
|
||||
|
||||
// MonitorHandleExtIOS
|
||||
|
|
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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 {
|
||||
|
@ -77,6 +80,7 @@ impl MonitorHandle {
|
|||
position,
|
||||
primary,
|
||||
rect,
|
||||
video_modes,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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 {
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
use std::{env, slice};
|
||||
use std::str::FromStr;
|
||||
|
||||
use monitor::VideoMode;
|
||||
use dpi::validate_hidpi_factor;
|
||||
use super::*;
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
} 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,
|
||||
|
@ -129,6 +157,6 @@ impl XConnection {
|
|||
};
|
||||
|
||||
(self.xrandr.XRRFreeOutputInfo)(output_info);
|
||||
Some((name, hidpi_factor))
|
||||
Some((name, hidpi_factor, modes.collect()))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)]
|
||||
|
@ -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();
|
||||
|
|
|
@ -1,21 +1,25 @@
|
|||
use winapi::shared::minwindef::{BOOL, DWORD, LPARAM, TRUE};
|
||||
use winapi::shared::minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD};
|
||||
use winapi::shared::windef::{HDC, HMONITOR, HWND, LPRECT, POINT};
|
||||
use winapi::um::winnt::LONG;
|
||||
use winapi::um::winuser;
|
||||
use winapi::um::{wingdi, winuser};
|
||||
|
||||
use std::{mem, ptr, io};
|
||||
use std::collections::VecDeque;
|
||||
use std::collections::{HashSet, VecDeque};
|
||||
use std::{io, mem, ptr};
|
||||
|
||||
use super::{EventLoop, util};
|
||||
use super::{util, EventLoop};
|
||||
use dpi::{PhysicalPosition, PhysicalSize};
|
||||
use monitor::VideoMode;
|
||||
use platform_impl::platform::dpi::{dpi_to_scale_factor, get_monitor_dpi};
|
||||
use platform_impl::platform::window::Window;
|
||||
|
||||
/// Win32 implementation of the main `MonitorHandle` object.
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Derivative)]
|
||||
#[derivative(Debug, Clone)]
|
||||
pub struct MonitorHandle {
|
||||
/// Monitor handle.
|
||||
hmonitor: HMonitor,
|
||||
#[derivative(Debug = "ignore")]
|
||||
monitor_info: winuser::MONITORINFOEXW,
|
||||
/// The system name of the monitor.
|
||||
monitor_name: String,
|
||||
/// True if this is the primary monitor.
|
||||
|
@ -130,6 +134,7 @@ impl MonitorHandle {
|
|||
position: (place.left as i32, place.top as i32),
|
||||
dimensions,
|
||||
hidpi_factor: dpi_to_scale_factor(get_monitor_dpi(hmonitor).unwrap_or(96)),
|
||||
monitor_info,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,4 +175,39 @@ impl MonitorHandle {
|
|||
pub fn hidpi_factor(&self) -> f64 {
|
||||
self.hidpi_factor
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
||||
// EnumDisplaySettingsExW can return duplicate values (or some of the
|
||||
// fields are probably changing, but we aren't looking at those fields
|
||||
// anyway), so we're using a HashSet deduplicate
|
||||
let mut modes = HashSet::new();
|
||||
let mut i = 0;
|
||||
|
||||
loop {
|
||||
unsafe {
|
||||
let device_name = self.monitor_info.szDevice.as_ptr();
|
||||
let mut mode: wingdi::DEVMODEW = mem::zeroed();
|
||||
mode.dmSize = mem::size_of_val(&mode) as WORD;
|
||||
if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 {
|
||||
break;
|
||||
}
|
||||
i += 1;
|
||||
|
||||
const REQUIRED_FIELDS: DWORD = wingdi::DM_BITSPERPEL
|
||||
| wingdi::DM_PELSWIDTH
|
||||
| wingdi::DM_PELSHEIGHT
|
||||
| wingdi::DM_DISPLAYFREQUENCY;
|
||||
assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS);
|
||||
|
||||
modes.insert(VideoMode {
|
||||
dimensions: (mode.dmPelsWidth, mode.dmPelsHeight),
|
||||
bit_depth: mode.dmBitsPerPel as u16,
|
||||
refresh_rate: mode.dmDisplayFrequency as u16,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
modes.into_iter()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue