Skip to content

Commit

Permalink
Merge branch 'master' into improved-fullscreen-monitor-switching
Browse files Browse the repository at this point in the history
  • Loading branch information
Osspial authored Sep 19, 2019
2 parents fcf513f + 2ef3965 commit 7914b89
Show file tree
Hide file tree
Showing 8 changed files with 105 additions and 59 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@
- On iOS, fix null window on initial `HiDpiFactorChanged` event.
- On Windows, disable monitor change keyboard shortcut while in exclusive fullscreen.
- On Windows, ensure that changing a borderless fullscreen window's monitor via keyboard shortcuts keeps the window fullscreen on the new monitor.
- On Windows, fix fullscreen window shrinking upon getting restored to a normal window.
- On macOS, fix events not being emitted during modal loops, such as when windows are being resized
by the user.
- On Windows, fix hovering the mouse over the active window creating an endless stream of CursorMoved events.
- On X11, prevent stealing input focus when creating a new window.
Only steal input focus when entering fullscreen mode.

# 0.20.0 Alpha 3 (2019-08-14)

Expand Down
79 changes: 54 additions & 25 deletions src/platform_impl/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ pub struct SharedState {
pub frame_extents: Option<util::FrameExtentsHeuristic>,
pub min_inner_size: Option<LogicalSize>,
pub max_inner_size: Option<LogicalSize>,
pub is_visible: bool,
}

impl SharedState {
fn new(dpi_factor: f64) -> Mutex<Self> {
fn new(dpi_factor: f64, is_visible: bool) -> Mutex<Self> {
let mut shared_state = SharedState::default();
shared_state.guessed_dpi = Some(dpi_factor);
shared_state.is_visible = is_visible;
Mutex::new(shared_state)
}
}
Expand Down Expand Up @@ -230,7 +232,7 @@ impl UnownedWindow {
cursor_grabbed: Mutex::new(false),
cursor_visible: Mutex::new(true),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
shared_state: SharedState::new(dpi_factor),
shared_state: SharedState::new(dpi_factor, window_attrs.visible),
pending_redraws: event_loop.pending_redraws.clone(),
};

Expand Down Expand Up @@ -355,6 +357,8 @@ impl UnownedWindow {
unsafe {
(xconn.xlib.XMapRaised)(xconn.display, window.xwindow);
} //.queue();

window.wait_for_visibility_notify();
}

// Attempt to make keyboard input repeat detectable
Expand Down Expand Up @@ -420,27 +424,6 @@ impl UnownedWindow {
.set_always_on_top_inner(window_attrs.always_on_top)
.queue();
}

if window_attrs.visible {
unsafe {
// XSetInputFocus generates an error if the window is not visible, so we wait
// until we receive VisibilityNotify.
let mut event = MaybeUninit::uninit();
(xconn.xlib.XIfEvent)(
// This will flush the request buffer IF it blocks.
xconn.display,
event.as_mut_ptr(),
Some(visibility_predicate),
window.xwindow as _,
);
(xconn.xlib.XSetInputFocus)(
xconn.display,
window.xwindow,
ffi::RevertToParent,
ffi::CurrentTime,
);
}
}
}

// We never want to give the user a broken window, since by then, it's too late to handle.
Expand Down Expand Up @@ -566,11 +549,32 @@ impl UnownedWindow {
fn set_fullscreen_hint(&self, fullscreen: bool) -> util::Flusher<'_> {
let fullscreen_atom =
unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_FULLSCREEN\0") };
self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0))
let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0));

if fullscreen {
// Ensure that the fullscreen window receives input focus to prevent
// locking up the user's display.
unsafe {
(self.xconn.xlib.XSetInputFocus)(
self.xconn.display,
self.xwindow,
ffi::RevertToParent,
ffi::CurrentTime,
);
}
}

flusher
}

fn set_fullscreen_inner(&self, fullscreen: Option<Fullscreen>) -> Option<util::Flusher<'_>> {
let mut shared_state_lock = self.shared_state.lock();

if !shared_state_lock.is_visible {
// Setting fullscreen on a window that is not visible will generate an error.
return None;
}

let old_fullscreen = shared_state_lock.fullscreen.clone();
if old_fullscreen == fullscreen {
return None;
Expand Down Expand Up @@ -681,7 +685,7 @@ impl UnownedWindow {
pub fn set_fullscreen(&self, fullscreen: Option<Fullscreen>) {
if let Some(flusher) = self.set_fullscreen_inner(fullscreen) {
flusher
.flush()
.sync()
.expect("Failed to change window fullscreen state");
self.invalidate_cached_frame_extents();
}
Expand Down Expand Up @@ -837,12 +841,22 @@ impl UnownedWindow {

#[inline]
pub fn set_visible(&self, visible: bool) {
let is_visible = self.shared_state.lock().is_visible;

if visible == is_visible {
return;
}

match visible {
true => unsafe {
(self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow);
self.xconn
.flush_requests()
.expect("Failed to call XMapRaised");

// Some X requests may generate an error if the window is not
// visible, so we must wait until the window becomes visible.
self.wait_for_visibility_notify();
},
false => unsafe {
(self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow);
Expand All @@ -851,6 +865,21 @@ impl UnownedWindow {
.expect("Failed to call XUnmapWindow");
},
}

self.shared_state.lock().is_visible = visible;
}

fn wait_for_visibility_notify(&self) {
unsafe {
let mut event = MaybeUninit::uninit();

(self.xconn.xlib.XIfEvent)(
self.xconn.display,
event.as_mut_ptr(),
Some(visibility_predicate),
self.xwindow as _,
);
}
}

fn update_cached_frame_extents(&self) {
Expand Down
23 changes: 0 additions & 23 deletions src/platform_impl/macos/app_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ struct Handler {
start_time: Mutex<Option<Instant>>,
callback: Mutex<Option<Box<dyn EventHandler>>>,
pending_events: Mutex<VecDeque<Event<Never>>>,
deferred_events: Mutex<VecDeque<Event<Never>>>,
pending_redraw: Mutex<Vec<WindowId>>,
waker: Mutex<EventLoopWaker>,
}
Expand All @@ -97,10 +96,6 @@ impl Handler {
self.pending_events.lock().unwrap()
}

fn deferred<'a>(&'a self) -> MutexGuard<'a, VecDeque<Event<Never>>> {
self.deferred_events.lock().unwrap()
}

fn redraw<'a>(&'a self) -> MutexGuard<'a, Vec<WindowId>> {
self.pending_redraw.lock().unwrap()
}
Expand Down Expand Up @@ -145,10 +140,6 @@ impl Handler {
mem::replace(&mut *self.events(), Default::default())
}

fn take_deferred(&self) -> VecDeque<Event<Never>> {
mem::replace(&mut *self.deferred(), Default::default())
}

fn should_redraw(&self) -> Vec<WindowId> {
mem::replace(&mut *self.redraw(), Default::default())
}
Expand Down Expand Up @@ -264,20 +255,6 @@ impl AppState {
HANDLER.events().append(&mut events);
}

pub fn send_event_immediately(event: Event<Never>) {
if !unsafe { msg_send![class!(NSThread), isMainThread] } {
panic!("Event sent from different thread: {:#?}", event);
}
HANDLER.deferred().push_back(event);
if !HANDLER.get_in_callback() {
HANDLER.set_in_callback(true);
for event in HANDLER.take_deferred() {
HANDLER.handle_nonuser_event(event);
}
HANDLER.set_in_callback(false);
}
}

pub fn cleared() {
if !HANDLER.is_ready() {
return;
Expand Down
3 changes: 1 addition & 2 deletions src/platform_impl/macos/observer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use crate::platform_impl::platform::{app_state::AppState, ffi};

#[link(name = "CoreFoundation", kind = "framework")]
extern "C" {
pub static kCFRunLoopDefaultMode: CFRunLoopMode;
pub static kCFRunLoopCommonModes: CFRunLoopMode;

pub fn CFRunLoopGetMain() -> CFRunLoopRef;
Expand Down Expand Up @@ -162,7 +161,7 @@ impl RunLoop {
handler,
ptr::null_mut(),
);
CFRunLoopAddObserver(self.0, observer, kCFRunLoopDefaultMode);
CFRunLoopAddObserver(self.0, observer, kCFRunLoopCommonModes);
}
}

Expand Down
6 changes: 1 addition & 5 deletions src/platform_impl/macos/window_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,11 +84,7 @@ impl WindowDelegateState {
pub fn emit_resize_event(&mut self) {
let rect = unsafe { NSView::frame(*self.ns_view) };
let size = LogicalSize::new(rect.size.width as f64, rect.size.height as f64);
let event = Event::WindowEvent {
window_id: WindowId(get_window_id(*self.ns_window)),
event: WindowEvent::Resized(size),
};
AppState::send_event_immediately(event);
self.emit_event(WindowEvent::Resized(size));
}

fn emit_move_event(&mut self) {
Expand Down
23 changes: 23 additions & 0 deletions src/platform_impl/windows/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,16 @@ pub fn set_cursor_hidden(hidden: bool) {
}
}

pub fn get_cursor_clip() -> Result<RECT, io::Error> {
unsafe {
let mut rect: RECT = mem::zeroed();
win_to_err(|| winuser::GetClipCursor(&mut rect)).map(|_| rect)
}
}

/// Sets the cursor's clip rect.
///
/// Note that calling this will automatically dispatch a `WM_MOUSEMOVE` event.
pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
unsafe {
let rect_ptr = rect
Expand All @@ -151,6 +161,19 @@ pub fn set_cursor_clip(rect: Option<RECT>) -> Result<(), io::Error> {
}
}

pub fn get_desktop_rect() -> RECT {
unsafe {
let left = winuser::GetSystemMetrics(winuser::SM_XVIRTUALSCREEN);
let top = winuser::GetSystemMetrics(winuser::SM_YVIRTUALSCREEN);
RECT {
left,
top,
right: left + winuser::GetSystemMetrics(winuser::SM_CXVIRTUALSCREEN),
bottom: top + winuser::GetSystemMetrics(winuser::SM_CYVIRTUALSCREEN),
}
}
}

pub fn is_focused(window: HWND) -> bool {
window == unsafe { winuser::GetActiveWindow() }
}
Expand Down
1 change: 1 addition & 0 deletions src/platform_impl/windows/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -599,6 +599,7 @@ impl Window {
{
window_state_lock.dpi_factor = dpi_factor;
drop(window_state_lock);
let client_rect = util::adjust_window_rect(window.0, client_rect).unwrap();

unsafe {
winuser::SetWindowPos(
Expand Down
23 changes: 19 additions & 4 deletions src/platform_impl/windows/window_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,10 +307,25 @@ impl CursorFlags {
let client_rect = util::get_client_rect(window)?;

if util::is_focused(window) {
if self.contains(CursorFlags::GRABBED) {
util::set_cursor_clip(Some(client_rect))?;
} else {
util::set_cursor_clip(None)?;
let cursor_clip = match self.contains(CursorFlags::GRABBED) {
true => Some(client_rect),
false => None,
};

let rect_to_tuple = |rect: RECT| (rect.left, rect.top, rect.right, rect.bottom);
let active_cursor_clip = rect_to_tuple(util::get_cursor_clip()?);
let desktop_rect = rect_to_tuple(util::get_desktop_rect());

let active_cursor_clip = match desktop_rect == active_cursor_clip {
true => None,
false => Some(active_cursor_clip),
};

// We do this check because calling `set_cursor_clip` incessantly will flood the event
// loop with `WM_MOUSEMOVE` events, and `refresh_os_cursor` is called by `set_cursor_flags`
// which at times gets called once every iteration of the eventloop.
if active_cursor_clip != cursor_clip.map(rect_to_tuple) {
util::set_cursor_clip(cursor_clip)?;
}
}

Expand Down

0 comments on commit 7914b89

Please sign in to comment.