mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 22:01:31 +11:00
Add exclusive fullscreen mode (#925)
* Add exclusive fullscreen mode * Add `WindowExtMacOS::set_fullscreen_presentation_options` * Capture display for exclusive fullscreen on macOS * Fix applying video mode on macOS after a fullscreen cycle * Fix compilation on iOS * Set monitor appropriately for fullscreen on macOS * Fix exclusive to borderless fullscreen transitions on macOS * Fix borderless to exclusive fullscreen transition on macOS * Sort video modes on Windows * Fix fullscreen issues on Windows * Fix video mode changes during exclusive fullscreen on Windows * Add video mode sorting for macOS and iOS * Fix monitor `ns_screen` returning `None` after video mode change * Fix "multithreaded" example on macOS * Restore video mode upon closing an exclusive fullscreen window * Fix "multithreaded" example closing multiple windows at once * Fix compilation on Linux * Update FEATURES.md * Don't care about logical monitor groups on X11 * Add exclusive fullscreen for X11 * Update FEATURES.md * Fix transitions between exclusive and borderless fullscreen on X11 * Update CHANGELOG.md * Document that Wayland doesn't support exclusive fullscreen * Replace core-graphics display mode bindings on macOS * Use `panic!()` instead of `unreachable!()` in "fullscreen" example * Fix fullscreen "always on top" flag on Windows * Track current monitor for fullscreen in "multithreaded" example * Fix exclusive fullscreen sometimes not positioning window properly * Format * More formatting and fix CI issues * Fix formatting * Fix changelog formatting
This commit is contained in:
parent
131e67ddc1
commit
5bc3cf18d9
|
@ -1,10 +1,16 @@
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
- On macOS, drop the run closure on exit.
|
- On macOS, drop the run closure on exit.
|
||||||
- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates.
|
- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates.
|
||||||
- On X11, fix delayed events after window redraw.
|
- On X11, fix delayed events after window redraw.
|
||||||
- On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface.
|
- On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface.
|
||||||
- On Windows, screen saver won't start if the window is in fullscreen mode.
|
- On Windows, screen saver won't start if the window is in fullscreen mode.
|
||||||
- Change all occurrences of the `new_user_event` method to `with_user_event`.
|
- Change all occurrences of the `new_user_event` method to `with_user_event`.
|
||||||
|
- On macOS, the dock and the menu bar are now hidden in fullscreen mode.
|
||||||
|
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
|
||||||
|
consists of `Fullscreen::Exclusive(VideoMode)` and
|
||||||
|
`Fullscreen::Borderless(MonitorHandle)` variants.
|
||||||
|
- Adds support for exclusive fullscreen mode.
|
||||||
|
|
||||||
# 0.20.0 Alpha 2 (2019-07-09)
|
# 0.20.0 Alpha 2 (2019-07-09)
|
||||||
|
|
||||||
|
|
|
@ -84,6 +84,9 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||||
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
|
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
|
||||||
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
|
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
|
||||||
creation.
|
creation.
|
||||||
|
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
|
||||||
|
for fullscreen windows, and if applicable, captures the monitor for exclusive
|
||||||
|
use by this application.
|
||||||
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
|
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
|
||||||
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
|
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
|
||||||
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
|
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
|
||||||
|
@ -157,6 +160,7 @@ Legend:
|
||||||
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|
||||||
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
||||||
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|
||||||
|
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |❌ |❌ |
|
||||||
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|
||||||
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ |
|
||||||
|
|
||||||
|
|
|
@ -1,56 +1,35 @@
|
||||||
use std::io::{self, Write};
|
use std::io::{stdin, stdout, Write};
|
||||||
use winit::{
|
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
|
||||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
event_loop::{ControlFlow, EventLoop},
|
use winit::monitor::{MonitorHandle, VideoMode};
|
||||||
monitor::MonitorHandle,
|
use winit::window::{Fullscreen, WindowBuilder};
|
||||||
window::WindowBuilder,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
let event_loop = EventLoop::new();
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
|
||||||
let mut macos_use_simple_fullscreen = false;
|
stdout().flush().unwrap();
|
||||||
|
|
||||||
let monitor = {
|
let mut num = String::new();
|
||||||
// On macOS there are two fullscreen modes "native" and "simple"
|
stdin().read_line(&mut num).unwrap();
|
||||||
#[cfg(target_os = "macos")]
|
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||||
{
|
|
||||||
print!("Please choose the fullscreen mode: (1) native, (2) simple: ");
|
|
||||||
io::stdout().flush().unwrap();
|
|
||||||
|
|
||||||
let mut num = String::new();
|
let fullscreen = Some(match num {
|
||||||
io::stdin().read_line(&mut num).unwrap();
|
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
|
||||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
|
||||||
match num {
|
_ => panic!("Please enter a valid number"),
|
||||||
2 => macos_use_simple_fullscreen = true,
|
});
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prompt for monitor when using native fullscreen
|
|
||||||
if !macos_use_simple_fullscreen {
|
|
||||||
Some(prompt_for_monitor(&event_loop))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(not(target_os = "macos"))]
|
|
||||||
Some(prompt_for_monitor(&event_loop))
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut is_fullscreen = monitor.is_some();
|
|
||||||
let mut is_maximized = false;
|
let mut is_maximized = false;
|
||||||
let mut decorations = true;
|
let mut decorations = true;
|
||||||
|
|
||||||
let window = WindowBuilder::new()
|
let window = WindowBuilder::new()
|
||||||
.with_title("Hello world!")
|
.with_title("Hello world!")
|
||||||
.with_fullscreen(monitor)
|
.with_fullscreen(fullscreen.clone())
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
println!("{:?}", event);
|
|
||||||
*control_flow = ControlFlow::Wait;
|
*control_flow = ControlFlow::Wait;
|
||||||
|
|
||||||
match event {
|
match event {
|
||||||
|
@ -67,35 +46,14 @@ fn main() {
|
||||||
} => match (virtual_code, state) {
|
} => match (virtual_code, state) {
|
||||||
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
|
||||||
(VirtualKeyCode::F, ElementState::Pressed) => {
|
(VirtualKeyCode::F, ElementState::Pressed) => {
|
||||||
#[cfg(target_os = "macos")]
|
if window.fullscreen().is_some() {
|
||||||
{
|
|
||||||
if macos_use_simple_fullscreen {
|
|
||||||
use winit::platform::macos::WindowExtMacOS;
|
|
||||||
if WindowExtMacOS::set_simple_fullscreen(&window, !is_fullscreen) {
|
|
||||||
is_fullscreen = !is_fullscreen;
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is_fullscreen = !is_fullscreen;
|
|
||||||
if !is_fullscreen {
|
|
||||||
window.set_fullscreen(None);
|
window.set_fullscreen(None);
|
||||||
} else {
|
} else {
|
||||||
window.set_fullscreen(Some(window.current_monitor()));
|
window.set_fullscreen(fullscreen.clone());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
(VirtualKeyCode::S, ElementState::Pressed) => {
|
(VirtualKeyCode::S, ElementState::Pressed) => {
|
||||||
println!("window.fullscreen {:?}", window.fullscreen());
|
println!("window.fullscreen {:?}", window.fullscreen());
|
||||||
|
|
||||||
#[cfg(target_os = "macos")]
|
|
||||||
{
|
|
||||||
use winit::platform::macos::WindowExtMacOS;
|
|
||||||
println!(
|
|
||||||
"window.simple_fullscreen {:?}",
|
|
||||||
WindowExtMacOS::simple_fullscreen(&window)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
(VirtualKeyCode::M, ElementState::Pressed) => {
|
(VirtualKeyCode::M, ElementState::Pressed) => {
|
||||||
is_maximized = !is_maximized;
|
is_maximized = !is_maximized;
|
||||||
|
@ -121,10 +79,10 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
print!("Please write the number of the monitor to use: ");
|
print!("Please write the number of the monitor to use: ");
|
||||||
io::stdout().flush().unwrap();
|
stdout().flush().unwrap();
|
||||||
|
|
||||||
let mut num = String::new();
|
let mut num = String::new();
|
||||||
io::stdin().read_line(&mut num).unwrap();
|
stdin().read_line(&mut num).unwrap();
|
||||||
let num = num.trim().parse().ok().expect("Please enter a number");
|
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||||
let monitor = event_loop
|
let monitor = event_loop
|
||||||
.available_monitors()
|
.available_monitors()
|
||||||
|
@ -135,3 +93,24 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
|
||||||
|
|
||||||
monitor
|
monitor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
|
||||||
|
for (i, video_mode) in monitor.video_modes().enumerate() {
|
||||||
|
println!("Video mode #{}: {}", i, video_mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
print!("Please write the number of the video mode to use: ");
|
||||||
|
stdout().flush().unwrap();
|
||||||
|
|
||||||
|
let mut num = String::new();
|
||||||
|
stdin().read_line(&mut num).unwrap();
|
||||||
|
let num = num.trim().parse().ok().expect("Please enter a number");
|
||||||
|
let video_mode = monitor
|
||||||
|
.video_modes()
|
||||||
|
.nth(num)
|
||||||
|
.expect("Please enter a valid ID");
|
||||||
|
|
||||||
|
println!("Using {}", video_mode);
|
||||||
|
|
||||||
|
video_mode
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
|
||||||
use winit::{
|
use winit::{
|
||||||
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
|
||||||
event_loop::{ControlFlow, EventLoop},
|
event_loop::{ControlFlow, EventLoop},
|
||||||
window::{CursorIcon, WindowBuilder},
|
window::{CursorIcon, Fullscreen, WindowBuilder},
|
||||||
};
|
};
|
||||||
|
|
||||||
const WINDOW_COUNT: usize = 3;
|
const WINDOW_COUNT: usize = 3;
|
||||||
|
@ -19,11 +19,34 @@ fn main() {
|
||||||
.with_inner_size(WINDOW_SIZE.into())
|
.with_inner_size(WINDOW_SIZE.into())
|
||||||
.build(&event_loop)
|
.build(&event_loop)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
|
let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect();
|
||||||
|
let mut video_mode_id = 0usize;
|
||||||
|
|
||||||
let (tx, rx) = mpsc::channel();
|
let (tx, rx) = mpsc::channel();
|
||||||
window_senders.insert(window.id(), tx);
|
window_senders.insert(window.id(), tx);
|
||||||
thread::spawn(move || {
|
thread::spawn(move || {
|
||||||
while let Ok(event) = rx.recv() {
|
while let Ok(event) = rx.recv() {
|
||||||
match event {
|
match event {
|
||||||
|
WindowEvent::Moved { .. } => {
|
||||||
|
// We need to update our chosen video mode if the window
|
||||||
|
// was moved to an another monitor, so that the window
|
||||||
|
// appears on this monitor instead when we go fullscreen
|
||||||
|
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
|
||||||
|
video_modes = window.current_monitor().video_modes().collect();
|
||||||
|
video_mode_id = video_mode_id.min(video_modes.len());
|
||||||
|
let video_mode = video_modes.iter().nth(video_mode_id);
|
||||||
|
|
||||||
|
// Different monitors may support different video modes,
|
||||||
|
// and the index we chose previously may now point to a
|
||||||
|
// completely different video mode, so notify the user
|
||||||
|
if video_mode != previous_video_mode.as_ref() {
|
||||||
|
println!(
|
||||||
|
"Window moved to another monitor, picked video mode: {}",
|
||||||
|
video_modes.iter().nth(video_mode_id).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
WindowEvent::KeyboardInput {
|
WindowEvent::KeyboardInput {
|
||||||
input:
|
input:
|
||||||
KeyboardInput {
|
KeyboardInput {
|
||||||
|
@ -44,9 +67,26 @@ fn main() {
|
||||||
false => CursorIcon::Default,
|
false => CursorIcon::Default,
|
||||||
}),
|
}),
|
||||||
D => window.set_decorations(!state),
|
D => window.set_decorations(!state),
|
||||||
F => window.set_fullscreen(match state {
|
// Cycle through video modes
|
||||||
true => Some(window.current_monitor()),
|
Right | Left => {
|
||||||
false => None,
|
video_mode_id = match key {
|
||||||
|
Left => video_mode_id.saturating_sub(1),
|
||||||
|
Right => (video_modes.len() - 1).min(video_mode_id + 1),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
println!(
|
||||||
|
"Picking video mode: {}",
|
||||||
|
video_modes.iter().nth(video_mode_id).unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
F => window.set_fullscreen(match (state, modifiers.alt) {
|
||||||
|
(true, false) => {
|
||||||
|
Some(Fullscreen::Borderless(window.current_monitor()))
|
||||||
|
}
|
||||||
|
(true, true) => Some(Fullscreen::Exclusive(
|
||||||
|
video_modes.iter().nth(video_mode_id).unwrap().clone(),
|
||||||
|
)),
|
||||||
|
(false, _) => None,
|
||||||
}),
|
}),
|
||||||
G => window.set_cursor_grab(state).unwrap(),
|
G => window.set_cursor_grab(state).unwrap(),
|
||||||
H => window.set_cursor_visible(!state),
|
H => window.set_cursor_visible(!state),
|
||||||
|
@ -56,6 +96,7 @@ fn main() {
|
||||||
println!("-> inner_position : {:?}", window.inner_position());
|
println!("-> inner_position : {:?}", window.inner_position());
|
||||||
println!("-> outer_size : {:?}", window.outer_size());
|
println!("-> outer_size : {:?}", window.outer_size());
|
||||||
println!("-> inner_size : {:?}", window.inner_size());
|
println!("-> inner_size : {:?}", window.inner_size());
|
||||||
|
println!("-> fullscreen : {:?}", window.fullscreen());
|
||||||
}
|
}
|
||||||
L => window.set_min_inner_size(match state {
|
L => window.set_min_inner_size(match state {
|
||||||
true => Some(WINDOW_SIZE.into()),
|
true => Some(WINDOW_SIZE.into()),
|
||||||
|
@ -108,6 +149,7 @@ fn main() {
|
||||||
| WindowEvent::KeyboardInput {
|
| WindowEvent::KeyboardInput {
|
||||||
input:
|
input:
|
||||||
KeyboardInput {
|
KeyboardInput {
|
||||||
|
state: ElementState::Released,
|
||||||
virtual_keycode: Some(VirtualKeyCode::Escape),
|
virtual_keycode: Some(VirtualKeyCode::Escape),
|
||||||
..
|
..
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,6 +7,6 @@ fn main() {
|
||||||
println!("Listing available video modes:");
|
println!("Listing available video modes:");
|
||||||
|
|
||||||
for mode in monitor.video_modes() {
|
for mode in monitor.video_modes() {
|
||||||
println!("{:?}", mode);
|
println!("{}", mode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -121,7 +121,6 @@ extern crate log;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
#[cfg(target_os = "windows")]
|
|
||||||
extern crate derivative;
|
extern crate derivative;
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
#[cfg(target_os = "windows")]
|
#[cfg(target_os = "windows")]
|
||||||
|
|
|
@ -52,17 +52,41 @@ impl Iterator for AvailableMonitorsIter {
|
||||||
/// - [`MonitorHandle::video_modes`][monitor_get].
|
/// - [`MonitorHandle::video_modes`][monitor_get].
|
||||||
///
|
///
|
||||||
/// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes
|
/// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Derivative)]
|
||||||
|
#[derivative(Clone, Debug = "transparent", PartialEq, Eq, Hash)]
|
||||||
pub struct VideoMode {
|
pub struct VideoMode {
|
||||||
pub(crate) size: (u32, u32),
|
pub(crate) video_mode: platform_impl::VideoMode,
|
||||||
pub(crate) bit_depth: u16,
|
}
|
||||||
pub(crate) refresh_rate: u16,
|
|
||||||
|
impl PartialOrd for VideoMode {
|
||||||
|
fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for VideoMode {
|
||||||
|
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
|
||||||
|
// TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32`
|
||||||
|
// to `u32` there
|
||||||
|
let size: (u32, u32) = self.size().into();
|
||||||
|
let other_size: (u32, u32) = other.size().into();
|
||||||
|
self.monitor().cmp(&other.monitor()).then(
|
||||||
|
size.cmp(&other_size)
|
||||||
|
.then(
|
||||||
|
self.refresh_rate()
|
||||||
|
.cmp(&other.refresh_rate())
|
||||||
|
.then(self.bit_depth().cmp(&other.bit_depth())),
|
||||||
|
)
|
||||||
|
.reverse(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VideoMode {
|
impl VideoMode {
|
||||||
/// Returns the resolution of this video mode.
|
/// Returns the resolution of this video mode.
|
||||||
|
#[inline]
|
||||||
pub fn size(&self) -> PhysicalSize {
|
pub fn size(&self) -> PhysicalSize {
|
||||||
self.size.into()
|
self.video_mode.size()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the bit depth of this video mode, as in how many bits you have
|
/// Returns the bit depth of this video mode, as in how many bits you have
|
||||||
|
@ -73,15 +97,37 @@ impl VideoMode {
|
||||||
///
|
///
|
||||||
/// - **Wayland:** Always returns 32.
|
/// - **Wayland:** Always returns 32.
|
||||||
/// - **iOS:** Always returns 32.
|
/// - **iOS:** Always returns 32.
|
||||||
|
#[inline]
|
||||||
pub fn bit_depth(&self) -> u16 {
|
pub fn bit_depth(&self) -> u16 {
|
||||||
self.bit_depth
|
self.video_mode.bit_depth()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the refresh rate of this video mode. **Note**: the returned
|
/// Returns the refresh rate of this video mode. **Note**: the returned
|
||||||
/// refresh rate is an integer approximation, and you shouldn't rely on this
|
/// refresh rate is an integer approximation, and you shouldn't rely on this
|
||||||
/// value to be exact.
|
/// value to be exact.
|
||||||
|
#[inline]
|
||||||
pub fn refresh_rate(&self) -> u16 {
|
pub fn refresh_rate(&self) -> u16 {
|
||||||
self.refresh_rate
|
self.video_mode.refresh_rate()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the monitor that this video mode is valid for. Each monitor has
|
||||||
|
/// a separate set of valid video modes.
|
||||||
|
#[inline]
|
||||||
|
pub fn monitor(&self) -> MonitorHandle {
|
||||||
|
self.video_mode.monitor()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for VideoMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}x{} @ {} Hz ({} bpp)",
|
||||||
|
self.size().width,
|
||||||
|
self.size().height,
|
||||||
|
self.refresh_rate(),
|
||||||
|
self.bit_depth()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +136,7 @@ impl VideoMode {
|
||||||
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
|
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
|
||||||
///
|
///
|
||||||
/// [`Window`]: ../window/struct.Window.html
|
/// [`Window`]: ../window/struct.Window.html
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub struct MonitorHandle {
|
pub struct MonitorHandle {
|
||||||
pub(crate) inner: platform_impl::MonitorHandle,
|
pub(crate) inner: platform_impl::MonitorHandle,
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,7 +79,7 @@ use std::fmt;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||||
monitor::MonitorHandle,
|
monitor::{MonitorHandle, VideoMode},
|
||||||
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,44 @@
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashSet, VecDeque},
|
collections::{BTreeSet, VecDeque},
|
||||||
fmt,
|
fmt,
|
||||||
ops::{Deref, DerefMut},
|
ops::{Deref, DerefMut},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
monitor::VideoMode,
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
|
platform_impl::platform::ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::platform_impl::platform::ffi::{
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger,
|
pub struct VideoMode {
|
||||||
};
|
pub(crate) size: (u32, u32),
|
||||||
|
pub(crate) bit_depth: u16,
|
||||||
|
pub(crate) refresh_rate: u16,
|
||||||
|
pub(crate) monitor: MonitorHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoMode {
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct Inner {
|
pub struct Inner {
|
||||||
uiscreen: id,
|
uiscreen: id,
|
||||||
}
|
}
|
||||||
|
@ -25,6 +51,7 @@ impl Drop for Inner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||||
pub struct MonitorHandle {
|
pub struct MonitorHandle {
|
||||||
inner: Inner,
|
inner: Inner,
|
||||||
}
|
}
|
||||||
|
@ -140,21 +167,24 @@ impl Inner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||||
let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] };
|
let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] };
|
||||||
|
|
||||||
let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] };
|
let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] };
|
||||||
let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] };
|
let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] };
|
||||||
|
|
||||||
let mut modes = HashSet::with_capacity(available_mode_count);
|
let mut modes = BTreeSet::new();
|
||||||
|
|
||||||
for i in 0..available_mode_count {
|
for i in 0..available_mode_count {
|
||||||
let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] };
|
let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] };
|
||||||
let size: CGSize = unsafe { msg_send![mode, size] };
|
let size: CGSize = unsafe { msg_send![mode, size] };
|
||||||
modes.insert(VideoMode {
|
modes.insert(RootVideoMode {
|
||||||
size: (size.width as u32, size.height as u32),
|
video_mode: VideoMode {
|
||||||
bit_depth: 32,
|
size: (size.width as u32, size.height as u32),
|
||||||
refresh_rate: refresh_rate as u16,
|
bit_depth: 32,
|
||||||
|
refresh_rate: refresh_rate as u16,
|
||||||
|
monitor: MonitorHandle::retained_new(self.uiscreen),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,15 +8,14 @@ use objc::{
|
||||||
use crate::{
|
use crate::{
|
||||||
event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent},
|
event::{DeviceId as RootDeviceId, Event, Touch, TouchPhase, WindowEvent},
|
||||||
platform::ios::MonitorHandleExtIOS,
|
platform::ios::MonitorHandleExtIOS,
|
||||||
window::{WindowAttributes, WindowId as RootWindowId},
|
platform_impl::platform::{
|
||||||
};
|
app_state::AppState,
|
||||||
|
event_loop,
|
||||||
use crate::platform_impl::platform::{
|
ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase},
|
||||||
app_state::AppState,
|
window::PlatformSpecificWindowBuilderAttributes,
|
||||||
event_loop,
|
DeviceId,
|
||||||
ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase},
|
},
|
||||||
window::PlatformSpecificWindowBuilderAttributes,
|
window::{Fullscreen, WindowAttributes, WindowId as RootWindowId},
|
||||||
DeviceId,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// requires main thread
|
// requires main thread
|
||||||
|
@ -366,8 +365,12 @@ pub unsafe fn create_window(
|
||||||
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
|
if let Some(hidpi_factor) = platform_attributes.hidpi_factor {
|
||||||
let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat];
|
let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat];
|
||||||
}
|
}
|
||||||
if let &Some(ref monitor) = &window_attributes.fullscreen {
|
match window_attributes.fullscreen {
|
||||||
let () = msg_send![window, setScreen:monitor.ui_screen()];
|
Some(Fullscreen::Exclusive(_)) => unimplemented!(),
|
||||||
|
Some(Fullscreen::Borderless(ref monitor)) => {
|
||||||
|
msg_send![window, setScreen:monitor.ui_screen()]
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
window
|
window
|
||||||
|
|
|
@ -17,7 +17,7 @@ use crate::{
|
||||||
ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask},
|
ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask},
|
||||||
monitor, view, EventLoopWindowTarget, MonitorHandle,
|
monitor, view, EventLoopWindowTarget, MonitorHandle,
|
||||||
},
|
},
|
||||||
window::{CursorIcon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct Inner {
|
pub struct Inner {
|
||||||
|
@ -157,10 +157,11 @@ impl Inner {
|
||||||
warn!("`Window::set_maximized` is ignored on iOS")
|
warn!("`Window::set_maximized` is ignored on iOS")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||||
unsafe {
|
unsafe {
|
||||||
match monitor {
|
match monitor {
|
||||||
Some(monitor) => {
|
Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO
|
||||||
|
Some(Fullscreen::Borderless(monitor)) => {
|
||||||
let uiscreen = monitor.ui_screen() as id;
|
let uiscreen = monitor.ui_screen() as id;
|
||||||
let current: id = msg_send![self.window, screen];
|
let current: id = msg_send![self.window, screen];
|
||||||
let bounds: CGRect = msg_send![uiscreen, bounds];
|
let bounds: CGRect = msg_send![uiscreen, bounds];
|
||||||
|
@ -176,7 +177,7 @@ impl Inner {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let monitor = self.current_monitor();
|
let monitor = self.current_monitor();
|
||||||
let uiscreen = monitor.inner.ui_screen();
|
let uiscreen = monitor.inner.ui_screen();
|
||||||
|
@ -189,7 +190,7 @@ impl Inner {
|
||||||
&& screen_space_bounds.size.width == screen_bounds.size.width
|
&& screen_space_bounds.size.width == screen_bounds.size.width
|
||||||
&& screen_space_bounds.size.height == screen_bounds.size.height
|
&& screen_space_bounds.size.height == screen_bounds.size.height
|
||||||
{
|
{
|
||||||
Some(monitor)
|
Some(Fullscreen::Borderless(monitor))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
@ -293,11 +294,12 @@ impl Window {
|
||||||
// TODO: transparency, visible
|
// TODO: transparency, visible
|
||||||
|
|
||||||
unsafe {
|
unsafe {
|
||||||
let screen = window_attributes
|
let screen = match window_attributes.fullscreen {
|
||||||
.fullscreen
|
Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO: do we set the frame to video mode bounds instead of screen bounds?
|
||||||
.as_ref()
|
Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id,
|
||||||
.map(|screen| screen.ui_screen() as _)
|
None => monitor::main_uiscreen().ui_screen(),
|
||||||
.unwrap_or_else(|| monitor::main_uiscreen().ui_screen());
|
};
|
||||||
|
|
||||||
let screen_bounds: CGRect = msg_send![screen, bounds];
|
let screen_bounds: CGRect = msg_send![screen, bounds];
|
||||||
|
|
||||||
let frame = match window_attributes.inner_size {
|
let frame = match window_attributes.inner_size {
|
||||||
|
|
|
@ -13,8 +13,8 @@ use crate::{
|
||||||
event::Event,
|
event::Event,
|
||||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||||
icon::Icon,
|
icon::Icon,
|
||||||
monitor::{MonitorHandle as RootMonitorHandle, VideoMode},
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
window::{CursorIcon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod dlopen;
|
mod dlopen;
|
||||||
|
@ -92,7 +92,7 @@ impl DeviceId {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
||||||
pub enum MonitorHandle {
|
pub enum MonitorHandle {
|
||||||
X(x11::MonitorHandle),
|
X(x11::MonitorHandle),
|
||||||
Wayland(wayland::MonitorHandle),
|
Wayland(wayland::MonitorHandle),
|
||||||
|
@ -140,7 +140,7 @@ impl MonitorHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn video_modes(&self) -> Box<dyn Iterator<Item = VideoMode>> {
|
pub fn video_modes(&self) -> Box<dyn Iterator<Item = RootVideoMode>> {
|
||||||
match self {
|
match self {
|
||||||
MonitorHandle::X(m) => Box::new(m.video_modes()),
|
MonitorHandle::X(m) => Box::new(m.video_modes()),
|
||||||
MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
|
MonitorHandle::Wayland(m) => Box::new(m.video_modes()),
|
||||||
|
@ -148,6 +148,46 @@ impl MonitorHandle {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub enum VideoMode {
|
||||||
|
X(x11::VideoMode),
|
||||||
|
Wayland(wayland::VideoMode),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoMode {
|
||||||
|
#[inline]
|
||||||
|
pub fn size(&self) -> PhysicalSize {
|
||||||
|
match self {
|
||||||
|
&VideoMode::X(ref m) => m.size(),
|
||||||
|
&VideoMode::Wayland(ref m) => m.size(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn bit_depth(&self) -> u16 {
|
||||||
|
match self {
|
||||||
|
&VideoMode::X(ref m) => m.bit_depth(),
|
||||||
|
&VideoMode::Wayland(ref m) => m.bit_depth(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn refresh_rate(&self) -> u16 {
|
||||||
|
match self {
|
||||||
|
&VideoMode::X(ref m) => m.refresh_rate(),
|
||||||
|
&VideoMode::Wayland(ref m) => m.refresh_rate(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn monitor(&self) -> RootMonitorHandle {
|
||||||
|
match self {
|
||||||
|
&VideoMode::X(ref m) => m.monitor(),
|
||||||
|
&VideoMode::Wayland(ref m) => m.monitor(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Window {
|
impl Window {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn new<T>(
|
pub fn new<T>(
|
||||||
|
@ -310,17 +350,15 @@ impl Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
match self {
|
match self {
|
||||||
&Window::X(ref w) => w.fullscreen(),
|
&Window::X(ref w) => w.fullscreen(),
|
||||||
&Window::Wayland(ref w) => w.fullscreen().map(|monitor_id| RootMonitorHandle {
|
&Window::Wayland(ref w) => w.fullscreen(),
|
||||||
inner: MonitorHandle::Wayland(monitor_id),
|
|
||||||
}),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||||
match self {
|
match self {
|
||||||
&Window::X(ref w) => w.set_fullscreen(monitor),
|
&Window::X(ref w) => w.set_fullscreen(monitor),
|
||||||
&Window::Wayland(ref w) => w.set_fullscreen(monitor),
|
&Window::Wayland(ref w) => w.set_fullscreen(monitor),
|
||||||
|
|
|
@ -16,8 +16,11 @@ use crate::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
event::ModifiersState,
|
event::ModifiersState,
|
||||||
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
||||||
monitor::VideoMode,
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
platform_impl::platform::sticky_exit_callback,
|
platform_impl::platform::{
|
||||||
|
sticky_exit_callback, MonitorHandle as PlatformMonitorHandle,
|
||||||
|
VideoMode as PlatformVideoMode,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{window::WindowStore, DeviceId, WindowId};
|
use super::{window::WindowStore, DeviceId, WindowId};
|
||||||
|
@ -603,17 +606,67 @@ impl<T> Drop for SeatData<T> {
|
||||||
* Monitor stuff
|
* Monitor stuff
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct VideoMode {
|
||||||
|
pub(crate) size: (u32, u32),
|
||||||
|
pub(crate) bit_depth: u16,
|
||||||
|
pub(crate) refresh_rate: u16,
|
||||||
|
pub(crate) monitor: MonitorHandle,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoMode {
|
||||||
|
#[inline]
|
||||||
|
pub fn size(&self) -> PhysicalSize {
|
||||||
|
self.size.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn bit_depth(&self) -> u16 {
|
||||||
|
self.bit_depth
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn refresh_rate(&self) -> u16 {
|
||||||
|
self.refresh_rate
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn monitor(&self) -> RootMonitorHandle {
|
||||||
|
RootMonitorHandle {
|
||||||
|
inner: PlatformMonitorHandle::Wayland(self.monitor.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct MonitorHandle {
|
pub struct MonitorHandle {
|
||||||
pub(crate) proxy: wl_output::WlOutput,
|
pub(crate) proxy: wl_output::WlOutput,
|
||||||
pub(crate) mgr: OutputMgr,
|
pub(crate) mgr: OutputMgr,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Clone for MonitorHandle {
|
impl PartialEq for MonitorHandle {
|
||||||
fn clone(&self) -> MonitorHandle {
|
fn eq(&self, other: &Self) -> bool {
|
||||||
MonitorHandle {
|
self.native_identifier() == other.native_identifier()
|
||||||
proxy: self.proxy.clone(),
|
}
|
||||||
mgr: self.mgr.clone(),
|
}
|
||||||
}
|
|
||||||
|
impl Eq for MonitorHandle {}
|
||||||
|
|
||||||
|
impl PartialOrd for MonitorHandle {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(&other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for MonitorHandle {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.native_identifier().cmp(&other.native_identifier())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for MonitorHandle {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.native_identifier().hash(state);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -680,15 +733,20 @@ impl MonitorHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||||
|
let monitor = self.clone();
|
||||||
|
|
||||||
self.mgr
|
self.mgr
|
||||||
.with_info(&self.proxy, |_, info| info.modes.clone())
|
.with_info(&self.proxy, |_, info| info.modes.clone())
|
||||||
.unwrap_or(vec![])
|
.unwrap_or(vec![])
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|x| VideoMode {
|
.map(move |x| RootVideoMode {
|
||||||
size: (x.dimensions.0 as u32, x.dimensions.1 as u32),
|
video_mode: PlatformVideoMode::Wayland(VideoMode {
|
||||||
refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16,
|
size: (x.dimensions.0 as u32, x.dimensions.1 as u32),
|
||||||
bit_depth: 32,
|
refresh_rate: (x.refresh_rate as f32 / 1000.0).round() as u16,
|
||||||
|
bit_depth: 32,
|
||||||
|
monitor: monitor.clone(),
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,7 +3,8 @@
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
event_loop::{
|
event_loop::{
|
||||||
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, WindowEventsSink,
|
EventLoop, EventLoopProxy, EventLoopWindowTarget, MonitorHandle, VideoMode,
|
||||||
|
WindowEventsSink,
|
||||||
},
|
},
|
||||||
window::Window,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
|
@ -8,10 +8,11 @@ use crate::{
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||||
monitor::MonitorHandle as RootMonitorHandle,
|
monitor::MonitorHandle as RootMonitorHandle,
|
||||||
platform_impl::{
|
platform_impl::{
|
||||||
|
platform::wayland::event_loop::{available_monitors, primary_monitor},
|
||||||
MonitorHandle as PlatformMonitorHandle,
|
MonitorHandle as PlatformMonitorHandle,
|
||||||
PlatformSpecificWindowBuilderAttributes as PlAttributes,
|
PlatformSpecificWindowBuilderAttributes as PlAttributes,
|
||||||
},
|
},
|
||||||
window::{CursorIcon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||||
};
|
};
|
||||||
|
|
||||||
use smithay_client_toolkit::{
|
use smithay_client_toolkit::{
|
||||||
|
@ -25,7 +26,6 @@ use smithay_client_toolkit::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
|
use super::{make_wid, EventLoopWindowTarget, MonitorHandle, WindowId};
|
||||||
use crate::platform_impl::platform::wayland::event_loop::{available_monitors, primary_monitor};
|
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
surface: wl_surface::WlSurface,
|
surface: wl_surface::WlSurface,
|
||||||
|
@ -108,13 +108,19 @@ impl Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for fullscreen requirements
|
// Check for fullscreen requirements
|
||||||
if let Some(RootMonitorHandle {
|
match attributes.fullscreen {
|
||||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
Some(Fullscreen::Exclusive(_)) => {
|
||||||
}) = attributes.fullscreen
|
panic!("Wayland doesn't support exclusive fullscreen")
|
||||||
{
|
}
|
||||||
frame.set_fullscreen(Some(&monitor_id.proxy));
|
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||||
} else if attributes.maximized {
|
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||||
frame.set_maximized();
|
})) => frame.set_fullscreen(Some(&monitor_id.proxy)),
|
||||||
|
Some(Fullscreen::Borderless(_)) => unreachable!(),
|
||||||
|
None => {
|
||||||
|
if attributes.maximized {
|
||||||
|
frame.set_maximized();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frame.set_resizable(attributes.resizable);
|
frame.set_resizable(attributes.resizable);
|
||||||
|
@ -252,25 +258,31 @@ impl Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fullscreen(&self) -> Option<MonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
if *(self.fullscreen.lock().unwrap()) {
|
if *(self.fullscreen.lock().unwrap()) {
|
||||||
Some(self.current_monitor())
|
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||||
|
inner: PlatformMonitorHandle::Wayland(self.current_monitor()),
|
||||||
|
}))
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||||
if let Some(RootMonitorHandle {
|
match fullscreen {
|
||||||
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
Some(Fullscreen::Exclusive(_)) => {
|
||||||
}) = monitor
|
panic!("Wayland doesn't support exclusive fullscreen")
|
||||||
{
|
}
|
||||||
self.frame
|
Some(Fullscreen::Borderless(RootMonitorHandle {
|
||||||
.lock()
|
inner: PlatformMonitorHandle::Wayland(ref monitor_id),
|
||||||
.unwrap()
|
})) => {
|
||||||
.set_fullscreen(Some(&monitor_id.proxy));
|
self.frame
|
||||||
} else {
|
.lock()
|
||||||
self.frame.lock().unwrap().unset_fullscreen();
|
.unwrap()
|
||||||
|
.set_fullscreen(Some(&monitor_id.proxy));
|
||||||
|
}
|
||||||
|
Some(Fullscreen::Borderless(_)) => unreachable!(),
|
||||||
|
None => self.frame.lock().unwrap().unset_fullscreen(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@ mod window;
|
||||||
mod xdisplay;
|
mod xdisplay;
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
monitor::MonitorHandle,
|
monitor::{MonitorHandle, VideoMode},
|
||||||
window::UnownedWindow,
|
window::UnownedWindow,
|
||||||
xdisplay::{XConnection, XError, XNotSupported},
|
xdisplay::{XConnection, XError, XNotSupported},
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,47 +4,66 @@ use parking_lot::Mutex;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ffi::{
|
ffi::{
|
||||||
RRCrtcChangeNotifyMask, RROutputPropertyNotifyMask, RRScreenChangeNotifyMask, True, Window,
|
RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask,
|
||||||
XRRScreenResources,
|
RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources,
|
||||||
},
|
},
|
||||||
util, XConnection, XError,
|
util, XConnection, XError,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
monitor::VideoMode,
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
|
platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Used to test XRandR < 1.5 code path. This should always be committed as false.
|
// Used for testing. This should always be committed as false.
|
||||||
const FORCE_RANDR_COMPAT: bool = false;
|
|
||||||
// Also used for testing. This should always be committed as false.
|
|
||||||
const DISABLE_MONITOR_LIST_CACHING: bool = false;
|
const DISABLE_MONITOR_LIST_CACHING: bool = false;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref XRANDR_VERSION: Mutex<Option<(c_int, c_int)>> = Mutex::default();
|
|
||||||
static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default();
|
static ref MONITORS: Mutex<Option<Vec<MonitorHandle>>> = Mutex::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn version_is_at_least(major: c_int, minor: c_int) -> bool {
|
|
||||||
if let Some((avail_major, avail_minor)) = *XRANDR_VERSION.lock() {
|
|
||||||
if avail_major == major {
|
|
||||||
avail_minor >= minor
|
|
||||||
} else {
|
|
||||||
avail_major > major
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
unreachable!();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
|
pub fn invalidate_cached_monitor_list() -> Option<Vec<MonitorHandle>> {
|
||||||
// We update this lazily.
|
// We update this lazily.
|
||||||
(*MONITORS.lock()).take()
|
(*MONITORS.lock()).take()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
pub struct VideoMode {
|
||||||
|
pub(crate) size: (u32, u32),
|
||||||
|
pub(crate) bit_depth: u16,
|
||||||
|
pub(crate) refresh_rate: u16,
|
||||||
|
pub(crate) native_mode: RRMode,
|
||||||
|
pub(crate) monitor: Option<MonitorHandle>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoMode {
|
||||||
|
#[inline]
|
||||||
|
pub fn size(&self) -> PhysicalSize {
|
||||||
|
self.size.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn bit_depth(&self) -> u16 {
|
||||||
|
self.bit_depth
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn refresh_rate(&self) -> u16 {
|
||||||
|
self.refresh_rate
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn monitor(&self) -> RootMonitorHandle {
|
||||||
|
RootMonitorHandle {
|
||||||
|
inner: PlatformMonitorHandle::X(self.monitor.clone().unwrap()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct MonitorHandle {
|
pub struct MonitorHandle {
|
||||||
/// The actual id
|
/// The actual id
|
||||||
id: u32,
|
pub(crate) id: RRCrtc,
|
||||||
/// The name of the monitor
|
/// The name of the monitor
|
||||||
pub(crate) name: String,
|
pub(crate) name: String,
|
||||||
/// The size of the monitor
|
/// The size of the monitor
|
||||||
|
@ -61,16 +80,43 @@ pub struct MonitorHandle {
|
||||||
video_modes: Vec<VideoMode>,
|
video_modes: Vec<VideoMode>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PartialEq for MonitorHandle {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
self.id == other.id
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for MonitorHandle {}
|
||||||
|
|
||||||
|
impl PartialOrd for MonitorHandle {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(&other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for MonitorHandle {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
self.id.cmp(&other.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for MonitorHandle {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
self.id.hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl MonitorHandle {
|
impl MonitorHandle {
|
||||||
fn from_repr(
|
fn new(
|
||||||
xconn: &XConnection,
|
xconn: &XConnection,
|
||||||
resources: *mut XRRScreenResources,
|
resources: *mut XRRScreenResources,
|
||||||
id: u32,
|
id: RRCrtc,
|
||||||
repr: util::MonitorRepr,
|
crtc: *mut XRRCrtcInfo,
|
||||||
primary: bool,
|
primary: bool,
|
||||||
) -> Option<Self> {
|
) -> Option<Self> {
|
||||||
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, &repr)? };
|
let (name, hidpi_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? };
|
||||||
let (dimensions, position) = unsafe { (repr.size(), repr.position()) };
|
let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) };
|
||||||
|
let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) };
|
||||||
let rect = util::AaRect::new(position, dimensions);
|
let rect = util::AaRect::new(position, dimensions);
|
||||||
Some(MonitorHandle {
|
Some(MonitorHandle {
|
||||||
id,
|
id,
|
||||||
|
@ -107,8 +153,14 @@ impl MonitorHandle {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||||
self.video_modes.clone().into_iter()
|
let monitor = self.clone();
|
||||||
|
self.video_modes.clone().into_iter().map(move |mut x| {
|
||||||
|
x.monitor = Some(monitor.clone());
|
||||||
|
RootVideoMode {
|
||||||
|
video_mode: PlatformVideoMode::X(x),
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,8 +191,12 @@ impl XConnection {
|
||||||
|
|
||||||
fn query_monitor_list(&self) -> Vec<MonitorHandle> {
|
fn query_monitor_list(&self) -> Vec<MonitorHandle> {
|
||||||
unsafe {
|
unsafe {
|
||||||
|
let mut major = 0;
|
||||||
|
let mut minor = 0;
|
||||||
|
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
|
||||||
|
|
||||||
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
||||||
let resources = if version_is_at_least(1, 3) {
|
let resources = if (major == 1 && minor >= 3) || major > 1 {
|
||||||
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
||||||
} else {
|
} else {
|
||||||
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
|
// WARNING: this function is supposedly very slow, on the order of hundreds of ms.
|
||||||
|
@ -155,48 +211,19 @@ impl XConnection {
|
||||||
let mut available;
|
let mut available;
|
||||||
let mut has_primary = false;
|
let mut has_primary = false;
|
||||||
|
|
||||||
if self.xrandr_1_5.is_some() && version_is_at_least(1, 5) && !FORCE_RANDR_COMPAT {
|
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
|
||||||
// We're in XRandR >= 1.5, enumerate monitors. This supports things like MST and
|
available = Vec::with_capacity((*resources).ncrtc as usize);
|
||||||
// videowalls.
|
for crtc_index in 0..(*resources).ncrtc {
|
||||||
let xrandr_1_5 = self.xrandr_1_5.as_ref().unwrap();
|
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
|
||||||
let mut monitor_count = 0;
|
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
||||||
let monitors =
|
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
|
||||||
(xrandr_1_5.XRRGetMonitors)(self.display, root, 1, &mut monitor_count);
|
if is_active {
|
||||||
assert!(monitor_count >= 0);
|
let is_primary = *(*crtc).outputs.offset(0) == primary;
|
||||||
available = Vec::with_capacity(monitor_count as usize);
|
|
||||||
for monitor_index in 0..monitor_count {
|
|
||||||
let monitor = monitors.offset(monitor_index as isize);
|
|
||||||
let is_primary = (*monitor).primary != 0;
|
|
||||||
has_primary |= is_primary;
|
has_primary |= is_primary;
|
||||||
MonitorHandle::from_repr(
|
MonitorHandle::new(self, resources, crtc_id, crtc, is_primary)
|
||||||
self,
|
.map(|monitor_id| available.push(monitor_id));
|
||||||
resources,
|
|
||||||
monitor_index as u32,
|
|
||||||
monitor.into(),
|
|
||||||
is_primary,
|
|
||||||
)
|
|
||||||
.map(|monitor_id| available.push(monitor_id));
|
|
||||||
}
|
|
||||||
(xrandr_1_5.XRRFreeMonitors)(monitors);
|
|
||||||
} else {
|
|
||||||
// We're in XRandR < 1.5, enumerate CRTCs. Everything will work except MST and
|
|
||||||
// videowall setups will also show monitors that aren't in the logical groups the user
|
|
||||||
// cares about.
|
|
||||||
let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root);
|
|
||||||
available = Vec::with_capacity((*resources).ncrtc as usize);
|
|
||||||
for crtc_index in 0..(*resources).ncrtc {
|
|
||||||
let crtc_id = *((*resources).crtcs.offset(crtc_index as isize));
|
|
||||||
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
|
||||||
let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0;
|
|
||||||
if is_active {
|
|
||||||
let crtc = util::MonitorRepr::from(crtc);
|
|
||||||
let is_primary = crtc.get_output() == primary;
|
|
||||||
has_primary |= is_primary;
|
|
||||||
MonitorHandle::from_repr(self, resources, crtc_id as u32, crtc, is_primary)
|
|
||||||
.map(|monitor_id| available.push(monitor_id));
|
|
||||||
}
|
|
||||||
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
|
||||||
}
|
}
|
||||||
|
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
||||||
}
|
}
|
||||||
|
|
||||||
// If no monitors were detected as being primary, we just pick one ourselves!
|
// If no monitors were detected as being primary, we just pick one ourselves!
|
||||||
|
@ -236,19 +263,15 @@ impl XConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
|
pub fn select_xrandr_input(&self, root: Window) -> Result<c_int, XError> {
|
||||||
{
|
let has_xrandr = unsafe {
|
||||||
let mut version_lock = XRANDR_VERSION.lock();
|
let mut major = 0;
|
||||||
if version_lock.is_none() {
|
let mut minor = 0;
|
||||||
let mut major = 0;
|
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor)
|
||||||
let mut minor = 0;
|
};
|
||||||
let has_extension =
|
assert!(
|
||||||
unsafe { (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) };
|
has_xrandr == True,
|
||||||
if has_extension != True {
|
"[winit] XRandR extension not available."
|
||||||
panic!("[winit] XRandR extension not available.");
|
);
|
||||||
}
|
|
||||||
*version_lock = Some((major, minor));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut event_offset = 0;
|
let mut event_offset = 0;
|
||||||
let mut error_offset = 0;
|
let mut error_offset = 0;
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
use std::{env, slice, str::FromStr};
|
use std::{env, slice, str::FromStr};
|
||||||
|
|
||||||
use super::*;
|
use super::{
|
||||||
use crate::{dpi::validate_hidpi_factor, monitor::VideoMode};
|
ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources},
|
||||||
|
*,
|
||||||
|
};
|
||||||
|
use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode};
|
||||||
|
|
||||||
pub fn calc_dpi_factor(
|
pub fn calc_dpi_factor(
|
||||||
(width_px, height_px): (u32, u32),
|
(width_px, height_px): (u32, u32),
|
||||||
|
@ -34,47 +37,6 @@ pub fn calc_dpi_factor(
|
||||||
dpi_factor
|
dpi_factor
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum MonitorRepr {
|
|
||||||
Monitor(*mut ffi::XRRMonitorInfo),
|
|
||||||
Crtc(*mut ffi::XRRCrtcInfo),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MonitorRepr {
|
|
||||||
pub unsafe fn get_output(&self) -> ffi::RROutput {
|
|
||||||
match *self {
|
|
||||||
// Same member names, but different locations within the struct...
|
|
||||||
MonitorRepr::Monitor(monitor) => *((*monitor).outputs.offset(0)),
|
|
||||||
MonitorRepr::Crtc(crtc) => *((*crtc).outputs.offset(0)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn size(&self) -> (u32, u32) {
|
|
||||||
match *self {
|
|
||||||
MonitorRepr::Monitor(monitor) => ((*monitor).width as u32, (*monitor).height as u32),
|
|
||||||
MonitorRepr::Crtc(crtc) => ((*crtc).width as u32, (*crtc).height as u32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub unsafe fn position(&self) -> (i32, i32) {
|
|
||||||
match *self {
|
|
||||||
MonitorRepr::Monitor(monitor) => ((*monitor).x as i32, (*monitor).y as i32),
|
|
||||||
MonitorRepr::Crtc(crtc) => ((*crtc).x as i32, (*crtc).y as i32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<*mut ffi::XRRMonitorInfo> for MonitorRepr {
|
|
||||||
fn from(monitor: *mut ffi::XRRMonitorInfo) -> Self {
|
|
||||||
MonitorRepr::Monitor(monitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<*mut ffi::XRRCrtcInfo> for MonitorRepr {
|
|
||||||
fn from(crtc: *mut ffi::XRRCrtcInfo) -> Self {
|
|
||||||
MonitorRepr::Crtc(crtc)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl XConnection {
|
impl XConnection {
|
||||||
// Retrieve DPI from Xft.dpi property
|
// Retrieve DPI from Xft.dpi property
|
||||||
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
|
pub unsafe fn get_xft_dpi(&self) -> Option<f64> {
|
||||||
|
@ -96,11 +58,11 @@ impl XConnection {
|
||||||
}
|
}
|
||||||
pub unsafe fn get_output_info(
|
pub unsafe fn get_output_info(
|
||||||
&self,
|
&self,
|
||||||
resources: *mut ffi::XRRScreenResources,
|
resources: *mut XRRScreenResources,
|
||||||
repr: &MonitorRepr,
|
crtc: *mut XRRCrtcInfo,
|
||||||
) -> Option<(String, f64, Vec<VideoMode>)> {
|
) -> Option<(String, f64, Vec<VideoMode>)> {
|
||||||
let output_info =
|
let output_info =
|
||||||
(self.xrandr.XRRGetOutputInfo)(self.display, resources, repr.get_output());
|
(self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0));
|
||||||
if output_info.is_null() {
|
if output_info.is_null() {
|
||||||
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
|
// When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display)
|
||||||
// it's possible for it to return null.
|
// it's possible for it to return null.
|
||||||
|
@ -132,6 +94,10 @@ impl XConnection {
|
||||||
size: (x.width, x.height),
|
size: (x.width, x.height),
|
||||||
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
|
refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16,
|
||||||
bit_depth: bit_depth as u16,
|
bit_depth: bit_depth as u16,
|
||||||
|
native_mode: x.id,
|
||||||
|
// This is populated in `MonitorHandle::video_modes` as the
|
||||||
|
// video mode is returned to the user
|
||||||
|
monitor: None,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -144,7 +110,7 @@ impl XConnection {
|
||||||
dpi / 96.
|
dpi / 96.
|
||||||
} else {
|
} else {
|
||||||
calc_dpi_factor(
|
calc_dpi_factor(
|
||||||
repr.size(),
|
((*crtc).width as u32, (*crtc).height as u32),
|
||||||
(
|
(
|
||||||
(*output_info).mm_width as u64,
|
(*output_info).mm_width as u64,
|
||||||
(*output_info).mm_height as u64,
|
(*output_info).mm_height as u64,
|
||||||
|
@ -155,4 +121,61 @@ impl XConnection {
|
||||||
(self.xrandr.XRRFreeOutputInfo)(output_info);
|
(self.xrandr.XRRFreeOutputInfo)(output_info);
|
||||||
Some((name, hidpi_factor, modes.collect()))
|
Some((name, hidpi_factor, modes.collect()))
|
||||||
}
|
}
|
||||||
|
pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> {
|
||||||
|
unsafe {
|
||||||
|
let mut major = 0;
|
||||||
|
let mut minor = 0;
|
||||||
|
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
|
||||||
|
|
||||||
|
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
||||||
|
let resources = if (major == 1 && minor >= 3) || major > 1 {
|
||||||
|
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
||||||
|
} else {
|
||||||
|
(self.xrandr.XRRGetScreenResources)(self.display, root)
|
||||||
|
};
|
||||||
|
|
||||||
|
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
||||||
|
let status = (self.xrandr.XRRSetCrtcConfig)(
|
||||||
|
self.display,
|
||||||
|
resources,
|
||||||
|
crtc_id,
|
||||||
|
CurrentTime,
|
||||||
|
(*crtc).x,
|
||||||
|
(*crtc).y,
|
||||||
|
mode_id,
|
||||||
|
(*crtc).rotation,
|
||||||
|
(*crtc).outputs.offset(0),
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
||||||
|
(self.xrandr.XRRFreeScreenResources)(resources);
|
||||||
|
|
||||||
|
if status == Success as i32 {
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode {
|
||||||
|
unsafe {
|
||||||
|
let mut major = 0;
|
||||||
|
let mut minor = 0;
|
||||||
|
(self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor);
|
||||||
|
|
||||||
|
let root = (self.xlib.XDefaultRootWindow)(self.display);
|
||||||
|
let resources = if (major == 1 && minor >= 3) || major > 1 {
|
||||||
|
(self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root)
|
||||||
|
} else {
|
||||||
|
(self.xrandr.XRRGetScreenResources)(self.display, root)
|
||||||
|
};
|
||||||
|
|
||||||
|
let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id);
|
||||||
|
let mode = (*crtc).mode;
|
||||||
|
(self.xrandr.XRRFreeCrtcInfo)(crtc);
|
||||||
|
(self.xrandr.XRRFreeScreenResources)(resources);
|
||||||
|
mode
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,13 @@ use parking_lot::Mutex;
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
dpi::{LogicalPosition, LogicalSize},
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||||
monitor::MonitorHandle as RootMonitorHandle,
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
platform_impl::{
|
platform_impl::{
|
||||||
x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle},
|
x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle},
|
||||||
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
|
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
|
||||||
|
VideoMode as PlatformVideoMode,
|
||||||
},
|
},
|
||||||
window::{CursorIcon, Icon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError};
|
use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError};
|
||||||
|
@ -46,9 +47,11 @@ pub struct SharedState {
|
||||||
pub guessed_dpi: Option<f64>,
|
pub guessed_dpi: Option<f64>,
|
||||||
pub last_monitor: Option<X11MonitorHandle>,
|
pub last_monitor: Option<X11MonitorHandle>,
|
||||||
pub dpi_adjusted: Option<(f64, f64)>,
|
pub dpi_adjusted: Option<(f64, f64)>,
|
||||||
pub fullscreen: Option<RootMonitorHandle>,
|
pub fullscreen: Option<Fullscreen>,
|
||||||
// Used to restore position after exiting fullscreen.
|
// Used to restore position after exiting fullscreen
|
||||||
pub restore_position: Option<(i32, i32)>,
|
pub restore_position: Option<(i32, i32)>,
|
||||||
|
// Used to restore video mode after exiting fullscreen
|
||||||
|
pub desktop_video_mode: Option<(ffi::RRCrtc, ffi::RRMode)>,
|
||||||
pub frame_extents: Option<util::FrameExtentsHeuristic>,
|
pub frame_extents: Option<util::FrameExtentsHeuristic>,
|
||||||
pub min_inner_size: Option<LogicalSize>,
|
pub min_inner_size: Option<LogicalSize>,
|
||||||
pub max_inner_size: Option<LogicalSize>,
|
pub max_inner_size: Option<LogicalSize>,
|
||||||
|
@ -408,6 +411,7 @@ impl UnownedWindow {
|
||||||
if window_attrs.fullscreen.is_some() {
|
if window_attrs.fullscreen.is_some() {
|
||||||
window
|
window
|
||||||
.set_fullscreen_inner(window_attrs.fullscreen.clone())
|
.set_fullscreen_inner(window_attrs.fullscreen.clone())
|
||||||
|
.unwrap()
|
||||||
.queue();
|
.queue();
|
||||||
}
|
}
|
||||||
if window_attrs.always_on_top {
|
if window_attrs.always_on_top {
|
||||||
|
@ -564,41 +568,122 @@ impl UnownedWindow {
|
||||||
self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0))
|
self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_fullscreen_inner(&self, monitor: Option<RootMonitorHandle>) -> util::Flusher<'_> {
|
fn set_fullscreen_inner(&self, fullscreen: Option<Fullscreen>) -> Option<util::Flusher<'_>> {
|
||||||
match monitor {
|
let mut shared_state_lock = self.shared_state.lock();
|
||||||
|
let old_fullscreen = shared_state_lock.fullscreen.clone();
|
||||||
|
if old_fullscreen == fullscreen {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
shared_state_lock.fullscreen = fullscreen.clone();
|
||||||
|
|
||||||
|
match (&old_fullscreen, &fullscreen) {
|
||||||
|
// Store the desktop video mode before entering exclusive
|
||||||
|
// fullscreen, so we can restore it upon exit, as XRandR does not
|
||||||
|
// provide a mechanism to set this per app-session or restore this
|
||||||
|
// to the desktop video mode as macOS and Windows do
|
||||||
|
(
|
||||||
|
&None,
|
||||||
|
&Some(Fullscreen::Exclusive(RootVideoMode {
|
||||||
|
video_mode: PlatformVideoMode::X(ref video_mode),
|
||||||
|
})),
|
||||||
|
)
|
||||||
|
| (
|
||||||
|
&Some(Fullscreen::Borderless(_)),
|
||||||
|
&Some(Fullscreen::Exclusive(RootVideoMode {
|
||||||
|
video_mode: PlatformVideoMode::X(ref video_mode),
|
||||||
|
})),
|
||||||
|
) => {
|
||||||
|
let monitor = video_mode.monitor.as_ref().unwrap();
|
||||||
|
shared_state_lock.desktop_video_mode =
|
||||||
|
Some((monitor.id, self.xconn.get_crtc_mode(monitor.id)));
|
||||||
|
}
|
||||||
|
// Restore desktop video mode upon exiting exclusive fullscreen
|
||||||
|
(&Some(Fullscreen::Exclusive(_)), &None)
|
||||||
|
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
|
||||||
|
let (monitor_id, mode_id) = shared_state_lock.desktop_video_mode.take().unwrap();
|
||||||
|
self.xconn
|
||||||
|
.set_crtc_config(monitor_id, mode_id)
|
||||||
|
.expect("failed to restore desktop video mode");
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
drop(shared_state_lock);
|
||||||
|
|
||||||
|
match fullscreen {
|
||||||
None => {
|
None => {
|
||||||
let flusher = self.set_fullscreen_hint(false);
|
let flusher = self.set_fullscreen_hint(false);
|
||||||
if let Some(position) = self.shared_state.lock().restore_position.take() {
|
let mut shared_state_lock = self.shared_state.lock();
|
||||||
|
if let Some(position) = shared_state_lock.restore_position.take() {
|
||||||
self.set_position_inner(position.0, position.1).queue();
|
self.set_position_inner(position.0, position.1).queue();
|
||||||
}
|
}
|
||||||
flusher
|
Some(flusher)
|
||||||
}
|
}
|
||||||
Some(RootMonitorHandle {
|
Some(fullscreen) => {
|
||||||
inner: PlatformMonitorHandle::X(monitor),
|
let (video_mode, monitor) = match fullscreen {
|
||||||
}) => {
|
Fullscreen::Exclusive(RootVideoMode {
|
||||||
|
video_mode: PlatformVideoMode::X(ref video_mode),
|
||||||
|
}) => (Some(video_mode), video_mode.monitor.as_ref().unwrap()),
|
||||||
|
Fullscreen::Borderless(RootMonitorHandle {
|
||||||
|
inner: PlatformMonitorHandle::X(ref monitor),
|
||||||
|
}) => (None, monitor),
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(video_mode) = video_mode {
|
||||||
|
// FIXME: this is actually not correct if we're setting the
|
||||||
|
// video mode to a resolution higher than the current
|
||||||
|
// desktop resolution, because XRandR does not automatically
|
||||||
|
// reposition the monitors to the right and below this
|
||||||
|
// monitor.
|
||||||
|
//
|
||||||
|
// What ends up happening is we will get the fullscreen
|
||||||
|
// window showing up on those monitors as well, because
|
||||||
|
// their virtual position now overlaps with the monitor that
|
||||||
|
// we just made larger..
|
||||||
|
//
|
||||||
|
// It'd be quite a bit of work to handle this correctly (and
|
||||||
|
// nobody else seems to bother doing this correctly either),
|
||||||
|
// so we're just leaving this broken. Fixing this would
|
||||||
|
// involve storing all CRTCs upon entering fullscreen,
|
||||||
|
// restoring them upon exit, and after entering fullscreen,
|
||||||
|
// repositioning displays to the right and below this
|
||||||
|
// display. I think there would still be edge cases that are
|
||||||
|
// difficult or impossible to handle correctly, e.g. what if
|
||||||
|
// a new monitor was plugged in while in fullscreen?
|
||||||
|
//
|
||||||
|
// I think we might just want to disallow setting the video
|
||||||
|
// mode higher than the current desktop video mode (I'm sure
|
||||||
|
// this will make someone unhappy, but it's very unusual for
|
||||||
|
// games to want to do this anyway).
|
||||||
|
self.xconn
|
||||||
|
.set_crtc_config(monitor.id, video_mode.native_mode)
|
||||||
|
.expect("failed to set video mode");
|
||||||
|
}
|
||||||
|
|
||||||
let window_position = self.outer_position_physical();
|
let window_position = self.outer_position_physical();
|
||||||
self.shared_state.lock().restore_position = Some(window_position);
|
self.shared_state.lock().restore_position = Some(window_position);
|
||||||
let monitor_origin: (i32, i32) = monitor.position().into();
|
let monitor_origin: (i32, i32) = monitor.position().into();
|
||||||
self.set_position_inner(monitor_origin.0, monitor_origin.1)
|
self.set_position_inner(monitor_origin.0, monitor_origin.1)
|
||||||
.queue();
|
.queue();
|
||||||
self.set_fullscreen_hint(true)
|
Some(self.set_fullscreen_hint(true))
|
||||||
}
|
}
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
self.shared_state.lock().fullscreen.clone()
|
self.shared_state.lock().fullscreen.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||||
self.shared_state.lock().fullscreen = monitor.clone();
|
if let Some(flusher) = self.set_fullscreen_inner(fullscreen) {
|
||||||
self.set_fullscreen_inner(monitor)
|
flusher
|
||||||
.flush()
|
.flush()
|
||||||
.expect("Failed to change window fullscreen state");
|
.expect("Failed to change window fullscreen state");
|
||||||
self.invalidate_cached_frame_extents();
|
self.invalidate_cached_frame_extents();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_rect(&self) -> util::AaRect {
|
fn get_rect(&self) -> util::AaRect {
|
||||||
|
|
|
@ -6,6 +6,13 @@ use cocoa::{
|
||||||
base::id,
|
base::id,
|
||||||
foundation::{NSInteger, NSUInteger},
|
foundation::{NSInteger, NSUInteger},
|
||||||
};
|
};
|
||||||
|
use core_foundation::{
|
||||||
|
array::CFArrayRef, dictionary::CFDictionaryRef, string::CFStringRef, uuid::CFUUIDRef,
|
||||||
|
};
|
||||||
|
use core_graphics::{
|
||||||
|
base::CGError,
|
||||||
|
display::{CGDirectDisplayID, CGDisplayConfigRef},
|
||||||
|
};
|
||||||
use objc;
|
use objc;
|
||||||
|
|
||||||
pub const NSNotFound: NSInteger = NSInteger::max_value();
|
pub const NSNotFound: NSInteger = NSInteger::max_value();
|
||||||
|
@ -108,3 +115,95 @@ pub enum NSWindowLevel {
|
||||||
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
|
NSPopUpMenuWindowLevel = kCGPopUpMenuWindowLevelKey as _,
|
||||||
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
|
NSScreenSaverWindowLevel = kCGScreenSaverWindowLevelKey as _,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type CGDisplayFadeInterval = f32;
|
||||||
|
pub type CGDisplayReservationInterval = f32;
|
||||||
|
pub type CGDisplayBlendFraction = f32;
|
||||||
|
|
||||||
|
pub const kCGDisplayBlendNormal: f32 = 0.0;
|
||||||
|
pub const kCGDisplayBlendSolidColor: f32 = 1.0;
|
||||||
|
|
||||||
|
pub type CGDisplayFadeReservationToken = u32;
|
||||||
|
pub const kCGDisplayFadeReservationInvalidToken: CGDisplayFadeReservationToken = 0;
|
||||||
|
|
||||||
|
pub type Boolean = u8;
|
||||||
|
pub const FALSE: Boolean = 0;
|
||||||
|
pub const TRUE: Boolean = 1;
|
||||||
|
|
||||||
|
pub const kCGErrorSuccess: i32 = 0;
|
||||||
|
pub const kCGErrorFailure: i32 = 1000;
|
||||||
|
pub const kCGErrorIllegalArgument: i32 = 1001;
|
||||||
|
pub const kCGErrorInvalidConnection: i32 = 1002;
|
||||||
|
pub const kCGErrorInvalidContext: i32 = 1003;
|
||||||
|
pub const kCGErrorCannotComplete: i32 = 1004;
|
||||||
|
pub const kCGErrorNotImplemented: i32 = 1006;
|
||||||
|
pub const kCGErrorRangeCheck: i32 = 1007;
|
||||||
|
pub const kCGErrorTypeCheck: i32 = 1008;
|
||||||
|
pub const kCGErrorInvalidOperation: i32 = 1010;
|
||||||
|
pub const kCGErrorNoneAvailable: i32 = 1011;
|
||||||
|
|
||||||
|
pub const IO1BitIndexedPixels: &str = "P";
|
||||||
|
pub const IO2BitIndexedPixels: &str = "PP";
|
||||||
|
pub const IO4BitIndexedPixels: &str = "PPPP";
|
||||||
|
pub const IO8BitIndexedPixels: &str = "PPPPPPPP";
|
||||||
|
pub const IO16BitDirectPixels: &str = "-RRRRRGGGGGBBBBB";
|
||||||
|
pub const IO32BitDirectPixels: &str = "--------RRRRRRRRGGGGGGGGBBBBBBBB";
|
||||||
|
|
||||||
|
pub const kIO30BitDirectPixels: &str = "--RRRRRRRRRRGGGGGGGGGGBBBBBBBBBB";
|
||||||
|
pub const kIO64BitDirectPixels: &str = "-16R16G16B16";
|
||||||
|
|
||||||
|
pub const kIO16BitFloatPixels: &str = "-16FR16FG16FB16";
|
||||||
|
pub const kIO32BitFloatPixels: &str = "-32FR32FG32FB32";
|
||||||
|
|
||||||
|
pub const IOYUV422Pixels: &str = "Y4U2V2";
|
||||||
|
pub const IO8BitOverlayPixels: &str = "O8";
|
||||||
|
|
||||||
|
pub type CGWindowLevel = i32;
|
||||||
|
pub type CGDisplayModeRef = *mut libc::c_void;
|
||||||
|
|
||||||
|
#[link(name = "CoreGraphics", kind = "framework")]
|
||||||
|
extern "C" {
|
||||||
|
pub fn CGRestorePermanentDisplayConfiguration();
|
||||||
|
pub fn CGDisplayCapture(display: CGDirectDisplayID) -> CGError;
|
||||||
|
pub fn CGDisplayRelease(display: CGDirectDisplayID) -> CGError;
|
||||||
|
pub fn CGConfigureDisplayFadeEffect(
|
||||||
|
config: CGDisplayConfigRef,
|
||||||
|
fadeOutSeconds: CGDisplayFadeInterval,
|
||||||
|
fadeInSeconds: CGDisplayFadeInterval,
|
||||||
|
fadeRed: f32,
|
||||||
|
fadeGreen: f32,
|
||||||
|
fadeBlue: f32,
|
||||||
|
) -> CGError;
|
||||||
|
pub fn CGAcquireDisplayFadeReservation(
|
||||||
|
seconds: CGDisplayReservationInterval,
|
||||||
|
token: *mut CGDisplayFadeReservationToken,
|
||||||
|
) -> CGError;
|
||||||
|
pub fn CGDisplayFade(
|
||||||
|
token: CGDisplayFadeReservationToken,
|
||||||
|
duration: CGDisplayFadeInterval,
|
||||||
|
startBlend: CGDisplayBlendFraction,
|
||||||
|
endBlend: CGDisplayBlendFraction,
|
||||||
|
redBlend: f32,
|
||||||
|
greenBlend: f32,
|
||||||
|
blueBlend: f32,
|
||||||
|
synchronous: Boolean,
|
||||||
|
) -> CGError;
|
||||||
|
pub fn CGReleaseDisplayFadeReservation(token: CGDisplayFadeReservationToken) -> CGError;
|
||||||
|
pub fn CGDisplayCreateUUIDFromDisplayID(display: CGDirectDisplayID) -> CFUUIDRef;
|
||||||
|
pub fn CGShieldingWindowLevel() -> CGWindowLevel;
|
||||||
|
pub fn CGDisplaySetDisplayMode(
|
||||||
|
display: CGDirectDisplayID,
|
||||||
|
mode: CGDisplayModeRef,
|
||||||
|
options: CFDictionaryRef,
|
||||||
|
) -> CGError;
|
||||||
|
pub fn CGDisplayCopyAllDisplayModes(
|
||||||
|
display: CGDirectDisplayID,
|
||||||
|
options: CFDictionaryRef,
|
||||||
|
) -> CFArrayRef;
|
||||||
|
pub fn CGDisplayModeGetPixelWidth(mode: CGDisplayModeRef) -> usize;
|
||||||
|
pub fn CGDisplayModeGetPixelHeight(mode: CGDisplayModeRef) -> usize;
|
||||||
|
pub fn CGDisplayModeGetRefreshRate(mode: CGDisplayModeRef) -> f64;
|
||||||
|
pub fn CGDisplayModeCopyPixelEncoding(mode: CGDisplayModeRef) -> CFStringRef;
|
||||||
|
pub fn CGDisplayModeRetain(mode: CGDisplayModeRef);
|
||||||
|
pub fn CGDisplayModeRelease(mode: CGDisplayModeRef);
|
||||||
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ use std::{fmt, ops::Deref, sync::Arc};
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
|
event_loop::{EventLoop, EventLoopWindowTarget, Proxy as EventLoopProxy},
|
||||||
monitor::MonitorHandle,
|
monitor::{MonitorHandle, VideoMode},
|
||||||
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
|
window::{Id as WindowId, PlatformSpecificWindowBuilderAttributes, UnownedWindow},
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
|
|
|
@ -1,25 +1,119 @@
|
||||||
use std::{collections::VecDeque, fmt};
|
use std::{collections::VecDeque, fmt};
|
||||||
|
|
||||||
|
use super::ffi;
|
||||||
|
use crate::{
|
||||||
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
|
platform_impl::platform::util::IdRef,
|
||||||
|
};
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::NSScreen,
|
appkit::NSScreen,
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::{NSString, NSUInteger},
|
foundation::{NSString, NSUInteger},
|
||||||
};
|
};
|
||||||
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds, CGDisplayMode};
|
use core_foundation::{
|
||||||
|
array::{CFArrayGetCount, CFArrayGetValueAtIndex},
|
||||||
|
base::{CFRelease, TCFType},
|
||||||
|
string::CFString,
|
||||||
|
};
|
||||||
|
use core_graphics::display::{CGDirectDisplayID, CGDisplay, CGDisplayBounds};
|
||||||
use core_video_sys::{
|
use core_video_sys::{
|
||||||
kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay,
|
kCVReturnSuccess, kCVTimeIsIndefinite, CVDisplayLinkCreateWithCGDisplay,
|
||||||
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease,
|
CVDisplayLinkGetNominalOutputVideoRefreshPeriod, CVDisplayLinkRelease,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
#[derive(Derivative)]
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
#[derivative(Debug, Clone, PartialEq, Hash)]
|
||||||
monitor::VideoMode,
|
pub struct VideoMode {
|
||||||
platform_impl::platform::util::IdRef,
|
pub(crate) size: (u32, u32),
|
||||||
};
|
pub(crate) bit_depth: u16,
|
||||||
|
pub(crate) refresh_rate: u16,
|
||||||
|
pub(crate) monitor: MonitorHandle,
|
||||||
|
#[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")]
|
||||||
|
pub(crate) native_mode: NativeDisplayMode,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
pub struct NativeDisplayMode(pub ffi::CGDisplayModeRef);
|
||||||
|
|
||||||
|
unsafe impl Send for NativeDisplayMode {}
|
||||||
|
|
||||||
|
impl Drop for NativeDisplayMode {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
unsafe {
|
||||||
|
ffi::CGDisplayModeRelease(self.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for NativeDisplayMode {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
unsafe {
|
||||||
|
ffi::CGDisplayModeRetain(self.0);
|
||||||
|
}
|
||||||
|
NativeDisplayMode(self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl VideoMode {
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct MonitorHandle(CGDirectDisplayID);
|
pub struct MonitorHandle(CGDirectDisplayID);
|
||||||
|
|
||||||
|
// `CGDirectDisplayID` changes on video mode change, so we cannot rely on that
|
||||||
|
// for comparisons, but we can use `CGDisplayCreateUUIDFromDisplayID` to get an
|
||||||
|
// unique identifier that persists even across system reboots
|
||||||
|
impl PartialEq for MonitorHandle {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
unsafe {
|
||||||
|
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
||||||
|
== ffi::CGDisplayCreateUUIDFromDisplayID(other.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Eq for MonitorHandle {}
|
||||||
|
|
||||||
|
impl PartialOrd for MonitorHandle {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(&other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for MonitorHandle {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
unsafe {
|
||||||
|
ffi::CGDisplayCreateUUIDFromDisplayID(self.0)
|
||||||
|
.cmp(&ffi::CGDisplayCreateUUIDFromDisplayID(other.0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::hash::Hash for MonitorHandle {
|
||||||
|
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
|
||||||
|
unsafe {
|
||||||
|
ffi::CGDisplayCreateUUIDFromDisplayID(self.0).hash(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||||
if let Ok(displays) = CGDisplay::active_displays() {
|
if let Ok(displays) = CGDisplay::active_displays() {
|
||||||
let mut monitors = VecDeque::with_capacity(displays.len());
|
let mut monitors = VecDeque::with_capacity(displays.len());
|
||||||
|
@ -101,7 +195,7 @@ impl MonitorHandle {
|
||||||
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
|
unsafe { NSScreen::backingScaleFactor(screen) as f64 }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||||
let cv_refresh_rate = unsafe {
|
let cv_refresh_rate = unsafe {
|
||||||
let mut display_link = std::ptr::null_mut();
|
let mut display_link = std::ptr::null_mut();
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -117,11 +211,27 @@ impl MonitorHandle {
|
||||||
time.timeScale as i64 / time.timeValue
|
time.timeScale as i64 / time.timeValue
|
||||||
};
|
};
|
||||||
|
|
||||||
CGDisplayMode::all_display_modes(self.0, std::ptr::null())
|
let monitor = self.clone();
|
||||||
.expect("failed to obtain list of display modes")
|
|
||||||
.into_iter()
|
unsafe {
|
||||||
.map(move |mode| {
|
let modes = {
|
||||||
let cg_refresh_rate = mode.refresh_rate().round() as i64;
|
let array = ffi::CGDisplayCopyAllDisplayModes(self.0, std::ptr::null());
|
||||||
|
assert!(!array.is_null(), "failed to get list of display modes");
|
||||||
|
let array_count = CFArrayGetCount(array);
|
||||||
|
let modes: Vec<_> = (0..array_count)
|
||||||
|
.into_iter()
|
||||||
|
.map(move |i| {
|
||||||
|
let mode = CFArrayGetValueAtIndex(array, i) as *mut _;
|
||||||
|
ffi::CGDisplayModeRetain(mode);
|
||||||
|
mode
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
CFRelease(array as *const _);
|
||||||
|
modes
|
||||||
|
};
|
||||||
|
|
||||||
|
modes.into_iter().map(move |mode| {
|
||||||
|
let cg_refresh_rate = ffi::CGDisplayModeGetRefreshRate(mode).round() as i64;
|
||||||
|
|
||||||
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
|
// CGDisplayModeGetRefreshRate returns 0.0 for any display that
|
||||||
// isn't a CRT
|
// isn't a CRT
|
||||||
|
@ -131,34 +241,55 @@ impl MonitorHandle {
|
||||||
cv_refresh_rate
|
cv_refresh_rate
|
||||||
};
|
};
|
||||||
|
|
||||||
VideoMode {
|
let pixel_encoding =
|
||||||
size: (mode.width() as u32, mode.height() as u32),
|
CFString::wrap_under_create_rule(ffi::CGDisplayModeCopyPixelEncoding(mode))
|
||||||
|
.to_string();
|
||||||
|
let bit_depth = if pixel_encoding.eq_ignore_ascii_case(ffi::IO32BitDirectPixels) {
|
||||||
|
32
|
||||||
|
} else if pixel_encoding.eq_ignore_ascii_case(ffi::IO16BitDirectPixels) {
|
||||||
|
16
|
||||||
|
} else if pixel_encoding.eq_ignore_ascii_case(ffi::kIO30BitDirectPixels) {
|
||||||
|
30
|
||||||
|
} else {
|
||||||
|
unimplemented!()
|
||||||
|
};
|
||||||
|
|
||||||
|
let video_mode = VideoMode {
|
||||||
|
size: (
|
||||||
|
ffi::CGDisplayModeGetPixelWidth(mode) as u32,
|
||||||
|
ffi::CGDisplayModeGetPixelHeight(mode) as u32,
|
||||||
|
),
|
||||||
refresh_rate: refresh_rate as u16,
|
refresh_rate: refresh_rate as u16,
|
||||||
bit_depth: mode.bit_depth() as u16,
|
bit_depth,
|
||||||
}
|
monitor: monitor.clone(),
|
||||||
|
native_mode: NativeDisplayMode(mode),
|
||||||
|
};
|
||||||
|
|
||||||
|
RootVideoMode { video_mode }
|
||||||
})
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ns_screen(&self) -> Option<id> {
|
pub(crate) fn ns_screen(&self) -> Option<id> {
|
||||||
unsafe {
|
unsafe {
|
||||||
let native_id = self.native_identifier();
|
let uuid = ffi::CGDisplayCreateUUIDFromDisplayID(self.0);
|
||||||
let screens = NSScreen::screens(nil);
|
let screens = NSScreen::screens(nil);
|
||||||
let count: NSUInteger = msg_send![screens, count];
|
let count: NSUInteger = msg_send![screens, count];
|
||||||
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
let key = IdRef::new(NSString::alloc(nil).init_str("NSScreenNumber"));
|
||||||
let mut matching_screen: Option<id> = None;
|
|
||||||
for i in 0..count {
|
for i in 0..count {
|
||||||
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
|
let screen = msg_send![screens, objectAtIndex: i as NSUInteger];
|
||||||
let device_description = NSScreen::deviceDescription(screen);
|
let device_description = NSScreen::deviceDescription(screen);
|
||||||
let value: id = msg_send![device_description, objectForKey:*key];
|
let value: id = msg_send![device_description, objectForKey:*key];
|
||||||
if value != nil {
|
if value != nil {
|
||||||
let screen_number: NSUInteger = msg_send![value, unsignedIntegerValue];
|
let other_native_id: NSUInteger = msg_send![value, unsignedIntegerValue];
|
||||||
if screen_number as u32 == native_id {
|
let other_uuid =
|
||||||
matching_screen = Some(screen);
|
ffi::CGDisplayCreateUUIDFromDisplayID(other_native_id as CGDirectDisplayID);
|
||||||
break;
|
if uuid == other_uuid {
|
||||||
|
return Some(screen);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
matching_screen
|
None
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{self, os::raw::*, ptr, time::Instant};
|
use std::{self, os::raw::*, ptr, time::Instant};
|
||||||
|
|
||||||
use crate::platform_impl::platform::app_state::AppState;
|
use crate::platform_impl::platform::{app_state::AppState, ffi};
|
||||||
|
|
||||||
#[link(name = "CoreFoundation", kind = "framework")]
|
#[link(name = "CoreFoundation", kind = "framework")]
|
||||||
extern "C" {
|
extern "C" {
|
||||||
|
@ -13,7 +13,7 @@ extern "C" {
|
||||||
pub fn CFRunLoopObserverCreate(
|
pub fn CFRunLoopObserverCreate(
|
||||||
allocator: CFAllocatorRef,
|
allocator: CFAllocatorRef,
|
||||||
activities: CFOptionFlags,
|
activities: CFOptionFlags,
|
||||||
repeats: Boolean,
|
repeats: ffi::Boolean,
|
||||||
order: CFIndex,
|
order: CFIndex,
|
||||||
callout: CFRunLoopObserverCallBack,
|
callout: CFRunLoopObserverCallBack,
|
||||||
context: *mut CFRunLoopObserverContext,
|
context: *mut CFRunLoopObserverContext,
|
||||||
|
@ -51,11 +51,6 @@ extern "C" {
|
||||||
pub fn CFRelease(cftype: *const c_void);
|
pub fn CFRelease(cftype: *const c_void);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Boolean = u8;
|
|
||||||
#[allow(dead_code)]
|
|
||||||
const FALSE: Boolean = 0;
|
|
||||||
const TRUE: Boolean = 1;
|
|
||||||
|
|
||||||
pub enum CFAllocator {}
|
pub enum CFAllocator {}
|
||||||
pub type CFAllocatorRef = *mut CFAllocator;
|
pub type CFAllocatorRef = *mut CFAllocator;
|
||||||
pub enum CFRunLoop {}
|
pub enum CFRunLoop {}
|
||||||
|
@ -102,7 +97,7 @@ pub struct CFRunLoopSourceContext {
|
||||||
pub retain: extern "C" fn(*const c_void) -> *const c_void,
|
pub retain: extern "C" fn(*const c_void) -> *const c_void,
|
||||||
pub release: extern "C" fn(*const c_void),
|
pub release: extern "C" fn(*const c_void),
|
||||||
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
|
pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef,
|
||||||
pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean,
|
pub equal: extern "C" fn(*const c_void, *const c_void) -> ffi::Boolean,
|
||||||
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
|
pub hash: extern "C" fn(*const c_void) -> CFHashCode,
|
||||||
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
||||||
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode),
|
||||||
|
@ -162,8 +157,8 @@ impl RunLoop {
|
||||||
let observer = CFRunLoopObserverCreate(
|
let observer = CFRunLoopObserverCreate(
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
flags,
|
flags,
|
||||||
TRUE, // Indicates we want this to run repeatedly
|
ffi::TRUE, // Indicates we want this to run repeatedly
|
||||||
priority, // The lower the value, the sooner this will run
|
priority, // The lower the value, the sooner this will run
|
||||||
handler,
|
handler,
|
||||||
ptr::null_mut(),
|
ptr::null_mut(),
|
||||||
);
|
);
|
||||||
|
|
|
@ -206,7 +206,10 @@ extern "C" fn toggle_full_screen_callback(context: *mut c_void) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Window level must be restored from `CGShieldingWindowLevel()
|
||||||
|
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||||
|
// anything
|
||||||
|
context.ns_window.setLevel_(0);
|
||||||
context.ns_window.toggleFullScreen_(nil);
|
context.ns_window.toggleFullScreen_(nil);
|
||||||
}
|
}
|
||||||
Box::from_raw(context_ptr);
|
Box::from_raw(context_ptr);
|
||||||
|
|
|
@ -8,6 +8,23 @@ use std::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
dpi::{LogicalPosition, LogicalSize},
|
||||||
|
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||||
|
icon::Icon,
|
||||||
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
|
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS},
|
||||||
|
platform_impl::platform::{
|
||||||
|
app_state::AppState,
|
||||||
|
ffi,
|
||||||
|
monitor::{self, MonitorHandle, VideoMode},
|
||||||
|
util::{self, IdRef},
|
||||||
|
view::{self, new_view},
|
||||||
|
window_delegate::new_delegate,
|
||||||
|
OsError,
|
||||||
|
},
|
||||||
|
window::{CursorIcon, Fullscreen, WindowAttributes, WindowId as RootWindowId},
|
||||||
|
};
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::{
|
appkit::{
|
||||||
self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy,
|
self, CGFloat, NSApp, NSApplication, NSApplicationActivationPolicy,
|
||||||
|
@ -17,30 +34,12 @@ use cocoa::{
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
foundation::{NSAutoreleasePool, NSDictionary, NSPoint, NSRect, NSSize, NSString},
|
||||||
};
|
};
|
||||||
use core_graphics::display::CGDisplay;
|
use core_graphics::display::{CGDisplay, CGDisplayMode};
|
||||||
use objc::{
|
use objc::{
|
||||||
declare::ClassDecl,
|
declare::ClassDecl,
|
||||||
runtime::{Class, Object, Sel, BOOL, NO, YES},
|
runtime::{Class, Object, Sel, BOOL, NO, YES},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
|
||||||
icon::Icon,
|
|
||||||
monitor::MonitorHandle as RootMonitorHandle,
|
|
||||||
platform::macos::{ActivationPolicy, RequestUserAttentionType, WindowExtMacOS},
|
|
||||||
platform_impl::platform::{
|
|
||||||
app_state::AppState,
|
|
||||||
ffi,
|
|
||||||
monitor::{self, MonitorHandle},
|
|
||||||
util::{self, IdRef},
|
|
||||||
view::{self, new_view},
|
|
||||||
window_delegate::new_delegate,
|
|
||||||
OsError,
|
|
||||||
},
|
|
||||||
window::{CursorIcon, WindowAttributes, WindowId as RootWindowId},
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
pub struct Id(pub usize);
|
pub struct Id(pub usize);
|
||||||
|
|
||||||
|
@ -119,11 +118,14 @@ fn create_window(
|
||||||
unsafe {
|
unsafe {
|
||||||
let pool = NSAutoreleasePool::new(nil);
|
let pool = NSAutoreleasePool::new(nil);
|
||||||
let screen = match attrs.fullscreen {
|
let screen = match attrs.fullscreen {
|
||||||
Some(ref monitor_id) => {
|
Some(Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }))
|
||||||
let monitor_screen = monitor_id.inner.ns_screen();
|
| Some(Fullscreen::Exclusive(RootVideoMode {
|
||||||
|
video_mode: VideoMode { ref monitor, .. },
|
||||||
|
})) => {
|
||||||
|
let monitor_screen = monitor.ns_screen();
|
||||||
Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil)))
|
Some(monitor_screen.unwrap_or(appkit::NSScreen::mainScreen(nil)))
|
||||||
}
|
}
|
||||||
_ => None,
|
None => None,
|
||||||
};
|
};
|
||||||
let frame = match screen {
|
let frame = match screen {
|
||||||
Some(screen) => appkit::NSScreen::frame(screen),
|
Some(screen) => appkit::NSScreen::frame(screen),
|
||||||
|
@ -239,12 +241,15 @@ lazy_static! {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct SharedState {
|
pub struct SharedState {
|
||||||
pub resizable: bool,
|
pub resizable: bool,
|
||||||
pub fullscreen: Option<RootMonitorHandle>,
|
pub fullscreen: Option<Fullscreen>,
|
||||||
pub maximized: bool,
|
pub maximized: bool,
|
||||||
pub standard_frame: Option<NSRect>,
|
pub standard_frame: Option<NSRect>,
|
||||||
is_simple_fullscreen: bool,
|
is_simple_fullscreen: bool,
|
||||||
pub saved_style: Option<NSWindowStyleMask>,
|
pub saved_style: Option<NSWindowStyleMask>,
|
||||||
|
/// Presentation options saved before entering `set_simple_fullscreen`, and
|
||||||
|
/// restored upon exiting it
|
||||||
save_presentation_opts: Option<NSApplicationPresentationOptions>,
|
save_presentation_opts: Option<NSApplicationPresentationOptions>,
|
||||||
|
pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedState {
|
impl SharedState {
|
||||||
|
@ -362,16 +367,7 @@ impl UnownedWindow {
|
||||||
let delegate = new_delegate(&window, fullscreen.is_some());
|
let delegate = new_delegate(&window, fullscreen.is_some());
|
||||||
|
|
||||||
// Set fullscreen mode after we setup everything
|
// Set fullscreen mode after we setup everything
|
||||||
if let Some(monitor) = fullscreen {
|
window.set_fullscreen(fullscreen);
|
||||||
if monitor.inner != window.current_monitor().inner {
|
|
||||||
// To do this with native fullscreen, we probably need to
|
|
||||||
// warp the window... while we could use
|
|
||||||
// `enterFullScreenMode`, they're idiomatically different
|
|
||||||
// fullscreen modes, so we'd have to support both anyway.
|
|
||||||
unimplemented!();
|
|
||||||
}
|
|
||||||
window.set_fullscreen(Some(monitor));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Setting the window as key has to happen *after* we set the fullscreen
|
// Setting the window as key has to happen *after* we set the fullscreen
|
||||||
// state, since otherwise we'll briefly see the window at normal size
|
// state, since otherwise we'll briefly see the window at normal size
|
||||||
|
@ -601,22 +597,44 @@ impl UnownedWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// This is called when the window is exiting fullscreen, whether by the
|
||||||
|
/// user clicking on the green fullscreen button or programmatically by
|
||||||
|
/// `toggleFullScreen:`
|
||||||
pub(crate) fn restore_state_from_fullscreen(&self) {
|
pub(crate) fn restore_state_from_fullscreen(&self) {
|
||||||
let maximized = {
|
trace!("Locked shared state in `restore_state_from_fullscreen`");
|
||||||
trace!("Locked shared state in `restore_state_from_fullscreen`");
|
let mut shared_state_lock = self.shared_state.lock().unwrap();
|
||||||
let mut shared_state_lock = self.shared_state.lock().unwrap();
|
|
||||||
|
|
||||||
shared_state_lock.fullscreen = None;
|
shared_state_lock.fullscreen = None;
|
||||||
|
|
||||||
let mask = self.saved_style(&mut *shared_state_lock);
|
let maximized = shared_state_lock.maximized;
|
||||||
|
let mask = self.saved_style(&mut *shared_state_lock);
|
||||||
|
|
||||||
self.set_style_mask_async(mask);
|
drop(shared_state_lock);
|
||||||
shared_state_lock.maximized
|
|
||||||
};
|
|
||||||
trace!("Unocked shared state in `restore_state_from_fullscreen`");
|
trace!("Unocked shared state in `restore_state_from_fullscreen`");
|
||||||
|
|
||||||
|
self.set_style_mask_async(mask);
|
||||||
self.set_maximized(maximized);
|
self.set_maximized(maximized);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn restore_display_mode(&self) {
|
||||||
|
trace!("Locked shared state in `restore_display_mode`");
|
||||||
|
let shared_state_lock = self.shared_state.lock().unwrap();
|
||||||
|
|
||||||
|
if let Some(Fullscreen::Exclusive(RootVideoMode { ref video_mode })) =
|
||||||
|
shared_state_lock.fullscreen
|
||||||
|
{
|
||||||
|
unsafe {
|
||||||
|
ffi::CGRestorePermanentDisplayConfiguration();
|
||||||
|
assert_eq!(
|
||||||
|
ffi::CGDisplayRelease(video_mode.monitor().inner.native_identifier()),
|
||||||
|
ffi::kCGErrorSuccess
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("Unlocked shared state in `restore_display_mode`");
|
||||||
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_maximized(&self, maximized: bool) {
|
pub fn set_maximized(&self, maximized: bool) {
|
||||||
let is_zoomed = self.is_zoomed();
|
let is_zoomed = self.is_zoomed();
|
||||||
|
@ -634,44 +652,159 @@ impl UnownedWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
let shared_state_lock = self.shared_state.lock().unwrap();
|
let shared_state_lock = self.shared_state.lock().unwrap();
|
||||||
shared_state_lock.fullscreen.clone()
|
shared_state_lock.fullscreen.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
/// TODO: Right now set_fullscreen do not work on switching monitors
|
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||||
/// in fullscreen mode
|
trace!("Locked shared state in `set_fullscreen`");
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
|
||||||
let shared_state_lock = self.shared_state.lock().unwrap();
|
let shared_state_lock = self.shared_state.lock().unwrap();
|
||||||
if shared_state_lock.is_simple_fullscreen {
|
if shared_state_lock.is_simple_fullscreen {
|
||||||
|
trace!("Unlocked shared state in `set_fullscreen`");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
let old_fullscreen = shared_state_lock.fullscreen.clone();
|
||||||
let not_fullscreen = {
|
if fullscreen == old_fullscreen {
|
||||||
trace!("Locked shared state in `set_fullscreen`");
|
|
||||||
let current = &shared_state_lock.fullscreen;
|
|
||||||
match (current, monitor) {
|
|
||||||
(&Some(ref a), Some(ref b)) if a.inner != b.inner => {
|
|
||||||
// Our best bet is probably to move to the origin of the
|
|
||||||
// target monitor.
|
|
||||||
unimplemented!()
|
|
||||||
}
|
|
||||||
(&None, None) | (&Some(_), Some(_)) => return,
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
trace!("Unlocked shared state in `set_fullscreen`");
|
trace!("Unlocked shared state in `set_fullscreen`");
|
||||||
current.is_none()
|
return;
|
||||||
};
|
}
|
||||||
|
trace!("Unlocked shared state in `set_fullscreen`");
|
||||||
|
drop(shared_state_lock);
|
||||||
|
|
||||||
unsafe {
|
// If the fullscreen is on a different monitor, we must move the window
|
||||||
util::toggle_full_screen_async(
|
// to that monitor before we toggle fullscreen (as `toggleFullScreen`
|
||||||
*self.ns_window,
|
// does not take a screen parameter, but uses the current screen)
|
||||||
*self.ns_view,
|
if let Some(ref fullscreen) = fullscreen {
|
||||||
not_fullscreen,
|
let new_screen = match fullscreen {
|
||||||
Arc::downgrade(&self.shared_state),
|
Fullscreen::Borderless(RootMonitorHandle { inner: ref monitor }) => monitor,
|
||||||
)
|
Fullscreen::Exclusive(RootVideoMode {
|
||||||
};
|
video_mode: VideoMode { ref monitor, .. },
|
||||||
|
}) => monitor,
|
||||||
|
}
|
||||||
|
.ns_screen()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let old_screen = NSWindow::screen(*self.ns_window);
|
||||||
|
if old_screen != new_screen {
|
||||||
|
let mut screen_frame: NSRect = msg_send![new_screen, frame];
|
||||||
|
// The coordinate system here has its origin at bottom-left
|
||||||
|
// and Y goes up
|
||||||
|
screen_frame.origin.y += screen_frame.size.height;
|
||||||
|
util::set_frame_top_left_point_async(*self.ns_window, screen_frame.origin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(Fullscreen::Exclusive(ref video_mode)) = fullscreen {
|
||||||
|
// Note: `enterFullScreenMode:withOptions:` seems to do the exact
|
||||||
|
// same thing as we're doing here (captures the display, sets the
|
||||||
|
// video mode, and hides the menu bar and dock), with the exception
|
||||||
|
// of that I couldn't figure out how to set the display mode with
|
||||||
|
// it. I think `enterFullScreenMode:withOptions:` is still using the
|
||||||
|
// older display mode API where display modes were of the type
|
||||||
|
// `CFDictionary`, but this has changed, so we can't obtain the
|
||||||
|
// correct parameter for this any longer. Apple's code samples for
|
||||||
|
// this function seem to just pass in "YES" for the display mode
|
||||||
|
// parameter, which is not consistent with the docs saying that it
|
||||||
|
// takes a `NSDictionary`..
|
||||||
|
|
||||||
|
let display_id = video_mode.monitor().inner.native_identifier();
|
||||||
|
|
||||||
|
let mut fade_token = ffi::kCGDisplayFadeReservationInvalidToken;
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// Fade to black (and wait for the fade to complete) to hide the
|
||||||
|
// flicker from capturing the display and switching display mode
|
||||||
|
if ffi::CGAcquireDisplayFadeReservation(5.0, &mut fade_token)
|
||||||
|
== ffi::kCGErrorSuccess
|
||||||
|
{
|
||||||
|
ffi::CGDisplayFade(
|
||||||
|
fade_token,
|
||||||
|
0.3,
|
||||||
|
ffi::kCGDisplayBlendNormal,
|
||||||
|
ffi::kCGDisplayBlendSolidColor,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
ffi::TRUE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(ffi::CGDisplayCapture(display_id), ffi::kCGErrorSuccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
let result = ffi::CGDisplaySetDisplayMode(
|
||||||
|
display_id,
|
||||||
|
video_mode.video_mode.native_mode.0,
|
||||||
|
std::ptr::null(),
|
||||||
|
);
|
||||||
|
assert!(result == ffi::kCGErrorSuccess, "failed to set video mode");
|
||||||
|
|
||||||
|
// After the display has been configured, fade back in
|
||||||
|
// asynchronously
|
||||||
|
if fade_token != ffi::kCGDisplayFadeReservationInvalidToken {
|
||||||
|
ffi::CGDisplayFade(
|
||||||
|
fade_token,
|
||||||
|
0.6,
|
||||||
|
ffi::kCGDisplayBlendSolidColor,
|
||||||
|
ffi::kCGDisplayBlendNormal,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
0.0,
|
||||||
|
ffi::FALSE,
|
||||||
|
);
|
||||||
|
ffi::CGReleaseDisplayFadeReservation(fade_token);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match (&old_fullscreen, &fullscreen) {
|
||||||
|
(&Some(Fullscreen::Borderless(_)), &Some(Fullscreen::Exclusive(_))) => unsafe {
|
||||||
|
// If we're already in fullscreen mode, calling
|
||||||
|
// `CGDisplayCapture` will place the shielding window on top of
|
||||||
|
// our window, which results in a black display and is not what
|
||||||
|
// we want. So, we must place our window on top of the shielding
|
||||||
|
// window. Unfortunately, this also makes our window be on top
|
||||||
|
// of the menu bar, and this looks broken, so we must make sure
|
||||||
|
// that the menu bar is disabled. This is done in the window
|
||||||
|
// delegate in `window:willUseFullScreenPresentationOptions:`.
|
||||||
|
msg_send![*self.ns_window, setLevel: ffi::CGShieldingWindowLevel() + 1];
|
||||||
|
},
|
||||||
|
(&Some(Fullscreen::Exclusive(_)), &None) => unsafe {
|
||||||
|
self.restore_display_mode();
|
||||||
|
|
||||||
|
util::toggle_full_screen_async(
|
||||||
|
*self.ns_window,
|
||||||
|
*self.ns_view,
|
||||||
|
old_fullscreen.is_none(),
|
||||||
|
Arc::downgrade(&self.shared_state),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
(&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
|
||||||
|
self.restore_display_mode();
|
||||||
|
}
|
||||||
|
(&None, &Some(Fullscreen::Exclusive(_)))
|
||||||
|
| (&None, &Some(Fullscreen::Borderless(_)))
|
||||||
|
| (&Some(Fullscreen::Borderless(_)), &None) => unsafe {
|
||||||
|
// Wish it were this simple for all cases
|
||||||
|
util::toggle_full_screen_async(
|
||||||
|
*self.ns_window,
|
||||||
|
*self.ns_view,
|
||||||
|
old_fullscreen.is_none(),
|
||||||
|
Arc::downgrade(&self.shared_state),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("Locked shared state in `set_fullscreen`");
|
||||||
|
let mut shared_state_lock = self.shared_state.lock().unwrap();
|
||||||
|
shared_state_lock.fullscreen = fullscreen.clone();
|
||||||
|
trace!("Unlocked shared state in `set_fullscreen`");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
@ -5,9 +5,9 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use cocoa::{
|
use cocoa::{
|
||||||
appkit::{self, NSView, NSWindow},
|
appkit::{self, NSApplicationPresentationOptions, NSView, NSWindow},
|
||||||
base::{id, nil},
|
base::{id, nil},
|
||||||
foundation::NSAutoreleasePool,
|
foundation::{NSAutoreleasePool, NSUInteger},
|
||||||
};
|
};
|
||||||
use objc::{
|
use objc::{
|
||||||
declare::ClassDecl,
|
declare::ClassDecl,
|
||||||
|
@ -22,7 +22,7 @@ use crate::{
|
||||||
util::{self, IdRef},
|
util::{self, IdRef},
|
||||||
window::{get_window_id, UnownedWindow},
|
window::{get_window_id, UnownedWindow},
|
||||||
},
|
},
|
||||||
window::WindowId,
|
window::{Fullscreen, WindowId},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct WindowDelegateState {
|
pub struct WindowDelegateState {
|
||||||
|
@ -182,6 +182,11 @@ lazy_static! {
|
||||||
dragging_exited as extern "C" fn(&Object, Sel, id),
|
dragging_exited as extern "C" fn(&Object, Sel, id),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
decl.add_method(
|
||||||
|
sel!(window:willUseFullScreenPresentationOptions:),
|
||||||
|
window_will_use_fullscreen_presentation_options
|
||||||
|
as extern "C" fn(&Object, Sel, id, NSUInteger) -> NSUInteger,
|
||||||
|
);
|
||||||
decl.add_method(
|
decl.add_method(
|
||||||
sel!(windowDidEnterFullScreen:),
|
sel!(windowDidEnterFullScreen:),
|
||||||
window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id),
|
window_did_enter_fullscreen as extern "C" fn(&Object, Sel, id),
|
||||||
|
@ -408,6 +413,26 @@ extern "C" fn window_will_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||||
trace!("Completed `windowWillEnterFullscreen:`");
|
trace!("Completed `windowWillEnterFullscreen:`");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extern "C" fn window_will_use_fullscreen_presentation_options(
|
||||||
|
_this: &Object,
|
||||||
|
_: Sel,
|
||||||
|
_: id,
|
||||||
|
_proposed_options: NSUInteger,
|
||||||
|
) -> NSUInteger {
|
||||||
|
// Generally, games will want to disable the menu bar and the dock. Ideally,
|
||||||
|
// this would be configurable by the user. Unfortunately because of our
|
||||||
|
// `CGShieldingWindowLevel() + 1` hack (see `set_fullscreen`), our window is
|
||||||
|
// placed on top of the menu bar in exclusive fullscreen mode. This looks
|
||||||
|
// broken so we always disable the menu bar in exclusive fullscreen. We may
|
||||||
|
// still want to make this configurable for borderless fullscreen. Right now
|
||||||
|
// we don't, for consistency. If we do, it should be documented that the
|
||||||
|
// user-provided options are ignored in exclusive fullscreen.
|
||||||
|
(NSApplicationPresentationOptions::NSApplicationPresentationFullScreen
|
||||||
|
| NSApplicationPresentationOptions::NSApplicationPresentationHideDock
|
||||||
|
| NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar)
|
||||||
|
.bits()
|
||||||
|
}
|
||||||
|
|
||||||
/// Invoked when entered fullscreen
|
/// Invoked when entered fullscreen
|
||||||
extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||||
trace!("Triggered `windowDidEnterFullscreen:`");
|
trace!("Triggered `windowDidEnterFullscreen:`");
|
||||||
|
@ -415,8 +440,21 @@ extern "C" fn window_did_enter_fullscreen(this: &Object, _: Sel, _: id) {
|
||||||
state.with_window(|window| {
|
state.with_window(|window| {
|
||||||
let monitor = window.current_monitor();
|
let monitor = window.current_monitor();
|
||||||
trace!("Locked shared state in `window_did_enter_fullscreen`");
|
trace!("Locked shared state in `window_did_enter_fullscreen`");
|
||||||
window.shared_state.lock().unwrap().fullscreen = Some(monitor);
|
let mut shared_state = window.shared_state.lock().unwrap();
|
||||||
trace!("Unlocked shared state in `window_will_enter_fullscreen`");
|
match shared_state.fullscreen {
|
||||||
|
// Exclusive mode sets the state in `set_fullscreen` as the user
|
||||||
|
// can't enter exclusive mode by other means (like the
|
||||||
|
// fullscreen button on the window decorations)
|
||||||
|
Some(Fullscreen::Exclusive(_)) => (),
|
||||||
|
// `window_did_enter_fullscreen` was triggered and we're already
|
||||||
|
// in fullscreen, so we must've reached here by `set_fullscreen`
|
||||||
|
// as it updates the state
|
||||||
|
Some(Fullscreen::Borderless(_)) => (),
|
||||||
|
// Otherwise, we must've reached fullscreen by the user clicking
|
||||||
|
// on the green fullscreen button. Update state!
|
||||||
|
None => shared_state.fullscreen = Some(Fullscreen::Borderless(monitor)),
|
||||||
|
}
|
||||||
|
trace!("Unlocked shared state in `window_did_enter_fullscreen`");
|
||||||
});
|
});
|
||||||
state.initial_fullscreen = false;
|
state.initial_fullscreen = false;
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,7 @@ use winapi::{self, shared::windef::HWND};
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
|
||||||
monitor::MonitorHandle,
|
monitor::{MonitorHandle, VideoMode},
|
||||||
window::Window,
|
window::Window,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,54 +3,64 @@ use winapi::{
|
||||||
minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD},
|
minwindef::{BOOL, DWORD, LPARAM, TRUE, WORD},
|
||||||
windef::{HDC, HMONITOR, HWND, LPRECT, POINT},
|
windef::{HDC, HMONITOR, HWND, LPRECT, POINT},
|
||||||
},
|
},
|
||||||
um::{wingdi, winnt::LONG, winuser},
|
um::{wingdi, winuser},
|
||||||
};
|
};
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
collections::{HashSet, VecDeque},
|
collections::{BTreeSet, VecDeque},
|
||||||
io, mem, ptr,
|
io, mem, ptr,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{util, EventLoop};
|
use super::{util, EventLoop};
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize},
|
dpi::{PhysicalPosition, PhysicalSize},
|
||||||
monitor::VideoMode,
|
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
|
||||||
platform_impl::platform::{
|
platform_impl::platform::{
|
||||||
dpi::{dpi_to_scale_factor, get_monitor_dpi},
|
dpi::{dpi_to_scale_factor, get_monitor_dpi},
|
||||||
window::Window,
|
window::Window,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Win32 implementation of the main `MonitorHandle` object.
|
|
||||||
#[derive(Derivative)]
|
#[derive(Derivative)]
|
||||||
#[derivative(Debug, Clone)]
|
#[derivative(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct MonitorHandle {
|
pub struct VideoMode {
|
||||||
/// Monitor handle.
|
pub(crate) size: (u32, u32),
|
||||||
hmonitor: HMonitor,
|
pub(crate) bit_depth: u16,
|
||||||
#[derivative(Debug = "ignore")]
|
pub(crate) refresh_rate: u16,
|
||||||
monitor_info: winuser::MONITORINFOEXW,
|
pub(crate) monitor: MonitorHandle,
|
||||||
/// The system name of the monitor.
|
#[derivative(Debug = "ignore", PartialEq = "ignore", Hash = "ignore")]
|
||||||
monitor_name: String,
|
pub(crate) native_video_mode: wingdi::DEVMODEW,
|
||||||
/// True if this is the primary monitor.
|
|
||||||
primary: bool,
|
|
||||||
/// The position of the monitor in pixels on the desktop.
|
|
||||||
///
|
|
||||||
/// A window that is positioned at these coordinates will overlap the monitor.
|
|
||||||
position: (i32, i32),
|
|
||||||
/// The current resolution in pixels on the monitor.
|
|
||||||
dimensions: (u32, u32),
|
|
||||||
/// DPI scale factor.
|
|
||||||
hidpi_factor: f64,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VideoMode {
|
||||||
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, PartialOrd, Ord)]
|
||||||
|
pub struct MonitorHandle(HMONITOR);
|
||||||
|
|
||||||
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
|
// Send is not implemented for HMONITOR, we have to wrap it and implement it manually.
|
||||||
// For more info see:
|
// For more info see:
|
||||||
// https://github.com/retep998/winapi-rs/issues/360
|
// https://github.com/retep998/winapi-rs/issues/360
|
||||||
// https://github.com/retep998/winapi-rs/issues/396
|
// https://github.com/retep998/winapi-rs/issues/396
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct HMonitor(HMONITOR);
|
|
||||||
|
|
||||||
unsafe impl Send for HMonitor {}
|
unsafe impl Send for MonitorHandle {}
|
||||||
|
|
||||||
unsafe extern "system" fn monitor_enum_proc(
|
unsafe extern "system" fn monitor_enum_proc(
|
||||||
hmonitor: HMONITOR,
|
hmonitor: HMONITOR,
|
||||||
|
@ -59,7 +69,7 @@ unsafe extern "system" fn monitor_enum_proc(
|
||||||
data: LPARAM,
|
data: LPARAM,
|
||||||
) -> BOOL {
|
) -> BOOL {
|
||||||
let monitors = data as *mut VecDeque<MonitorHandle>;
|
let monitors = data as *mut VecDeque<MonitorHandle>;
|
||||||
(*monitors).push_back(MonitorHandle::from_hmonitor(hmonitor));
|
(*monitors).push_back(MonitorHandle::new(hmonitor));
|
||||||
TRUE // continue enumeration
|
TRUE // continue enumeration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,12 +89,12 @@ pub fn available_monitors() -> VecDeque<MonitorHandle> {
|
||||||
pub fn primary_monitor() -> MonitorHandle {
|
pub fn primary_monitor() -> MonitorHandle {
|
||||||
const ORIGIN: POINT = POINT { x: 0, y: 0 };
|
const ORIGIN: POINT = POINT { x: 0, y: 0 };
|
||||||
let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) };
|
let hmonitor = unsafe { winuser::MonitorFromPoint(ORIGIN, winuser::MONITOR_DEFAULTTOPRIMARY) };
|
||||||
MonitorHandle::from_hmonitor(hmonitor)
|
MonitorHandle::new(hmonitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn current_monitor(hwnd: HWND) -> MonitorHandle {
|
pub fn current_monitor(hwnd: HWND) -> MonitorHandle {
|
||||||
let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) };
|
let hmonitor = unsafe { winuser::MonitorFromWindow(hwnd, winuser::MONITOR_DEFAULTTONEAREST) };
|
||||||
MonitorHandle::from_hmonitor(hmonitor)
|
MonitorHandle::new(hmonitor)
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> EventLoop<T> {
|
impl<T> EventLoop<T> {
|
||||||
|
@ -125,73 +135,69 @@ pub(crate) fn get_monitor_info(hmonitor: HMONITOR) -> Result<winuser::MONITORINF
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MonitorHandle {
|
impl MonitorHandle {
|
||||||
pub(crate) fn from_hmonitor(hmonitor: HMONITOR) -> Self {
|
pub(crate) fn new(hmonitor: HMONITOR) -> Self {
|
||||||
let monitor_info = get_monitor_info(hmonitor).expect("`GetMonitorInfoW` failed");
|
MonitorHandle(hmonitor)
|
||||||
let place = monitor_info.rcMonitor;
|
|
||||||
let dimensions = (
|
|
||||||
(place.right - place.left) as u32,
|
|
||||||
(place.bottom - place.top) as u32,
|
|
||||||
);
|
|
||||||
MonitorHandle {
|
|
||||||
hmonitor: HMonitor(hmonitor),
|
|
||||||
monitor_name: util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()),
|
|
||||||
primary: util::has_flag(monitor_info.dwFlags, winuser::MONITORINFOF_PRIMARY),
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
|
pub(crate) fn contains_point(&self, point: &POINT) -> bool {
|
||||||
let left = self.position.0 as LONG;
|
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||||
let right = left + self.dimensions.0 as LONG;
|
point.x >= monitor_info.rcMonitor.left
|
||||||
let top = self.position.1 as LONG;
|
&& point.x <= monitor_info.rcMonitor.right
|
||||||
let bottom = top + self.dimensions.1 as LONG;
|
&& point.y >= monitor_info.rcMonitor.top
|
||||||
point.x >= left && point.x <= right && point.y >= top && point.y <= bottom
|
&& point.y <= monitor_info.rcMonitor.bottom
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn name(&self) -> Option<String> {
|
pub fn name(&self) -> Option<String> {
|
||||||
Some(self.monitor_name.clone())
|
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||||
|
Some(util::wchar_ptr_to_string(monitor_info.szDevice.as_ptr()))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn native_identifier(&self) -> String {
|
pub fn native_identifier(&self) -> String {
|
||||||
self.monitor_name.clone()
|
self.name().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn hmonitor(&self) -> HMONITOR {
|
pub fn hmonitor(&self) -> HMONITOR {
|
||||||
self.hmonitor.0
|
self.0
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn size(&self) -> PhysicalSize {
|
pub fn size(&self) -> PhysicalSize {
|
||||||
self.dimensions.into()
|
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||||
|
PhysicalSize {
|
||||||
|
width: (monitor_info.rcMonitor.right - monitor_info.rcMonitor.left) as f64,
|
||||||
|
height: (monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top) as f64,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn position(&self) -> PhysicalPosition {
|
pub fn position(&self) -> PhysicalPosition {
|
||||||
self.position.into()
|
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||||
|
PhysicalPosition {
|
||||||
|
x: monitor_info.rcMonitor.left as f64,
|
||||||
|
y: monitor_info.rcMonitor.top as f64,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn hidpi_factor(&self) -> f64 {
|
pub fn hidpi_factor(&self) -> f64 {
|
||||||
self.hidpi_factor
|
dpi_to_scale_factor(get_monitor_dpi(self.0).unwrap_or(96))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
|
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
|
||||||
// EnumDisplaySettingsExW can return duplicate values (or some of the
|
// EnumDisplaySettingsExW can return duplicate values (or some of the
|
||||||
// fields are probably changing, but we aren't looking at those fields
|
// fields are probably changing, but we aren't looking at those fields
|
||||||
// anyway), so we're using a HashSet deduplicate
|
// anyway), so we're using a BTreeSet deduplicate
|
||||||
let mut modes = HashSet::new();
|
let mut modes = BTreeSet::new();
|
||||||
let mut i = 0;
|
let mut i = 0;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
unsafe {
|
unsafe {
|
||||||
let device_name = self.monitor_info.szDevice.as_ptr();
|
let monitor_info = get_monitor_info(self.0).unwrap();
|
||||||
|
let device_name = monitor_info.szDevice.as_ptr();
|
||||||
let mut mode: wingdi::DEVMODEW = mem::zeroed();
|
let mut mode: wingdi::DEVMODEW = mem::zeroed();
|
||||||
mode.dmSize = mem::size_of_val(&mode) as WORD;
|
mode.dmSize = mem::size_of_val(&mode) as WORD;
|
||||||
if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 {
|
if winuser::EnumDisplaySettingsExW(device_name, i, &mut mode, 0) == 0 {
|
||||||
|
@ -205,10 +211,14 @@ impl MonitorHandle {
|
||||||
| wingdi::DM_DISPLAYFREQUENCY;
|
| wingdi::DM_DISPLAYFREQUENCY;
|
||||||
assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS);
|
assert!(mode.dmFields & REQUIRED_FIELDS == REQUIRED_FIELDS);
|
||||||
|
|
||||||
modes.insert(VideoMode {
|
modes.insert(RootVideoMode {
|
||||||
size: (mode.dmPelsWidth, mode.dmPelsHeight),
|
video_mode: VideoMode {
|
||||||
bit_depth: mode.dmBitsPerPel as u16,
|
size: (mode.dmPelsWidth, mode.dmPelsHeight),
|
||||||
refresh_rate: mode.dmDisplayFrequency as u16,
|
bit_depth: mode.dmBitsPerPel as u16,
|
||||||
|
refresh_rate: mode.dmDisplayFrequency as u16,
|
||||||
|
monitor: self.clone(),
|
||||||
|
native_video_mode: mode,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,7 +46,7 @@ use crate::{
|
||||||
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
|
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
|
||||||
PlatformSpecificWindowBuilderAttributes, WindowId,
|
PlatformSpecificWindowBuilderAttributes, WindowId,
|
||||||
},
|
},
|
||||||
window::{CursorIcon, Icon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, Icon, WindowAttributes},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The Win32 implementation of the main `Window` object.
|
/// The Win32 implementation of the main `Window` object.
|
||||||
|
@ -327,7 +327,7 @@ impl Window {
|
||||||
let window_state = Arc::clone(&self.window_state);
|
let window_state = Arc::clone(&self.window_state);
|
||||||
|
|
||||||
self.thread_executor.execute_in_thread(move || {
|
self.thread_executor.execute_in_thread(move || {
|
||||||
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
|
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||||
f.set(WindowFlags::RESIZABLE, resizable)
|
f.set(WindowFlags::RESIZABLE, resizable)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -421,80 +421,177 @@ impl Window {
|
||||||
let window_state = Arc::clone(&self.window_state);
|
let window_state = Arc::clone(&self.window_state);
|
||||||
|
|
||||||
self.thread_executor.execute_in_thread(move || {
|
self.thread_executor.execute_in_thread(move || {
|
||||||
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
|
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||||
f.set(WindowFlags::MAXIMIZED, maximized)
|
f.set(WindowFlags::MAXIMIZED, maximized)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fullscreen(&self) -> Option<RootMonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
let window_state = self.window_state.lock();
|
let window_state = self.window_state.lock();
|
||||||
window_state.fullscreen.clone()
|
window_state.fullscreen.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_fullscreen(&self, monitor: Option<RootMonitorHandle>) {
|
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||||
unsafe {
|
let window = self.window.clone();
|
||||||
let window = self.window.clone();
|
let window_state = Arc::clone(&self.window_state);
|
||||||
let window_state = Arc::clone(&self.window_state);
|
|
||||||
|
|
||||||
match &monitor {
|
let mut window_state_lock = window_state.lock();
|
||||||
&Some(RootMonitorHandle { ref inner }) => {
|
let old_fullscreen = window_state_lock.fullscreen.clone();
|
||||||
let (x, y): (i32, i32) = inner.position().into();
|
if window_state_lock.fullscreen == fullscreen {
|
||||||
let (width, height): (u32, u32) = inner.size().into();
|
return;
|
||||||
|
}
|
||||||
|
window_state_lock.fullscreen = fullscreen.clone();
|
||||||
|
drop(window_state_lock);
|
||||||
|
|
||||||
let mut monitor = monitor.clone();
|
self.thread_executor.execute_in_thread(move || {
|
||||||
self.thread_executor.execute_in_thread(move || {
|
let mut window_state_lock = window_state.lock();
|
||||||
let mut window_state_lock = window_state.lock();
|
|
||||||
|
|
||||||
let client_rect =
|
// Save window bounds before entering fullscreen
|
||||||
util::get_client_rect(window.0).expect("get client rect failed!");
|
match (&old_fullscreen, &fullscreen) {
|
||||||
window_state_lock.saved_window = Some(SavedWindow {
|
(&None, &Some(_)) => {
|
||||||
client_rect,
|
let client_rect = util::get_client_rect(window.0).unwrap();
|
||||||
dpi_factor: window_state_lock.dpi_factor,
|
window_state_lock.saved_window = Some(SavedWindow {
|
||||||
});
|
client_rect,
|
||||||
|
dpi_factor: window_state_lock.dpi_factor,
|
||||||
window_state_lock.fullscreen = monitor.take();
|
|
||||||
WindowState::refresh_window_state(
|
|
||||||
window_state_lock,
|
|
||||||
window.0,
|
|
||||||
Some(RECT {
|
|
||||||
left: x,
|
|
||||||
top: y,
|
|
||||||
right: x + width as c_int,
|
|
||||||
bottom: y + height as c_int,
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
|
|
||||||
mark_fullscreen(window.0, true);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
&None => {
|
_ => (),
|
||||||
self.thread_executor.execute_in_thread(move || {
|
}
|
||||||
let mut window_state_lock = window_state.lock();
|
|
||||||
window_state_lock.fullscreen = None;
|
|
||||||
|
|
||||||
if let Some(SavedWindow {
|
// Change video mode if we're transitioning to or from exclusive
|
||||||
client_rect,
|
// fullscreen
|
||||||
dpi_factor,
|
match (&old_fullscreen, &fullscreen) {
|
||||||
}) = window_state_lock.saved_window
|
(&None, &Some(Fullscreen::Exclusive(ref video_mode)))
|
||||||
{
|
| (
|
||||||
window_state_lock.dpi_factor = dpi_factor;
|
&Some(Fullscreen::Borderless(_)),
|
||||||
window_state_lock.saved_window = None;
|
&Some(Fullscreen::Exclusive(ref video_mode)),
|
||||||
|
)
|
||||||
|
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Exclusive(ref video_mode))) =>
|
||||||
|
{
|
||||||
|
let monitor = video_mode.monitor();
|
||||||
|
|
||||||
WindowState::refresh_window_state(
|
let mut display_name = OsStr::new(&monitor.inner.native_identifier())
|
||||||
window_state_lock,
|
.encode_wide()
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
// `encode_wide` does not add a null-terminator but
|
||||||
|
// `ChangeDisplaySettingsExW` requires a null-terminated
|
||||||
|
// string, so add it
|
||||||
|
display_name.push(0);
|
||||||
|
|
||||||
|
let mut native_video_mode = video_mode.video_mode.native_video_mode.clone();
|
||||||
|
|
||||||
|
let res = unsafe {
|
||||||
|
winuser::ChangeDisplaySettingsExW(
|
||||||
|
display_name.as_ptr(),
|
||||||
|
&mut native_video_mode,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
winuser::CDS_FULLSCREEN,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADMODE);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADPARAM);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_FAILED);
|
||||||
|
assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL);
|
||||||
|
}
|
||||||
|
(&Some(Fullscreen::Exclusive(_)), &None)
|
||||||
|
| (&Some(Fullscreen::Exclusive(_)), &Some(Fullscreen::Borderless(_))) => {
|
||||||
|
let res = unsafe {
|
||||||
|
winuser::ChangeDisplaySettingsExW(
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
winuser::CDS_FULLSCREEN,
|
||||||
|
std::ptr::null_mut(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADFLAGS);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADMODE);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_BADPARAM);
|
||||||
|
debug_assert!(res != winuser::DISP_CHANGE_FAILED);
|
||||||
|
assert_eq!(res, winuser::DISP_CHANGE_SUCCESSFUL);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
// There are some scenarios where calling `ChangeDisplaySettingsExW` takes long
|
||||||
|
// enough to execute that the DWM thinks our program has frozen and takes over
|
||||||
|
// our program's window. When that happens, the `SetWindowPos` call below gets
|
||||||
|
// eaten and the window doesn't get set to the proper fullscreen position.
|
||||||
|
//
|
||||||
|
// Calling `PeekMessageW` here notifies Windows that our process is still running
|
||||||
|
// fine, taking control back from the DWM and ensuring that the `SetWindowPos` call
|
||||||
|
// below goes through.
|
||||||
|
let mut msg = mem::zeroed();
|
||||||
|
winuser::PeekMessageW(&mut msg, ptr::null_mut(), 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update window style
|
||||||
|
WindowState::set_window_flags(window_state_lock, window.0, |f| {
|
||||||
|
f.set(WindowFlags::MARKER_FULLSCREEN, fullscreen.is_some())
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update window bounds
|
||||||
|
match &fullscreen {
|
||||||
|
Some(fullscreen) => {
|
||||||
|
let monitor = match fullscreen {
|
||||||
|
Fullscreen::Exclusive(ref video_mode) => video_mode.monitor(),
|
||||||
|
Fullscreen::Borderless(ref monitor) => monitor.clone(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let position: (i32, i32) = monitor.position().into();
|
||||||
|
let size: (u32, u32) = monitor.size().into();
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
winuser::SetWindowPos(
|
||||||
|
window.0,
|
||||||
|
ptr::null_mut(),
|
||||||
|
position.0,
|
||||||
|
position.1,
|
||||||
|
size.0 as i32,
|
||||||
|
size.1 as i32,
|
||||||
|
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
|
||||||
|
);
|
||||||
|
winuser::UpdateWindow(window.0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
let mut window_state_lock = window_state.lock();
|
||||||
|
if let Some(SavedWindow {
|
||||||
|
client_rect,
|
||||||
|
dpi_factor,
|
||||||
|
}) = window_state_lock.saved_window.take()
|
||||||
|
{
|
||||||
|
window_state_lock.dpi_factor = dpi_factor;
|
||||||
|
drop(window_state_lock);
|
||||||
|
|
||||||
|
unsafe {
|
||||||
|
winuser::SetWindowPos(
|
||||||
window.0,
|
window.0,
|
||||||
Some(client_rect),
|
ptr::null_mut(),
|
||||||
|
client_rect.left,
|
||||||
|
client_rect.top,
|
||||||
|
client_rect.right - client_rect.left,
|
||||||
|
client_rect.bottom - client_rect.top,
|
||||||
|
winuser::SWP_ASYNCWINDOWPOS | winuser::SWP_NOZORDER,
|
||||||
);
|
);
|
||||||
|
winuser::UpdateWindow(window.0);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
mark_fullscreen(window.0, false);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
unsafe {
|
||||||
|
taskbar_mark_fullscreen(window.0, fullscreen.is_some());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -503,8 +600,7 @@ impl Window {
|
||||||
let window_state = Arc::clone(&self.window_state);
|
let window_state = Arc::clone(&self.window_state);
|
||||||
|
|
||||||
self.thread_executor.execute_in_thread(move || {
|
self.thread_executor.execute_in_thread(move || {
|
||||||
let client_rect = util::get_client_rect(window.0).expect("get client rect failed!");
|
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||||
WindowState::set_window_flags(window_state.lock(), window.0, Some(client_rect), |f| {
|
|
||||||
f.set(WindowFlags::DECORATIONS, decorations)
|
f.set(WindowFlags::DECORATIONS, decorations)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -516,7 +612,7 @@ impl Window {
|
||||||
let window_state = Arc::clone(&self.window_state);
|
let window_state = Arc::clone(&self.window_state);
|
||||||
|
|
||||||
self.thread_executor.execute_in_thread(move || {
|
self.thread_executor.execute_in_thread(move || {
|
||||||
WindowState::set_window_flags(window_state.lock(), window.0, None, |f| {
|
WindowState::set_window_flags(window_state.lock(), window.0, |f| {
|
||||||
f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top)
|
f.set(WindowFlags::ALWAYS_ON_TOP, always_on_top)
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -769,9 +865,7 @@ unsafe fn init<T: 'static>(
|
||||||
let window_state = {
|
let window_state = {
|
||||||
let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor);
|
let window_state = WindowState::new(&attributes, window_icon, taskbar_icon, dpi_factor);
|
||||||
let window_state = Arc::new(Mutex::new(window_state));
|
let window_state = Arc::new(Mutex::new(window_state));
|
||||||
WindowState::set_window_flags(window_state.lock(), real_window.0, None, |f| {
|
WindowState::set_window_flags(window_state.lock(), real_window.0, |f| *f = window_flags);
|
||||||
*f = window_flags
|
|
||||||
});
|
|
||||||
window_state
|
window_state
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -865,7 +959,7 @@ pub fn com_initialized() {
|
||||||
// is activated. If the window is not fullscreen, the Shell falls back to
|
// is activated. If the window is not fullscreen, the Shell falls back to
|
||||||
// heuristics to determine how the window should be treated, which means
|
// heuristics to determine how the window should be treated, which means
|
||||||
// that it could still consider the window as fullscreen. :(
|
// that it could still consider the window as fullscreen. :(
|
||||||
unsafe fn mark_fullscreen(handle: HWND, fullscreen: bool) {
|
unsafe fn taskbar_mark_fullscreen(handle: HWND, fullscreen: bool) {
|
||||||
com_initialized();
|
com_initialized();
|
||||||
|
|
||||||
TASKBAR_LIST.with(|task_bar_list_ptr| {
|
TASKBAR_LIST.with(|task_bar_list_ptr| {
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::LogicalSize,
|
dpi::LogicalSize,
|
||||||
monitor::MonitorHandle,
|
|
||||||
platform_impl::platform::{event_loop, icon::WinIcon, util},
|
platform_impl::platform::{event_loop, icon::WinIcon, util},
|
||||||
window::{CursorIcon, WindowAttributes},
|
window::{CursorIcon, Fullscreen, WindowAttributes},
|
||||||
};
|
};
|
||||||
use parking_lot::MutexGuard;
|
use parking_lot::MutexGuard;
|
||||||
use std::{io, ptr};
|
use std::{io, ptr};
|
||||||
|
@ -29,7 +28,7 @@ pub struct WindowState {
|
||||||
pub saved_window: Option<SavedWindow>,
|
pub saved_window: Option<SavedWindow>,
|
||||||
pub dpi_factor: f64,
|
pub dpi_factor: f64,
|
||||||
|
|
||||||
pub fullscreen: Option<MonitorHandle>,
|
pub fullscreen: Option<Fullscreen>,
|
||||||
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
|
/// Used to supress duplicate redraw attempts when calling `request_redraw` multiple
|
||||||
/// times in `EventsCleared`.
|
/// times in `EventsCleared`.
|
||||||
pub queued_out_of_band_redraw: bool,
|
pub queued_out_of_band_redraw: bool,
|
||||||
|
@ -84,6 +83,7 @@ bitflags! {
|
||||||
WindowFlags::RESIZABLE.bits |
|
WindowFlags::RESIZABLE.bits |
|
||||||
WindowFlags::MAXIMIZED.bits
|
WindowFlags::MAXIMIZED.bits
|
||||||
);
|
);
|
||||||
|
const FULLSCREEN_OR_MASK = WindowFlags::ALWAYS_ON_TOP.bits;
|
||||||
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
|
const NO_DECORATIONS_AND_MASK = !WindowFlags::RESIZABLE.bits;
|
||||||
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
|
const INVISIBLE_AND_MASK = !WindowFlags::MAXIMIZED.bits;
|
||||||
}
|
}
|
||||||
|
@ -122,32 +122,16 @@ impl WindowState {
|
||||||
self.window_flags
|
self.window_flags
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_window_flags<F>(
|
pub fn set_window_flags<F>(mut this: MutexGuard<'_, Self>, window: HWND, f: F)
|
||||||
mut this: MutexGuard<'_, Self>,
|
where
|
||||||
window: HWND,
|
|
||||||
set_client_rect: Option<RECT>,
|
|
||||||
f: F,
|
|
||||||
) where
|
|
||||||
F: FnOnce(&mut WindowFlags),
|
F: FnOnce(&mut WindowFlags),
|
||||||
{
|
{
|
||||||
let old_flags = this.window_flags;
|
let old_flags = this.window_flags;
|
||||||
f(&mut this.window_flags);
|
f(&mut this.window_flags);
|
||||||
|
|
||||||
let is_fullscreen = this.fullscreen.is_some();
|
|
||||||
this.window_flags
|
|
||||||
.set(WindowFlags::MARKER_FULLSCREEN, is_fullscreen);
|
|
||||||
let new_flags = this.window_flags;
|
let new_flags = this.window_flags;
|
||||||
|
|
||||||
drop(this);
|
drop(this);
|
||||||
old_flags.apply_diff(window, new_flags, set_client_rect);
|
old_flags.apply_diff(window, new_flags);
|
||||||
}
|
|
||||||
|
|
||||||
pub fn refresh_window_state(
|
|
||||||
this: MutexGuard<'_, Self>,
|
|
||||||
window: HWND,
|
|
||||||
set_client_rect: Option<RECT>,
|
|
||||||
) {
|
|
||||||
Self::set_window_flags(this, window, set_client_rect, |_| ());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_window_flags_in_place<F>(&mut self, f: F)
|
pub fn set_window_flags_in_place<F>(&mut self, f: F)
|
||||||
|
@ -185,6 +169,7 @@ impl WindowFlags {
|
||||||
fn mask(mut self) -> WindowFlags {
|
fn mask(mut self) -> WindowFlags {
|
||||||
if self.contains(WindowFlags::MARKER_FULLSCREEN) {
|
if self.contains(WindowFlags::MARKER_FULLSCREEN) {
|
||||||
self &= WindowFlags::FULLSCREEN_AND_MASK;
|
self &= WindowFlags::FULLSCREEN_AND_MASK;
|
||||||
|
self |= WindowFlags::FULLSCREEN_OR_MASK;
|
||||||
}
|
}
|
||||||
if !self.contains(WindowFlags::VISIBLE) {
|
if !self.contains(WindowFlags::VISIBLE) {
|
||||||
self &= WindowFlags::INVISIBLE_AND_MASK;
|
self &= WindowFlags::INVISIBLE_AND_MASK;
|
||||||
|
@ -236,7 +221,7 @@ impl WindowFlags {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adjust the window client rectangle to the return value, if present.
|
/// Adjust the window client rectangle to the return value, if present.
|
||||||
fn apply_diff(mut self, window: HWND, mut new: WindowFlags, set_client_rect: Option<RECT>) {
|
fn apply_diff(mut self, window: HWND, mut new: WindowFlags) {
|
||||||
self = self.mask();
|
self = self.mask();
|
||||||
new = new.mask();
|
new = new.mask();
|
||||||
|
|
||||||
|
@ -295,45 +280,20 @@ impl WindowFlags {
|
||||||
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
|
winuser::SetWindowLongW(window, winuser::GWL_STYLE, style as _);
|
||||||
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
|
winuser::SetWindowLongW(window, winuser::GWL_EXSTYLE, style_ex as _);
|
||||||
|
|
||||||
match set_client_rect
|
let mut flags = winuser::SWP_NOZORDER
|
||||||
.and_then(|r| util::adjust_window_rect_with_styles(window, style, style_ex, r))
|
| winuser::SWP_NOMOVE
|
||||||
{
|
| winuser::SWP_NOSIZE
|
||||||
Some(client_rect) => {
|
| winuser::SWP_FRAMECHANGED;
|
||||||
let (x, y, w, h) = (
|
|
||||||
client_rect.left,
|
// We generally don't want style changes here to affect window
|
||||||
client_rect.top,
|
// focus, but for fullscreen windows they must be activated
|
||||||
client_rect.right - client_rect.left,
|
// (i.e. focused) so that they appear on top of the taskbar
|
||||||
client_rect.bottom - client_rect.top,
|
if !new.contains(WindowFlags::MARKER_FULLSCREEN) {
|
||||||
);
|
flags |= winuser::SWP_NOACTIVATE;
|
||||||
winuser::SetWindowPos(
|
|
||||||
window,
|
|
||||||
ptr::null_mut(),
|
|
||||||
x,
|
|
||||||
y,
|
|
||||||
w,
|
|
||||||
h,
|
|
||||||
winuser::SWP_NOZORDER
|
|
||||||
| winuser::SWP_FRAMECHANGED
|
|
||||||
| winuser::SWP_NOACTIVATE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
// Refresh the window frame.
|
|
||||||
winuser::SetWindowPos(
|
|
||||||
window,
|
|
||||||
ptr::null_mut(),
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
winuser::SWP_NOZORDER
|
|
||||||
| winuser::SWP_NOMOVE
|
|
||||||
| winuser::SWP_NOSIZE
|
|
||||||
| winuser::SWP_FRAMECHANGED
|
|
||||||
| winuser::SWP_NOACTIVATE,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Refresh the window frame
|
||||||
|
winuser::SetWindowPos(window, ptr::null_mut(), 0, 0, 0, 0, flags);
|
||||||
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
|
winuser::SendMessageW(window, *event_loop::SET_RETAIN_STATE_ON_SIZE_MSG_ID, 0, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
dpi::{LogicalPosition, LogicalSize},
|
||||||
error::{ExternalError, NotSupportedError, OsError},
|
error::{ExternalError, NotSupportedError, OsError},
|
||||||
event_loop::EventLoopWindowTarget,
|
event_loop::EventLoopWindowTarget,
|
||||||
monitor::{AvailableMonitorsIter, MonitorHandle},
|
monitor::{AvailableMonitorsIter, MonitorHandle, VideoMode},
|
||||||
platform_impl,
|
platform_impl,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -45,6 +45,18 @@ impl fmt::Debug for Window {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Drop for Window {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// If the window is in exclusive fullscreen, we must restore the desktop
|
||||||
|
// video mode (generally this would be done on application exit, but
|
||||||
|
// closing the window doesn't necessarily always mean application exit,
|
||||||
|
// such as when there are multiple windows)
|
||||||
|
if let Some(Fullscreen::Exclusive(_)) = self.fullscreen() {
|
||||||
|
self.set_fullscreen(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Identifier of a window. Unique for each window.
|
/// Identifier of a window. Unique for each window.
|
||||||
///
|
///
|
||||||
/// Can be obtained with `window.id()`.
|
/// Can be obtained with `window.id()`.
|
||||||
|
@ -110,7 +122,7 @@ pub struct WindowAttributes {
|
||||||
/// Whether the window should be set as fullscreen upon creation.
|
/// Whether the window should be set as fullscreen upon creation.
|
||||||
///
|
///
|
||||||
/// The default is `None`.
|
/// The default is `None`.
|
||||||
pub fullscreen: Option<MonitorHandle>,
|
pub fullscreen: Option<Fullscreen>,
|
||||||
|
|
||||||
/// The title of the window in the title bar.
|
/// The title of the window in the title bar.
|
||||||
///
|
///
|
||||||
|
@ -222,14 +234,14 @@ impl WindowBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Sets the window fullscreen state. None means a normal window, Some(MonitorHandle)
|
/// Sets the window fullscreen state. None means a normal window, Some(Fullscreen)
|
||||||
/// means a fullscreen window on that specific monitor
|
/// means a fullscreen window on that specific monitor
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
/// - **Windows:** Screen saver is disabled in fullscreen mode.
|
/// - **Windows:** Screen saver is disabled in fullscreen mode.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn with_fullscreen(mut self, monitor: Option<MonitorHandle>) -> WindowBuilder {
|
pub fn with_fullscreen(mut self, monitor: Option<Fullscreen>) -> WindowBuilder {
|
||||||
self.window.fullscreen = monitor;
|
self.window.fullscreen = monitor;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -295,7 +307,6 @@ impl WindowBuilder {
|
||||||
self,
|
self,
|
||||||
window_target: &EventLoopWindowTarget<T>,
|
window_target: &EventLoopWindowTarget<T>,
|
||||||
) -> Result<Window, OsError> {
|
) -> Result<Window, OsError> {
|
||||||
// building
|
|
||||||
platform_impl::Window::new(&window_target.p, self.window, self.platform_specific)
|
platform_impl::Window::new(&window_target.p, self.window, self.platform_specific)
|
||||||
.map(|window| Window { window })
|
.map(|window| Window { window })
|
||||||
}
|
}
|
||||||
|
@ -537,11 +548,27 @@ impl Window {
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
|
/// - **macOS:** `Fullscreen::Exclusive` provides true exclusive mode with a
|
||||||
|
/// video mode change. *Caveat!* macOS doesn't provide task switching (or
|
||||||
|
/// spaces!) while in exclusive fullscreen mode. This mode should be used
|
||||||
|
/// when a video mode change is desired, but for a better user experience,
|
||||||
|
/// borderless fullscreen might be preferred.
|
||||||
|
///
|
||||||
|
/// `Fullscreen::Borderless` provides a borderless fullscreen window on a
|
||||||
|
/// separate space. This is the idiomatic way for fullscreen games to work
|
||||||
|
/// on macOS. See [`WindowExtMacOs::set_simple_fullscreen`][simple] if
|
||||||
|
/// separate spaces are not preferred.
|
||||||
|
///
|
||||||
|
/// The dock and the menu bar are always disabled in fullscreen mode.
|
||||||
/// - **iOS:** Can only be called on the main thread.
|
/// - **iOS:** Can only be called on the main thread.
|
||||||
|
/// - **Wayland:** Does not support exclusive fullscreen mode.
|
||||||
/// - **Windows:** Screen saver is disabled in fullscreen mode.
|
/// - **Windows:** Screen saver is disabled in fullscreen mode.
|
||||||
|
///
|
||||||
|
/// [simple]:
|
||||||
|
/// ../platform/macos/trait.WindowExtMacOS.html#tymethod.set_simple_fullscreen
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn set_fullscreen(&self, monitor: Option<MonitorHandle>) {
|
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
|
||||||
self.window.set_fullscreen(monitor)
|
self.window.set_fullscreen(fullscreen)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gets the window's current fullscreen state.
|
/// Gets the window's current fullscreen state.
|
||||||
|
@ -550,7 +577,7 @@ impl Window {
|
||||||
///
|
///
|
||||||
/// - **iOS:** Can only be called on the main thread.
|
/// - **iOS:** Can only be called on the main thread.
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn fullscreen(&self) -> Option<MonitorHandle> {
|
pub fn fullscreen(&self) -> Option<Fullscreen> {
|
||||||
self.window.fullscreen()
|
self.window.fullscreen()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -759,3 +786,9 @@ impl Default for CursorIcon {
|
||||||
CursorIcon::Default
|
CursorIcon::Default
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
pub enum Fullscreen {
|
||||||
|
Exclusive(VideoMode),
|
||||||
|
Borderless(MonitorHandle),
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue