diff --git a/CHANGELOG.md b/CHANGELOG.md index 66c011c2c40..7da61c63710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,11 @@ # Unreleased - On macOS, fix not sending ReceivedCharacter event for specific keys combinations. +- On X11, fix `ModifiersChanged` emitting incorrect modifier change events + +# 0.20.0 Alpha 6 (2020-01-03) + +- On macOS, fix `set_cursor_visible` hides cursor outside of window. - On macOS, fix `CursorEntered` and `CursorLeft` events fired at old window size. - On macOS, fix error when `set_fullscreen` is called during fullscreen transition. - On all platforms except mobile and WASM, implement `Window::set_minimized`. @@ -23,6 +28,8 @@ - On Windows, fix `Window::set_visible` not setting internal flags correctly. This resulted in some weird behavior. - Add `DeviceEvent::ModifiersChanged`. - Deprecate `modifiers` fields in other events in favor of `ModifiersChanged`. +- On X11, `WINIT_HIDPI_FACTOR` now dominates `Xft.dpi` when picking DPI factor for output. +- On X11, add special value `randr` for `WINIT_HIDPI_FACTOR` to make winit use self computed DPI factor instead of the one from `Xft.dpi`. # 0.20.0 Alpha 5 (2019-12-09) diff --git a/Cargo.toml b/Cargo.toml index 3f91840943c..1869b1c8098 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "winit" -version = "0.20.0-alpha5" +version = "0.20.0-alpha6" authors = ["The winit contributors", "Pierre Krieger "] description = "Cross-platform window creation library." edition = "2018" diff --git a/README.md b/README.md index 5511a4aa17a..ff5b59020bc 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ ```toml [dependencies] -winit = "0.20.0-alpha5" +winit = "0.20.0-alpha6" ``` ## [Documentation](https://docs.rs/winit) diff --git a/src/platform_impl/linux/x11/util/input.rs b/src/platform_impl/linux/x11/util/input.rs index 5af033d3365..e9f45aee1c9 100644 --- a/src/platform_impl/linux/x11/util/input.rs +++ b/src/platform_impl/linux/x11/util/input.rs @@ -18,9 +18,9 @@ impl ModifiersState { pub(crate) fn from_x11_mask(mask: c_uint) -> Self { let mut m = ModifiersState::empty(); - m.set(ModifiersState::SHIFT, mask & ffi::Mod1Mask != 0); - m.set(ModifiersState::CTRL, mask & ffi::ShiftMask != 0); - m.set(ModifiersState::ALT, mask & ffi::ControlMask != 0); + m.set(ModifiersState::ALT, mask & ffi::Mod1Mask != 0); + m.set(ModifiersState::SHIFT, mask & ffi::ShiftMask != 0); + m.set(ModifiersState::CTRL, mask & ffi::ControlMask != 0); m.set(ModifiersState::LOGO, mask & ffi::Mod4Mask != 0); m } diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index 28fcb601ca2..4630389e8d3 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -6,24 +6,17 @@ use super::{ }; use crate::{dpi::validate_hidpi_factor, platform_impl::platform::x11::VideoMode}; +/// Represents values of `WINIT_HIDPI_FACTOR`. +pub enum EnvVarDPI { + Randr, + Scale(f64), + NotSet, +} + pub fn calc_dpi_factor( (width_px, height_px): (u32, u32), (width_mm, height_mm): (u64, u64), ) -> f64 { - // Override DPI if `WINIT_HIDPI_FACTOR` variable is set - let dpi_override = env::var("WINIT_HIDPI_FACTOR") - .ok() - .and_then(|var| f64::from_str(&var).ok()); - if let Some(dpi_override) = dpi_override { - if !validate_hidpi_factor(dpi_override) { - panic!( - "`WINIT_HIDPI_FACTOR` invalid; DPI factors must be normal floats greater than 0. Got `{}`", - dpi_override, - ); - } - return dpi_override; - } - // See http://xpra.org/trac/ticket/728 for more information. if width_mm == 0 || height_mm == 0 { warn!("XRandR reported that the display's 0mm in size, which is certifiably insane"); @@ -107,16 +100,55 @@ impl XConnection { (*output_info).nameLen as usize, ); let name = String::from_utf8_lossy(name_slice).into(); - let hidpi_factor = if let Some(dpi) = self.get_xft_dpi() { - dpi / 96. - } else { - calc_dpi_factor( + // Override DPI if `WINIT_HIDPI_FACTOR` variable is set + let dpi_env = env::var("WINIT_HIDPI_FACTOR").ok().map_or_else( + || EnvVarDPI::NotSet, + |var| { + if var.to_lowercase() == "randr" { + EnvVarDPI::Randr + } else if let Ok(dpi) = f64::from_str(&var) { + EnvVarDPI::Scale(dpi) + } else if var.is_empty() { + EnvVarDPI::NotSet + } else { + panic!( + "`WINIT_HIDPI_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", + var + ); + } + }, + ); + + let hidpi_factor = match dpi_env { + EnvVarDPI::Randr => calc_dpi_factor( ((*crtc).width as u32, (*crtc).height as u32), ( (*output_info).mm_width as u64, (*output_info).mm_height as u64, ), - ) + ), + EnvVarDPI::Scale(dpi_override) => { + if !validate_hidpi_factor(dpi_override) { + panic!( + "`WINIT_HIDPI_FACTOR` invalid; DPI factors must be either normal floats greater than 0, or `randr`. Got `{}`", + dpi_override, + ); + } + dpi_override + } + EnvVarDPI::NotSet => { + if let Some(dpi) = self.get_xft_dpi() { + dpi / 96. + } else { + calc_dpi_factor( + ((*crtc).width as u32, (*crtc).height as u32), + ( + (*output_info).mm_width as u64, + (*output_info).mm_height as u64, + ), + ) + } + } }; (self.xrandr.XRRFreeOutputInfo)(output_info); diff --git a/src/platform_impl/macos/util/cursor.rs b/src/platform_impl/macos/util/cursor.rs index 7f0b57fe1b1..5c4d1537a73 100644 --- a/src/platform_impl/macos/util/cursor.rs +++ b/src/platform_impl/macos/util/cursor.rs @@ -3,7 +3,8 @@ use cocoa::{ base::{id, nil}, foundation::{NSDictionary, NSPoint, NSString}, }; -use objc::runtime::Sel; +use objc::{runtime::Sel, runtime::NO}; +use std::cell::RefCell; use crate::window::CursorIcon; @@ -126,3 +127,38 @@ pub unsafe fn load_webkit_cursor(cursor_name: &str) -> id { hotSpot:point ] } + +pub unsafe fn invisible_cursor() -> id { + // 16x16 GIF data for invisible cursor + // You can reproduce this via ImageMagick. + // $ convert -size 16x16 xc:none cursor.gif + static CURSOR_BYTES: &[u8] = &[ + 0x47, 0x49, 0x46, 0x38, 0x39, 0x61, 0x10, 0x00, 0x10, 0x00, 0xF0, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x21, 0xF9, 0x04, 0x01, 0x00, 0x00, 0x00, 0x00, 0x2C, 0x00, 0x00, + 0x00, 0x00, 0x10, 0x00, 0x10, 0x00, 0x00, 0x02, 0x0E, 0x84, 0x8F, 0xA9, 0xCB, 0xED, 0x0F, + 0xA3, 0x9C, 0xB4, 0xDA, 0x8B, 0xB3, 0x3E, 0x05, 0x00, 0x3B, + ]; + + thread_local! { + // We can't initialize this at startup. + static CURSOR_OBJECT: RefCell = RefCell::new(nil); + } + + CURSOR_OBJECT.with(|cursor_obj| { + if *cursor_obj.borrow() == nil { + // Create a cursor from `CURSOR_BYTES` + let cursor_data: id = msg_send![class!(NSData), + dataWithBytesNoCopy:CURSOR_BYTES as *const [u8] + length:CURSOR_BYTES.len() + freeWhenDone:NO + ]; + + let ns_image: id = msg_send![class!(NSImage), alloc]; + let _: id = msg_send![ns_image, initWithData: cursor_data]; + let cursor: id = msg_send![class!(NSCursor), alloc]; + *cursor_obj.borrow_mut() = + msg_send![cursor, initWithImage:ns_image hotSpot: NSPoint::new(0.0, 0.0)]; + } + *cursor_obj.borrow() + }) +} diff --git a/src/platform_impl/macos/view.rs b/src/platform_impl/macos/view.rs index d158c636e4c..9bd929da806 100644 --- a/src/platform_impl/macos/view.rs +++ b/src/platform_impl/macos/view.rs @@ -35,9 +35,23 @@ use crate::{ window::WindowId, }; +pub struct CursorState { + pub visible: bool, + pub cursor: util::Cursor, +} + +impl Default for CursorState { + fn default() -> Self { + Self { + visible: true, + cursor: Default::default(), + } + } +} + struct ViewState { ns_window: id, - pub cursor: Arc>, + pub cursor_state: Arc>, ime_spot: Option<(f64, f64)>, raw_characters: Option, is_key_down: bool, @@ -45,12 +59,12 @@ struct ViewState { tracking_rect: Option, } -pub fn new_view(ns_window: id) -> (IdRef, Weak>) { - let cursor = Default::default(); - let cursor_access = Arc::downgrade(&cursor); +pub fn new_view(ns_window: id) -> (IdRef, Weak>) { + let cursor_state = Default::default(); + let cursor_access = Arc::downgrade(&cursor_state); let state = ViewState { ns_window, - cursor, + cursor_state, ime_spot: None, raw_characters: None, is_key_down: false, @@ -349,7 +363,12 @@ extern "C" fn reset_cursor_rects(this: &Object, _sel: Sel) { let state = &mut *(state_ptr as *mut ViewState); let bounds: NSRect = msg_send![this, bounds]; - let cursor = state.cursor.lock().unwrap().load(); + let cursor_state = state.cursor_state.lock().unwrap(); + let cursor = if cursor_state.visible { + cursor_state.cursor.load() + } else { + util::invisible_cursor() + }; let _: () = msg_send![this, addCursorRect:bounds cursor:cursor diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index ee95f17a91c..ff66f82ac54 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -20,6 +20,7 @@ use crate::{ ffi, monitor::{self, MonitorHandle, VideoMode}, util::{self, IdRef}, + view::CursorState, view::{self, new_view}, window_delegate::new_delegate, OsError, @@ -90,8 +91,8 @@ fn create_app(activation_policy: ActivationPolicy) -> Option { unsafe fn create_view( ns_window: id, pl_attribs: &PlatformSpecificWindowBuilderAttributes, -) -> Option<(IdRef, Weak>)> { - let (ns_view, cursor) = new_view(ns_window); +) -> Option<(IdRef, Weak>)> { + let (ns_view, cursor_state) = new_view(ns_window); ns_view.non_nil().map(|ns_view| { if !pl_attribs.disallow_hidpi { ns_view.setWantsBestResolutionOpenGLSurface_(YES); @@ -108,7 +109,7 @@ unsafe fn create_view( ns_window.setContentView_(*ns_view); ns_window.makeFirstResponder_(*ns_view); - (ns_view, cursor) + (ns_view, cursor_state) }) } @@ -290,8 +291,7 @@ pub struct UnownedWindow { input_context: IdRef, // never changes pub shared_state: Arc>, decorations: AtomicBool, - cursor: Weak>, - cursor_visible: AtomicBool, + cursor_state: Weak>, } unsafe impl Send for UnownedWindow {} @@ -320,7 +320,7 @@ impl UnownedWindow { os_error!(OsError::CreationError("Couldn't create `NSWindow`")) })?; - let (ns_view, cursor) = + let (ns_view, cursor_state) = unsafe { create_view(*ns_window, &pl_attribs) }.ok_or_else(|| { unsafe { pool.drain() }; os_error!(OsError::CreationError("Couldn't create `NSView`")) @@ -368,8 +368,7 @@ impl UnownedWindow { input_context, shared_state: Arc::new(Mutex::new(win_attribs.into())), decorations: AtomicBool::new(decorations), - cursor, - cursor_visible: AtomicBool::new(true), + cursor_state, }); let delegate = new_delegate(&window, fullscreen.is_some()); @@ -516,8 +515,8 @@ impl UnownedWindow { pub fn set_cursor_icon(&self, cursor: CursorIcon) { let cursor = util::Cursor::from(cursor); - if let Some(cursor_access) = self.cursor.upgrade() { - *cursor_access.lock().unwrap() = cursor; + if let Some(cursor_access) = self.cursor_state.upgrade() { + cursor_access.lock().unwrap().cursor = cursor; } unsafe { let _: () = msg_send![*self.ns_window, @@ -535,16 +534,17 @@ impl UnownedWindow { #[inline] pub fn set_cursor_visible(&self, visible: bool) { - let cursor_class = class!(NSCursor); - // macOS uses a "hide counter" like Windows does, so we avoid incrementing it more than once. - // (otherwise, `hide_cursor(false)` would need to be called n times!) - if visible != self.cursor_visible.load(Ordering::Acquire) { - if visible { - let _: () = unsafe { msg_send![cursor_class, unhide] }; - } else { - let _: () = unsafe { msg_send![cursor_class, hide] }; + if let Some(cursor_access) = self.cursor_state.upgrade() { + let mut cursor_state = cursor_access.lock().unwrap(); + if visible != cursor_state.visible { + cursor_state.visible = visible; + drop(cursor_state); + unsafe { + let _: () = msg_send![*self.ns_window, + invalidateCursorRectsForView:*self.ns_view + ]; + } } - self.cursor_visible.store(visible, Ordering::Release); } }