Skip to content

Commit 5bc3cf1

Browse files
Aleksi JuvaniOsspial
Aleksi Juvani
authored andcommitted
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
1 parent 131e67d commit 5bc3cf1

31 files changed

+1454
-607
lines changed

CHANGELOG.md

+6
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
# Unreleased
2+
23
- On macOS, drop the run closure on exit.
34
- On Windows, location of `WindowEvent::Touch` are window client coordinates instead of screen coordinates.
45
- On X11, fix delayed events after window redraw.
56
- On macOS, add `WindowBuilderExt::with_disallow_hidpi` to have the option to turn off best resolution openGL surface.
67
- On Windows, screen saver won't start if the window is in fullscreen mode.
78
- Change all occurrences of the `new_user_event` method to `with_user_event`.
9+
- On macOS, the dock and the menu bar are now hidden in fullscreen mode.
10+
- `Window::set_fullscreen` now takes `Option<Fullscreen>` where `Fullscreen`
11+
consists of `Fullscreen::Exclusive(VideoMode)` and
12+
`Fullscreen::Borderless(MonitorHandle)` variants.
13+
- Adds support for exclusive fullscreen mode.
814

915
# 0.20.0 Alpha 2 (2019-07-09)
1016

FEATURES.md

+4
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ If your PR makes notable changes to Winit's features, please update this section
8484
- **Fullscreen**: The windows created by winit can be put into fullscreen mode.
8585
- **Fullscreen toggle**: The windows created by winit can be switched to and from fullscreen after
8686
creation.
87+
- **Exclusive fullscreen**: Winit allows changing the video mode of the monitor
88+
for fullscreen windows, and if applicable, captures the monitor for exclusive
89+
use by this application.
8790
- **HiDPI support**: Winit assists developers in appropriately scaling HiDPI content.
8891
- **Popup / modal windows**: Windows can be created relative to the client area of other windows, and parent
8992
windows can be disabled in favor of popup windows. This feature also guarantees that popup windows
@@ -157,6 +160,7 @@ Legend:
157160
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
158161
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**||
159162
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**||
163+
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** ||||
160164
|HiDPI support |✔️ |✔️ |✔️ |✔️ |[#721]|✔️ |✔️ |
161165
|Popup windows ||||||||
162166

examples/fullscreen.rs

+42-63
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,35 @@
1-
use std::io::{self, Write};
2-
use winit::{
3-
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
4-
event_loop::{ControlFlow, EventLoop},
5-
monitor::MonitorHandle,
6-
window::WindowBuilder,
7-
};
1+
use std::io::{stdin, stdout, Write};
2+
use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent};
3+
use winit::event_loop::{ControlFlow, EventLoop};
4+
use winit::monitor::{MonitorHandle, VideoMode};
5+
use winit::window::{Fullscreen, WindowBuilder};
86

97
fn main() {
108
let event_loop = EventLoop::new();
119

12-
#[cfg(target_os = "macos")]
13-
let mut macos_use_simple_fullscreen = false;
14-
15-
let monitor = {
16-
// On macOS there are two fullscreen modes "native" and "simple"
17-
#[cfg(target_os = "macos")]
18-
{
19-
print!("Please choose the fullscreen mode: (1) native, (2) simple: ");
20-
io::stdout().flush().unwrap();
21-
22-
let mut num = String::new();
23-
io::stdin().read_line(&mut num).unwrap();
24-
let num = num.trim().parse().ok().expect("Please enter a number");
25-
match num {
26-
2 => macos_use_simple_fullscreen = true,
27-
_ => {}
28-
}
29-
30-
// Prompt for monitor when using native fullscreen
31-
if !macos_use_simple_fullscreen {
32-
Some(prompt_for_monitor(&event_loop))
33-
} else {
34-
None
35-
}
36-
}
10+
print!("Please choose the fullscreen mode: (1) exclusive, (2) borderless: ");
11+
stdout().flush().unwrap();
3712

38-
#[cfg(not(target_os = "macos"))]
39-
Some(prompt_for_monitor(&event_loop))
40-
};
13+
let mut num = String::new();
14+
stdin().read_line(&mut num).unwrap();
15+
let num = num.trim().parse().ok().expect("Please enter a number");
16+
17+
let fullscreen = Some(match num {
18+
1 => Fullscreen::Exclusive(prompt_for_video_mode(&prompt_for_monitor(&event_loop))),
19+
2 => Fullscreen::Borderless(prompt_for_monitor(&event_loop)),
20+
_ => panic!("Please enter a valid number"),
21+
});
4122

42-
let mut is_fullscreen = monitor.is_some();
4323
let mut is_maximized = false;
4424
let mut decorations = true;
4525

4626
let window = WindowBuilder::new()
4727
.with_title("Hello world!")
48-
.with_fullscreen(monitor)
28+
.with_fullscreen(fullscreen.clone())
4929
.build(&event_loop)
5030
.unwrap();
5131

5232
event_loop.run(move |event, _, control_flow| {
53-
println!("{:?}", event);
5433
*control_flow = ControlFlow::Wait;
5534

5635
match event {
@@ -67,35 +46,14 @@ fn main() {
6746
} => match (virtual_code, state) {
6847
(VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit,
6948
(VirtualKeyCode::F, ElementState::Pressed) => {
70-
#[cfg(target_os = "macos")]
71-
{
72-
if macos_use_simple_fullscreen {
73-
use winit::platform::macos::WindowExtMacOS;
74-
if WindowExtMacOS::set_simple_fullscreen(&window, !is_fullscreen) {
75-
is_fullscreen = !is_fullscreen;
76-
}
77-
return;
78-
}
79-
}
80-
81-
is_fullscreen = !is_fullscreen;
82-
if !is_fullscreen {
49+
if window.fullscreen().is_some() {
8350
window.set_fullscreen(None);
8451
} else {
85-
window.set_fullscreen(Some(window.current_monitor()));
52+
window.set_fullscreen(fullscreen.clone());
8653
}
8754
}
8855
(VirtualKeyCode::S, ElementState::Pressed) => {
8956
println!("window.fullscreen {:?}", window.fullscreen());
90-
91-
#[cfg(target_os = "macos")]
92-
{
93-
use winit::platform::macos::WindowExtMacOS;
94-
println!(
95-
"window.simple_fullscreen {:?}",
96-
WindowExtMacOS::simple_fullscreen(&window)
97-
);
98-
}
9957
}
10058
(VirtualKeyCode::M, ElementState::Pressed) => {
10159
is_maximized = !is_maximized;
@@ -121,10 +79,10 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
12179
}
12280

12381
print!("Please write the number of the monitor to use: ");
124-
io::stdout().flush().unwrap();
82+
stdout().flush().unwrap();
12583

12684
let mut num = String::new();
127-
io::stdin().read_line(&mut num).unwrap();
85+
stdin().read_line(&mut num).unwrap();
12886
let num = num.trim().parse().ok().expect("Please enter a number");
12987
let monitor = event_loop
13088
.available_monitors()
@@ -135,3 +93,24 @@ fn prompt_for_monitor(event_loop: &EventLoop<()>) -> MonitorHandle {
13593

13694
monitor
13795
}
96+
97+
fn prompt_for_video_mode(monitor: &MonitorHandle) -> VideoMode {
98+
for (i, video_mode) in monitor.video_modes().enumerate() {
99+
println!("Video mode #{}: {}", i, video_mode);
100+
}
101+
102+
print!("Please write the number of the video mode to use: ");
103+
stdout().flush().unwrap();
104+
105+
let mut num = String::new();
106+
stdin().read_line(&mut num).unwrap();
107+
let num = num.trim().parse().ok().expect("Please enter a number");
108+
let video_mode = monitor
109+
.video_modes()
110+
.nth(num)
111+
.expect("Please enter a valid ID");
112+
113+
println!("Using {}", video_mode);
114+
115+
video_mode
116+
}

examples/multithreaded.rs

+46-4
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use std::{collections::HashMap, sync::mpsc, thread, time::Duration};
44
use winit::{
55
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
66
event_loop::{ControlFlow, EventLoop},
7-
window::{CursorIcon, WindowBuilder},
7+
window::{CursorIcon, Fullscreen, WindowBuilder},
88
};
99

1010
const WINDOW_COUNT: usize = 3;
@@ -19,11 +19,34 @@ fn main() {
1919
.with_inner_size(WINDOW_SIZE.into())
2020
.build(&event_loop)
2121
.unwrap();
22+
23+
let mut video_modes: Vec<_> = window.current_monitor().video_modes().collect();
24+
let mut video_mode_id = 0usize;
25+
2226
let (tx, rx) = mpsc::channel();
2327
window_senders.insert(window.id(), tx);
2428
thread::spawn(move || {
2529
while let Ok(event) = rx.recv() {
2630
match event {
31+
WindowEvent::Moved { .. } => {
32+
// We need to update our chosen video mode if the window
33+
// was moved to an another monitor, so that the window
34+
// appears on this monitor instead when we go fullscreen
35+
let previous_video_mode = video_modes.iter().cloned().nth(video_mode_id);
36+
video_modes = window.current_monitor().video_modes().collect();
37+
video_mode_id = video_mode_id.min(video_modes.len());
38+
let video_mode = video_modes.iter().nth(video_mode_id);
39+
40+
// Different monitors may support different video modes,
41+
// and the index we chose previously may now point to a
42+
// completely different video mode, so notify the user
43+
if video_mode != previous_video_mode.as_ref() {
44+
println!(
45+
"Window moved to another monitor, picked video mode: {}",
46+
video_modes.iter().nth(video_mode_id).unwrap()
47+
);
48+
}
49+
}
2750
WindowEvent::KeyboardInput {
2851
input:
2952
KeyboardInput {
@@ -44,9 +67,26 @@ fn main() {
4467
false => CursorIcon::Default,
4568
}),
4669
D => window.set_decorations(!state),
47-
F => window.set_fullscreen(match state {
48-
true => Some(window.current_monitor()),
49-
false => None,
70+
// Cycle through video modes
71+
Right | Left => {
72+
video_mode_id = match key {
73+
Left => video_mode_id.saturating_sub(1),
74+
Right => (video_modes.len() - 1).min(video_mode_id + 1),
75+
_ => unreachable!(),
76+
};
77+
println!(
78+
"Picking video mode: {}",
79+
video_modes.iter().nth(video_mode_id).unwrap()
80+
);
81+
}
82+
F => window.set_fullscreen(match (state, modifiers.alt) {
83+
(true, false) => {
84+
Some(Fullscreen::Borderless(window.current_monitor()))
85+
}
86+
(true, true) => Some(Fullscreen::Exclusive(
87+
video_modes.iter().nth(video_mode_id).unwrap().clone(),
88+
)),
89+
(false, _) => None,
5090
}),
5191
G => window.set_cursor_grab(state).unwrap(),
5292
H => window.set_cursor_visible(!state),
@@ -56,6 +96,7 @@ fn main() {
5696
println!("-> inner_position : {:?}", window.inner_position());
5797
println!("-> outer_size : {:?}", window.outer_size());
5898
println!("-> inner_size : {:?}", window.inner_size());
99+
println!("-> fullscreen : {:?}", window.fullscreen());
59100
}
60101
L => window.set_min_inner_size(match state {
61102
true => Some(WINDOW_SIZE.into()),
@@ -108,6 +149,7 @@ fn main() {
108149
| WindowEvent::KeyboardInput {
109150
input:
110151
KeyboardInput {
152+
state: ElementState::Released,
111153
virtual_keycode: Some(VirtualKeyCode::Escape),
112154
..
113155
},

examples/video_modes.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ fn main() {
77
println!("Listing available video modes:");
88

99
for mode in monitor.video_modes() {
10-
println!("{:?}", mode);
10+
println!("{}", mode);
1111
}
1212
}

src/lib.rs

-1
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ extern crate log;
121121
#[macro_use]
122122
extern crate serde;
123123
#[macro_use]
124-
#[cfg(target_os = "windows")]
125124
extern crate derivative;
126125
#[macro_use]
127126
#[cfg(target_os = "windows")]

src/monitor.rs

+54-8
Original file line numberDiff line numberDiff line change
@@ -52,17 +52,41 @@ impl Iterator for AvailableMonitorsIter {
5252
/// - [`MonitorHandle::video_modes`][monitor_get].
5353
///
5454
/// [monitor_get]: ../monitor/struct.MonitorHandle.html#method.video_modes
55-
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
55+
#[derive(Derivative)]
56+
#[derivative(Clone, Debug = "transparent", PartialEq, Eq, Hash)]
5657
pub struct VideoMode {
57-
pub(crate) size: (u32, u32),
58-
pub(crate) bit_depth: u16,
59-
pub(crate) refresh_rate: u16,
58+
pub(crate) video_mode: platform_impl::VideoMode,
59+
}
60+
61+
impl PartialOrd for VideoMode {
62+
fn partial_cmp(&self, other: &VideoMode) -> Option<std::cmp::Ordering> {
63+
Some(self.cmp(other))
64+
}
65+
}
66+
67+
impl Ord for VideoMode {
68+
fn cmp(&self, other: &VideoMode) -> std::cmp::Ordering {
69+
// TODO: we can impl `Ord` for `PhysicalSize` once we switch from `f32`
70+
// to `u32` there
71+
let size: (u32, u32) = self.size().into();
72+
let other_size: (u32, u32) = other.size().into();
73+
self.monitor().cmp(&other.monitor()).then(
74+
size.cmp(&other_size)
75+
.then(
76+
self.refresh_rate()
77+
.cmp(&other.refresh_rate())
78+
.then(self.bit_depth().cmp(&other.bit_depth())),
79+
)
80+
.reverse(),
81+
)
82+
}
6083
}
6184

6285
impl VideoMode {
6386
/// Returns the resolution of this video mode.
87+
#[inline]
6488
pub fn size(&self) -> PhysicalSize {
65-
self.size.into()
89+
self.video_mode.size()
6690
}
6791

6892
/// Returns the bit depth of this video mode, as in how many bits you have
@@ -73,15 +97,37 @@ impl VideoMode {
7397
///
7498
/// - **Wayland:** Always returns 32.
7599
/// - **iOS:** Always returns 32.
100+
#[inline]
76101
pub fn bit_depth(&self) -> u16 {
77-
self.bit_depth
102+
self.video_mode.bit_depth()
78103
}
79104

80105
/// Returns the refresh rate of this video mode. **Note**: the returned
81106
/// refresh rate is an integer approximation, and you shouldn't rely on this
82107
/// value to be exact.
108+
#[inline]
83109
pub fn refresh_rate(&self) -> u16 {
84-
self.refresh_rate
110+
self.video_mode.refresh_rate()
111+
}
112+
113+
/// Returns the monitor that this video mode is valid for. Each monitor has
114+
/// a separate set of valid video modes.
115+
#[inline]
116+
pub fn monitor(&self) -> MonitorHandle {
117+
self.video_mode.monitor()
118+
}
119+
}
120+
121+
impl std::fmt::Display for VideoMode {
122+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
123+
write!(
124+
f,
125+
"{}x{} @ {} Hz ({} bpp)",
126+
self.size().width,
127+
self.size().height,
128+
self.refresh_rate(),
129+
self.bit_depth()
130+
)
85131
}
86132
}
87133

@@ -90,7 +136,7 @@ impl VideoMode {
90136
/// Allows you to retrieve information about a given monitor and can be used in [`Window`] creation.
91137
///
92138
/// [`Window`]: ../window/struct.Window.html
93-
#[derive(Debug, Clone)]
139+
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
94140
pub struct MonitorHandle {
95141
pub(crate) inner: platform_impl::MonitorHandle,
96142
}

src/platform_impl/ios/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ use std::fmt;
7979

8080
pub use self::{
8181
event_loop::{EventLoop, EventLoopProxy, EventLoopWindowTarget},
82-
monitor::MonitorHandle,
82+
monitor::{MonitorHandle, VideoMode},
8383
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
8484
};
8585

0 commit comments

Comments
 (0)