Refine Window::set_cursor_grab API

This commit renames `Window::set_cursor_grab` to
`Window::set_cursor_grab_mode`. The new API now accepts enumeration
to control the way cursor grab is performed. The value could be: `lock`,
`confine`, or `none`.

This commit also implements `Window::set_cursor_position` for Wayland,
since it's tied to locked cursor.

Implements API from #1677.
This commit is contained in:
Kirill Chibisov 2022-06-13 09:43:14 +03:00 committed by GitHub
parent 8ef9fe44c7
commit 9e6f666616
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 359 additions and 131 deletions

View file

@ -58,6 +58,8 @@ And please only add new entries to the top of this list, right below the `# Unre
- **Breaking:** On X11, device events are now ignored for unfocused windows by default, use `EventLoopWindowTarget::set_device_event_filter` to set the filter level.
- Implemented `Default` on `EventLoop<()>`.
- Implemented `Eq` for `Fullscreen`, `Theme`, and `UserAttentionType`.
- **Breaking:** `Window::set_cursor_grab` now accepts `CursorGrabMode` to control grabbing behavior.
- On Wayland, add support for `Window::set_cursor_position`.
# 0.26.1 (2022-01-05)

View file

@ -100,7 +100,8 @@ If your PR makes notable changes to Winit's features, please update this section
### Input Handling
- **Mouse events**: Generating mouse events associated with pointer motion, click, and scrolling events.
- **Mouse set location**: Forcibly changing the location of the pointer.
- **Cursor grab**: Locking the cursor so it cannot exit the client area of a window.
- **Cursor locking**: Locking the cursor inside the window so it cannot move.
- **Cursor confining**: Confining the cursor to the window bounds so it cannot leave them.
- **Cursor icon**: Changing the cursor icon, or hiding the cursor.
- **Cursor hittest**: Handle or ignore mouse events for a window.
- **Touch events**: Single-touch events.
@ -197,8 +198,9 @@ Legend:
|Feature |Windows |MacOS |Linux x11|Linux Wayland|Android|iOS |WASM |
|----------------------- | ----- | ---- | ------- | ----------- | ----- | ----- | -------- |
|Mouse events |✔️ |▢[#63] |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |❓ |**N/A**|**N/A**|**N/A**|
|Cursor grab |✔️ |▢[#165] |▢[#242] |✔️ |**N/A**|**N/A**|✔️ |
|Mouse set location |✔️ |✔️ |✔️ |✔️(when locked) |**N/A**|**N/A**|**N/A**|
|Cursor locking |❌ |✔️ |❌ |✔️ |**N/A**|**N/A**|✔️ |
|Cursor confining |✔️ |❌ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|Cursor icon |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|✔️ |
|Cursor hittest |✔️ |✔️ |❌ |✔️ |**N/A**|**N/A**|❌ |
|Touch events |✔️ |❌ |✔️ |✔️ |✔️ |✔️ |❌ |

View file

@ -4,7 +4,7 @@ use simple_logger::SimpleLogger;
use winit::{
event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent},
event_loop::EventLoop,
window::WindowBuilder,
window::{CursorGrabMode, WindowBuilder},
};
fn main() {
@ -34,11 +34,23 @@ fn main() {
..
} => {
use winit::event::VirtualKeyCode::*;
match key {
Escape => control_flow.set_exit(),
G => window.set_cursor_grab(!modifiers.shift()).unwrap(),
H => window.set_cursor_visible(modifiers.shift()),
_ => (),
let result = match key {
Escape => {
control_flow.set_exit();
Ok(())
}
G => window.set_cursor_grab(CursorGrabMode::Confined),
L => window.set_cursor_grab(CursorGrabMode::Locked),
A => window.set_cursor_grab(CursorGrabMode::None),
H => {
window.set_cursor_visible(modifiers.shift());
Ok(())
}
_ => Ok(()),
};
if let Err(err) = result {
println!("error: {}", err);
}
}
WindowEvent::ModifiersChanged(m) => modifiers = m,

View file

@ -9,7 +9,7 @@ fn main() {
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent},
event_loop::EventLoop,
window::{CursorIcon, Fullscreen, WindowBuilder},
window::{CursorGrabMode, CursorIcon, Fullscreen, WindowBuilder},
};
const WINDOW_COUNT: usize = 3;
@ -88,7 +88,21 @@ fn main() {
}
(false, _) => None,
}),
G => window.set_cursor_grab(state).unwrap(),
L if state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Locked) {
println!("error: {}", err);
}
}
G if state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::Confined) {
println!("error: {}", err);
}
}
G | L if !state => {
if let Err(err) = window.set_cursor_grab(CursorGrabMode::None) {
println!("error: {}", err);
}
}
H => window.set_cursor_visible(!state),
I => {
println!("Info:");

View file

@ -20,7 +20,8 @@ use crate::{
error,
event::{self, VirtualKeyCode},
event_loop::{self, ControlFlow},
monitor, window,
monitor,
window::{self, CursorGrabMode},
};
static CONFIG: Lazy<RwLock<Configuration>> = Lazy::new(|| {
@ -765,7 +766,7 @@ impl Window {
))
}
pub fn set_cursor_grab(&self, _: bool) -> Result<(), error::ExternalError> {
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), error::ExternalError> {
Err(error::ExternalError::NotSupported(
error::NotSupportedError::new(),
))

View file

@ -23,7 +23,8 @@ use crate::{
monitor, view, EventLoopWindowTarget, MonitorHandle,
},
window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId,
CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes,
WindowId as RootWindowId,
},
};
@ -184,7 +185,7 @@ impl Inner {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}
pub fn set_cursor_grab(&self, _grab: bool) -> Result<(), ExternalError> {
pub fn set_cursor_grab(&self, _: CursorGrabMode) -> Result<(), ExternalError> {
Err(ExternalError::NotSupported(NotSupportedError::new()))
}

View file

@ -9,8 +9,6 @@
#[cfg(all(not(feature = "x11"), not(feature = "wayland")))]
compile_error!("Please select a feature to build for unix: `x11`, `wayland`");
#[cfg(feature = "wayland")]
use crate::window::Theme;
#[cfg(feature = "wayland")]
use std::error::Error;
@ -28,6 +26,8 @@ use raw_window_handle::RawWindowHandle;
pub use self::x11::XNotSupported;
#[cfg(feature = "x11")]
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
#[cfg(feature = "wayland")]
use crate::window::Theme;
use crate::{
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError},
@ -37,7 +37,7 @@ use crate::{
},
icon::Icon,
monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode},
window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
window::{CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes},
};
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
@ -388,8 +388,8 @@ impl Window {
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(grab))
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
x11_or_wayland!(match self; Window(window) => window.set_cursor_grab(mode))
}
#[inline]

View file

@ -24,23 +24,23 @@ use sctk::shm::ShmHandler;
/// Set of extra features that are supported by the compositor.
#[derive(Debug, Clone, Copy)]
pub struct WindowingFeatures {
cursor_grab: bool,
pointer_constraints: bool,
xdg_activation: bool,
}
impl WindowingFeatures {
/// Create `WindowingFeatures` based on the presented interfaces.
pub fn new(env: &Environment<WinitEnv>) -> Self {
let cursor_grab = env.get_global::<ZwpPointerConstraintsV1>().is_some();
let pointer_constraints = env.get_global::<ZwpPointerConstraintsV1>().is_some();
let xdg_activation = env.get_global::<XdgActivationV1>().is_some();
Self {
cursor_grab,
pointer_constraints,
xdg_activation,
}
}
pub fn cursor_grab(&self) -> bool {
self.cursor_grab
pub fn pointer_constraints(&self) -> bool {
self.pointer_constraints
}
pub fn xdg_activation(&self) -> bool {

View file

@ -5,8 +5,9 @@ use std::rc::Rc;
use sctk::reexports::client::protocol::wl_surface::WlSurface;
use sctk::reexports::client::Attached;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1};
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::ZwpPointerConstraintsV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
use crate::event::{ModifiersState, TouchPhase};
@ -25,6 +26,7 @@ pub(super) struct PointerData {
pub pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
pub confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
pub locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
/// Latest observed serial in pointer events.
pub latest_serial: Rc<Cell<u32>>,
@ -39,6 +41,7 @@ pub(super) struct PointerData {
impl PointerData {
pub fn new(
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
pointer_constraints: Option<Attached<ZwpPointerConstraintsV1>>,
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Self {
@ -47,6 +50,7 @@ impl PointerData {
latest_serial: Rc::new(Cell::new(0)),
latest_enter_serial: Rc::new(Cell::new(0)),
confined_pointer,
locked_pointer,
modifiers_state,
pointer_constraints,
axis_data: AxisData::new(),

View file

@ -60,6 +60,7 @@ pub(super) fn handle_pointer(
let winit_pointer = WinitPointer {
pointer,
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(),
latest_enter_serial: pointer_data.latest_enter_serial.clone(),
@ -104,6 +105,7 @@ pub(super) fn handle_pointer(
let winit_pointer = WinitPointer {
pointer,
confined_pointer: Rc::downgrade(&pointer_data.confined_pointer),
locked_pointer: Rc::downgrade(&pointer_data.locked_pointer),
pointer_constraints: pointer_data.pointer_constraints.clone(),
latest_serial: pointer_data.latest_serial.clone(),
latest_enter_serial: pointer_data.latest_enter_serial.clone(),

View file

@ -11,6 +11,7 @@ use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_rela
use sctk::reexports::protocols::unstable::relative_pointer::v1::client::zwp_relative_pointer_v1::ZwpRelativePointerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1, Lifetime};
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1;
use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_locked_pointer_v1::ZwpLockedPointerV1;
use sctk::seat::pointer::{ThemeManager, ThemedPointer};
use sctk::window::Window;
@ -35,9 +36,13 @@ pub struct WinitPointer {
/// Cursor to handle confine requests.
confined_pointer: Weak<RefCell<Option<ZwpConfinedPointerV1>>>,
/// Cursor to handle locked requests.
locked_pointer: Weak<RefCell<Option<ZwpLockedPointerV1>>>,
/// Latest observed serial in pointer events.
/// used by Window::start_interactive_move()
latest_serial: Rc<Cell<u32>>,
/// Latest observed serial in pointer enter events.
/// used by Window::set_cursor()
latest_enter_serial: Rc<Cell<u32>>,
@ -157,6 +162,52 @@ impl WinitPointer {
}
}
pub fn lock(&self, surface: &WlSurface) {
let pointer_constraints = match &self.pointer_constraints {
Some(pointer_constraints) => pointer_constraints,
None => return,
};
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
*locked_pointer.borrow_mut() = Some(init_locked_pointer(
pointer_constraints,
surface,
&*self.pointer,
));
}
pub fn unlock(&self) {
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
let mut locked_pointer = locked_pointer.borrow_mut();
if let Some(locked_pointer) = locked_pointer.take() {
locked_pointer.destroy();
}
}
pub fn set_cursor_position(&self, surface_x: u32, surface_y: u32) {
let locked_pointer = match self.locked_pointer.upgrade() {
Some(locked_pointer) => locked_pointer,
// A pointer is gone.
None => return,
};
let locked_pointer = locked_pointer.borrow_mut();
if let Some(locked_pointer) = locked_pointer.as_ref() {
locked_pointer.set_cursor_position_hint(surface_x.into(), surface_y.into());
}
}
pub fn drag_window(&self, window: &Window<WinitFrame>) {
// WlPointer::setart_interactive_move() expects the last serial of *any*
// pointer event (compare to set_cursor()).
@ -174,6 +225,9 @@ pub(super) struct Pointers {
/// Confined pointer.
confined_pointer: Rc<RefCell<Option<ZwpConfinedPointerV1>>>,
/// Locked pointer.
locked_pointer: Rc<RefCell<Option<ZwpLockedPointerV1>>>,
}
impl Pointers {
@ -185,11 +239,15 @@ impl Pointers {
modifiers_state: Rc<RefCell<ModifiersState>>,
) -> Self {
let confined_pointer = Rc::new(RefCell::new(None));
let locked_pointer = Rc::new(RefCell::new(None));
let pointer_data = Rc::new(RefCell::new(PointerData::new(
confined_pointer.clone(),
locked_pointer.clone(),
pointer_constraints.clone(),
modifiers_state,
)));
let pointer_seat = seat.detach();
let pointer = theme_manager.theme_pointer_with_impl(
seat,
@ -216,6 +274,7 @@ impl Pointers {
pointer,
relative_pointer,
confined_pointer,
locked_pointer,
}
}
}
@ -232,6 +291,11 @@ impl Drop for Pointers {
confined_pointer.destroy();
}
// Drop lock ponter.
if let Some(locked_pointer) = self.locked_pointer.borrow_mut().take() {
locked_pointer.destroy();
}
// Drop the pointer itself in case it's possible.
if self.pointer.as_ref().version() >= 3 {
self.pointer.release();
@ -264,3 +328,16 @@ pub(super) fn init_confined_pointer(
confined_pointer.detach()
}
pub(super) fn init_locked_pointer(
pointer_constraints: &Attached<ZwpPointerConstraintsV1>,
surface: &WlSurface,
pointer: &WlPointer,
) -> ZwpLockedPointerV1 {
let locked_pointer =
pointer_constraints.lock_pointer(surface, pointer, None, Lifetime::Persistent);
locked_pointer.quick_assign(move |_, _, _| {});
locked_pointer.detach()
}

View file

@ -17,7 +17,9 @@ use crate::platform_impl::{
MonitorHandle as PlatformMonitorHandle, OsError,
PlatformSpecificWindowBuilderAttributes as PlatformAttributes,
};
use crate::window::{CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes};
use crate::window::{
CursorGrabMode, CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes,
};
use super::env::WindowingFeatures;
use super::event_loop::WinitState;
@ -72,6 +74,9 @@ pub struct Window {
/// Whether the window is decorated.
decorated: AtomicBool,
/// Grabbing mode.
cursor_grab_mode: Mutex<CursorGrabMode>,
}
impl Window {
@ -285,6 +290,7 @@ impl Window {
windowing_features,
resizeable: AtomicBool::new(attributes.resizable),
decorated: AtomicBool::new(attributes.decorations),
cursor_grab_mode: Mutex::new(CursorGrabMode::None),
};
Ok(window)
@ -474,12 +480,17 @@ impl Window {
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
if !self.windowing_features.cursor_grab() {
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
if !self.windowing_features.pointer_constraints() {
if mode == CursorGrabMode::None {
return Ok(());
}
return Err(ExternalError::NotSupported(NotSupportedError::new()));
}
self.send_request(WindowRequest::GrabCursor(grab));
*self.cursor_grab_mode.lock().unwrap() = mode;
self.send_request(WindowRequest::SetCursorGrabMode(mode));
Ok(())
}
@ -494,15 +505,19 @@ impl Window {
}
#[inline]
pub fn set_cursor_position(&self, _: Position) -> Result<(), ExternalError> {
// XXX This is possible if the locked pointer is being used. We don't have any
// API for that right now, but it could be added in
// https://github.com/rust-windowing/winit/issues/1677.
//
// This function is essential for the locked pointer API.
//
// See pointer-constraints-unstable-v1.xml.
Err(ExternalError::NotSupported(NotSupportedError::new()))
pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> {
// Positon can be set only for locked cursor.
if *self.cursor_grab_mode.lock().unwrap() != CursorGrabMode::Locked {
return Err(ExternalError::Os(os_error!(OsError::WaylandMisc(
"cursor position can be set only for locked cursor."
))));
}
let scale_factor = self.scale_factor() as f64;
let position = position.to_logical(scale_factor);
self.send_request(WindowRequest::SetLockedCursorPosition(position));
Ok(())
}
#[inline]

View file

@ -19,7 +19,7 @@ use crate::platform_impl::wayland::event_loop::{EventSink, WinitState};
use crate::platform_impl::wayland::seat::pointer::WinitPointer;
use crate::platform_impl::wayland::seat::text_input::TextInputHandler;
use crate::platform_impl::wayland::WindowId;
use crate::window::{CursorIcon, Theme, UserAttentionType};
use crate::window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType};
use super::WinitFrame;
@ -40,8 +40,11 @@ pub enum WindowRequest {
/// Change the cursor icon.
NewCursorIcon(CursorIcon),
/// Grab cursor.
GrabCursor(bool),
/// Change cursor grabbing mode.
SetCursorGrabMode(CursorGrabMode),
/// Set cursor position.
SetLockedCursorPosition(LogicalPosition<u32>),
/// Drag window.
DragWindow,
@ -172,7 +175,7 @@ pub struct WindowHandle {
cursor_visible: Cell<bool>,
/// Cursor confined to the surface.
confined: Cell<bool>,
cursor_grab_mode: Cell<CursorGrabMode>,
/// Pointers over the current surface.
pointers: Vec<WinitPointer>,
@ -208,7 +211,7 @@ impl WindowHandle {
pending_window_requests,
cursor_icon: Cell::new(CursorIcon::Default),
is_resizable: Cell::new(true),
confined: Cell::new(false),
cursor_grab_mode: Cell::new(CursorGrabMode::None),
cursor_visible: Cell::new(true),
pointers: Vec::new(),
text_inputs: Vec::new(),
@ -219,24 +222,37 @@ impl WindowHandle {
}
}
pub fn set_cursor_grab(&self, grab: bool) {
pub fn set_cursor_grab(&self, mode: CursorGrabMode) {
// The new requested state matches the current confine status, return.
if self.confined.get() == grab {
let old_mode = self.cursor_grab_mode.replace(mode);
if old_mode == mode {
return;
}
self.confined.replace(grab);
// Clear old pointer data.
match old_mode {
CursorGrabMode::None => (),
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.unconfine()),
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.unlock()),
}
for pointer in self.pointers.iter() {
if self.confined.get() {
let surface = self.window.surface();
pointer.confine(surface);
} else {
pointer.unconfine();
let surface = self.window.surface();
match mode {
CursorGrabMode::Locked => self.pointers.iter().for_each(|p| p.lock(surface)),
CursorGrabMode::Confined => self.pointers.iter().for_each(|p| p.confine(surface)),
CursorGrabMode::None => {
// Current lock/confine was already removed.
}
}
}
pub fn set_locked_cursor_position(&self, position: LogicalPosition<u32>) {
// XXX the cursor locking is ensured inside `Window`.
self.pointers
.iter()
.for_each(|p| p.set_cursor_position(position.x, position.y));
}
pub fn set_user_attention(&self, request_type: Option<UserAttentionType>) {
let xdg_activation = match self.xdg_activation.as_ref() {
None => return,
@ -284,10 +300,13 @@ impl WindowHandle {
let position = self.pointers.iter().position(|p| *p == pointer);
if position.is_none() {
if self.confined.get() {
let surface = self.window.surface();
pointer.confine(surface);
let surface = self.window.surface();
match self.cursor_grab_mode.get() {
CursorGrabMode::None => (),
CursorGrabMode::Locked => pointer.lock(surface),
CursorGrabMode::Confined => pointer.confine(surface),
}
self.pointers.push(pointer);
}
@ -302,9 +321,11 @@ impl WindowHandle {
if let Some(position) = position {
let pointer = self.pointers.remove(position);
// Drop the confined pointer.
if self.confined.get() {
pointer.unconfine();
// Drop the grabbing mode.
match self.cursor_grab_mode.get() {
CursorGrabMode::None => (),
CursorGrabMode::Locked => pointer.unlock(),
CursorGrabMode::Confined => pointer.unconfine(),
}
}
}
@ -428,8 +449,11 @@ pub fn handle_window_requests(winit_state: &mut WinitState) {
let event_sink = &mut winit_state.event_sink;
window_handle.set_ime_allowed(allow, event_sink);
}
WindowRequest::GrabCursor(grab) => {
window_handle.set_cursor_grab(grab);
WindowRequest::SetCursorGrabMode(mode) => {
window_handle.set_cursor_grab(mode);
}
WindowRequest::SetLockedCursorPosition(position) => {
window_handle.set_locked_cursor_position(position);
}
WindowRequest::DragWindow => {
window_handle.drag_window();

View file

@ -22,7 +22,7 @@ use crate::{
MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes,
VideoMode as PlatformVideoMode,
},
window::{CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes},
window::{CursorGrabMode, CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes},
};
use super::{
@ -106,7 +106,7 @@ pub struct UnownedWindow {
root: ffi::Window, // never changes
screen_id: i32, // never changes
cursor: Mutex<CursorIcon>,
cursor_grabbed: Mutex<bool>,
cursor_grabbed_mode: Mutex<CursorGrabMode>,
cursor_visible: Mutex<bool>,
ime_sender: Mutex<ImeSender>,
pub shared_state: Mutex<SharedState>,
@ -276,7 +276,7 @@ impl UnownedWindow {
root,
screen_id,
cursor: Default::default(),
cursor_grabbed: Mutex::new(false),
cursor_grabbed_mode: Mutex::new(CursorGrabMode::None),
cursor_visible: Mutex::new(true),
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
shared_state: SharedState::new(guessed_monitor, &window_attrs),
@ -1272,64 +1272,75 @@ impl UnownedWindow {
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
let mut grabbed_lock = self.cursor_grabbed.lock();
if grab == *grabbed_lock {
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let mut grabbed_lock = self.cursor_grabbed_mode.lock();
if mode == *grabbed_lock {
return Ok(());
}
unsafe {
// We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`.
// Therefore, this is common to both codepaths.
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
}
let result = if grab {
let result = unsafe {
(self.xconn.xlib.XGrabPointer)(
self.xconn.display,
self.xwindow,
ffi::True,
(ffi::ButtonPressMask
| ffi::ButtonReleaseMask
| ffi::EnterWindowMask
| ffi::LeaveWindowMask
| ffi::PointerMotionMask
| ffi::PointerMotionHintMask
| ffi::Button1MotionMask
| ffi::Button2MotionMask
| ffi::Button3MotionMask
| ffi::Button4MotionMask
| ffi::Button5MotionMask
| ffi::ButtonMotionMask
| ffi::KeymapStateMask) as c_uint,
ffi::GrabModeAsync,
ffi::GrabModeAsync,
self.xwindow,
0,
ffi::CurrentTime,
)
};
match result {
ffi::GrabSuccess => Ok(()),
ffi::AlreadyGrabbed => {
Err("Cursor could not be grabbed: already grabbed by another client")
}
ffi::GrabInvalidTime => Err("Cursor could not be grabbed: invalid time"),
ffi::GrabNotViewable => {
Err("Cursor could not be grabbed: grab location not viewable")
}
ffi::GrabFrozen => Err("Cursor could not be grabbed: frozen by another client"),
_ => unreachable!(),
}
.map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err))))
} else {
self.xconn
let result = match mode {
CursorGrabMode::None => self
.xconn
.flush_requests()
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err)))),
CursorGrabMode::Confined => {
let result = unsafe {
(self.xconn.xlib.XGrabPointer)(
self.xconn.display,
self.xwindow,
ffi::True,
(ffi::ButtonPressMask
| ffi::ButtonReleaseMask
| ffi::EnterWindowMask
| ffi::LeaveWindowMask
| ffi::PointerMotionMask
| ffi::PointerMotionHintMask
| ffi::Button1MotionMask
| ffi::Button2MotionMask
| ffi::Button3MotionMask
| ffi::Button4MotionMask
| ffi::Button5MotionMask
| ffi::ButtonMotionMask
| ffi::KeymapStateMask) as c_uint,
ffi::GrabModeAsync,
ffi::GrabModeAsync,
self.xwindow,
0,
ffi::CurrentTime,
)
};
match result {
ffi::GrabSuccess => Ok(()),
ffi::AlreadyGrabbed => {
Err("Cursor could not be confined: already confined by another client")
}
ffi::GrabInvalidTime => Err("Cursor could not be confined: invalid time"),
ffi::GrabNotViewable => {
Err("Cursor could not be confined: confine location not viewable")
}
ffi::GrabFrozen => {
Err("Cursor could not be confined: frozen by another client")
}
_ => unreachable!(),
}
.map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err))))
}
CursorGrabMode::Locked => {
return Err(ExternalError::NotSupported(NotSupportedError::new()));
}
};
if result.is_ok() {
*grabbed_lock = grab;
*grabbed_lock = mode;
}
result
}
@ -1386,14 +1397,14 @@ impl UnownedWindow {
// we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer`
// if the cursor isn't currently grabbed
let mut grabbed_lock = self.cursor_grabbed.lock();
let mut grabbed_lock = self.cursor_grabbed_mode.lock();
unsafe {
(self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime);
}
self.xconn
.flush_requests()
.map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?;
*grabbed_lock = false;
*grabbed_lock = CursorGrabMode::None;
// we keep the lock until we are done
self.xconn

View file

@ -29,7 +29,8 @@ use crate::{
OsError,
},
window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWindowId,
CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes,
WindowId as RootWindowId,
},
};
use cocoa::{
@ -621,9 +622,17 @@ impl UnownedWindow {
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let associate_mouse_cursor = match mode {
CursorGrabMode::Locked => false,
CursorGrabMode::None => true,
CursorGrabMode::Confined => {
return Err(ExternalError::NotSupported(NotSupportedError::new()))
}
};
// TODO: Do this for real https://stackoverflow.com/a/40922095/5435443
CGDisplay::associate_mouse_and_mouse_cursor_position(!grab)
CGDisplay::associate_mouse_and_mouse_cursor_position(associate_mouse_cursor)
.map_err(|status| ExternalError::Os(os_error!(OsError::CGError(status))))
}

View file

@ -89,8 +89,8 @@ impl Canvas {
})
}
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), RootOE> {
if grab {
pub fn set_cursor_lock(&self, lock: bool) -> Result<(), RootOE> {
if lock {
self.raw().request_pointer_lock();
} else {
let window = web_sys::window()

View file

@ -4,7 +4,7 @@ use crate::event;
use crate::icon::Icon;
use crate::monitor::MonitorHandle as RootMH;
use crate::window::{
CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWI,
CursorGrabMode, CursorIcon, Fullscreen, UserAttentionType, WindowAttributes, WindowId as RootWI,
};
use raw_window_handle::{RawWindowHandle, WebHandle};
@ -216,10 +216,18 @@ impl Window {
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let lock = match mode {
CursorGrabMode::None => false,
CursorGrabMode::Locked => true,
CursorGrabMode::Confined => {
return Err(ExternalError::NotSupported(NotSupportedError::new()))
}
};
self.canvas
.borrow()
.set_cursor_grab(grab)
.set_cursor_lock(lock)
.map_err(ExternalError::Os)
}

View file

@ -69,7 +69,7 @@ use crate::{
window_state::{CursorFlags, SavedWindow, WindowFlags, WindowState},
Parent, PlatformSpecificWindowBuilderAttributes, WindowId,
},
window::{CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes},
window::{CursorGrabMode, CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes},
};
/// The Win32 implementation of the main `Window` object.
@ -276,7 +276,15 @@ impl Window {
}
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
let confine = match mode {
CursorGrabMode::None => false,
CursorGrabMode::Confined => true,
CursorGrabMode::Locked => {
return Err(ExternalError::NotSupported(NotSupportedError::new()))
}
};
let window = self.window.clone();
let window_state = Arc::clone(&self.window_state);
let (tx, rx) = channel();
@ -286,7 +294,7 @@ impl Window {
let result = window_state
.lock()
.mouse
.set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, grab))
.set_cursor_flags(window.0, |f| f.set(CursorFlags::GRABBED, confine))
.map_err(|e| ExternalError::Os(os_error!(e)));
let _ = tx.send(result);
});

View file

@ -906,18 +906,24 @@ impl Window {
self.window.set_cursor_position(position.into())
}
/// Grabs the cursor, preventing it from leaving the window.
/// Set grabbing [mode]([`CursorGrabMode`]) on the cursor preventing it from leaving the window.
///
/// There's no guarantee that the cursor will be hidden. You should
/// hide it by yourself if you want so.
/// # Example
///
/// ## Platform-specific
/// First try confining the cursor, and if that fails, try locking it instead.
///
/// - **macOS:** This locks the cursor in a fixed location, which looks visually awkward.
/// - **iOS / Android:** Always returns an [`ExternalError::NotSupported`].
/// ```no-run
/// # use winit::event_loop::EventLoop;
/// # use winit::window::{CursorGrabMode, Window};
/// # let mut event_loop = EventLoop::new();
/// # let window = Window::new(&event_loop).unwrap();
/// window.set_cursor_grab(CursorGrabMode::Confined)
/// .or_else(|_e| window.set_cursor_grab(CursorGrabMode::Locked))
/// .unwrap();
/// ```
#[inline]
pub fn set_cursor_grab(&self, grab: bool) -> Result<(), ExternalError> {
self.window.set_cursor_grab(grab)
pub fn set_cursor_grab(&self, mode: CursorGrabMode) -> Result<(), ExternalError> {
self.window.set_cursor_grab(mode)
}
/// Modifies the cursor's visibility.
@ -1028,6 +1034,38 @@ unsafe impl raw_window_handle::HasRawWindowHandle for Window {
}
}
/// The behavior of cursor grabbing.
///
/// Use this enum with [`Window::set_cursor_grab`] to grab the cursor.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum CursorGrabMode {
/// No grabbing of the cursor is performed.
None,
/// The cursor is confined to the window area.
///
/// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you
/// want to do so.
///
/// ## Platform-specific
///
/// - **macOS:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
/// - ** iOS / Android / Web:** Always returns an [`ExternalError::NotSupported`].
Confined,
/// The cursor is locked inside the window area to the certain position.
///
/// There's no guarantee that the cursor will be hidden. You should hide it by yourself if you
/// want to do so.
///
/// ## Platform-specific
///
/// - **X11 / Windows:** Not implemented. Always returns [`ExternalError::NotSupported`] for now.
/// - ** iOS / Android:** Always returns an [`ExternalError::NotSupported`].
Locked,
}
/// Describes the appearance of the mouse cursor.
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]