use MouseCursor; use CreationError; use CreationError::OsError; use libc; use std::borrow::Borrow; use std::{mem, cmp}; use std::sync::Arc; use std::os::raw::*; use std::ffi::CString; use parking_lot::Mutex; use CursorState; use WindowAttributes; use platform::PlatformSpecificWindowBuilderAttributes; use platform::MonitorId as PlatformMonitorId; use platform::x11::MonitorId as X11MonitorId; use window::MonitorId as RootMonitorId; use platform::x11::monitor::get_available_monitors; use super::{ffi, util, XConnection, XError, WindowId, EventsLoop}; unsafe extern "C" fn visibility_predicate( _display: *mut ffi::Display, event: *mut ffi::XEvent, arg: ffi::XPointer, // We populate this with the window ID (by value) when we call XIfEvent ) -> ffi::Bool { let event: &ffi::XAnyEvent = (*event).as_ref(); let window = arg as ffi::Window; (event.window == window && event.type_ == ffi::VisibilityNotify) as _ } pub struct XWindow { pub display: Arc, pub window: ffi::Window, pub root: ffi::Window, pub screen_id: i32, } unsafe impl Send for XWindow {} unsafe impl Sync for XWindow {} unsafe impl Send for Window2 {} unsafe impl Sync for Window2 {} #[derive(Debug, Default)] pub struct SharedState { pub frame_extents: Option, } pub struct Window2 { pub x: Arc, cursor: Mutex, cursor_state: Mutex, pub shared_state: Arc>, } impl Window2 { pub fn new( ctx: &EventsLoop, window_attrs: &WindowAttributes, pl_attribs: &PlatformSpecificWindowBuilderAttributes, ) -> Result { let xconn = &ctx.display; let dimensions = { // x11 only applies constraints when the window is actively resized // by the user, so we have to manually apply the initial constraints let mut dimensions = window_attrs.dimensions.unwrap_or((800, 600)); if let Some(max) = window_attrs.max_dimensions { dimensions.0 = cmp::min(dimensions.0, max.0); dimensions.1 = cmp::min(dimensions.1, max.1); } if let Some(min) = window_attrs.min_dimensions { dimensions.0 = cmp::max(dimensions.0, min.0); dimensions.1 = cmp::max(dimensions.1, min.1); } dimensions }; let screen_id = match pl_attribs.screen_id { Some(id) => id, None => unsafe { (xconn.xlib.XDefaultScreen)(xconn.display) }, }; // getting the root window let root = ctx.root; // creating let mut set_win_attr = { let mut swa: ffi::XSetWindowAttributes = unsafe { mem::zeroed() }; swa.colormap = if let Some(vi) = pl_attribs.visual_infos { unsafe { let visual = vi.visual; (xconn.xlib.XCreateColormap)(xconn.display, root, visual, ffi::AllocNone) } } else { 0 }; swa.event_mask = ffi::ExposureMask | ffi::StructureNotifyMask | ffi::VisibilityChangeMask | ffi::KeyPressMask | ffi::KeyReleaseMask | ffi::KeymapStateMask | ffi::ButtonPressMask | ffi::ButtonReleaseMask | ffi::PointerMotionMask; swa.border_pixel = 0; if window_attrs.transparent { swa.background_pixel = 0; } swa.override_redirect = 0; swa }; let mut window_attributes = ffi::CWBorderPixel | ffi::CWColormap | ffi::CWEventMask; if window_attrs.transparent { window_attributes |= ffi::CWBackPixel; } // finally creating the window let window = unsafe { (xconn.xlib.XCreateWindow)( xconn.display, root, 0, 0, dimensions.0 as c_uint, dimensions.1 as c_uint, 0, match pl_attribs.visual_infos { Some(vi) => vi.depth, None => ffi::CopyFromParent }, ffi::InputOutput as c_uint, match pl_attribs.visual_infos { Some(vi) => vi.visual, None => ffi::CopyFromParent as *mut _ }, window_attributes, &mut set_win_attr, ) }; let x_window = Arc::new(XWindow { display: Arc::clone(xconn), window, root, screen_id, }); let window = Window2 { x: x_window, cursor: Mutex::new(MouseCursor::Default), cursor_state: Mutex::new(CursorState::Normal), shared_state: Arc::new(Mutex::new(SharedState::default())), }; // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window // title to determine placement/etc., so doing this after mapping would cause the WM to // act on the wrong title state. window.set_title_inner(&window_attrs.title).queue(); window.set_decorations_inner(window_attrs.decorations).queue(); { let ref x_window: &XWindow = window.x.borrow(); // Enable drag and drop (TODO: extend API to make this toggleable) unsafe { let dnd_aware_atom = util::get_atom(xconn, b"XdndAware\0") .expect("Failed to call XInternAtom (XdndAware)"); let version = &[5 as c_ulong]; // Latest version; hasn't changed since 2002 util::change_property( xconn, x_window.window, dnd_aware_atom, ffi::XA_ATOM, util::Format::Long, util::PropMode::Replace, version, ) }.queue(); // Set ICCCM WM_CLASS property based on initial window title // Must be done *before* mapping the window by ICCCM 4.1.2.5 { let name = CString::new(window_attrs.title.as_str()) .expect("Window title contained null byte"); let mut class_hints = { let class_hints = unsafe { (xconn.xlib.XAllocClassHint)() }; util::XSmartPointer::new(xconn, class_hints) }.expect("XAllocClassHint returned null; out of memory"); (*class_hints).res_name = name.as_ptr() as *mut c_char; (*class_hints).res_class = name.as_ptr() as *mut c_char; unsafe { (xconn.xlib.XSetClassHint)( xconn.display, x_window.window, class_hints.ptr, ); }//.queue(); } // set size hints { let mut size_hints = { let size_hints = unsafe { (xconn.xlib.XAllocSizeHints)() }; util::XSmartPointer::new(xconn, size_hints) }.expect("XAllocSizeHints returned null; out of memory"); (*size_hints).flags = ffi::PSize; (*size_hints).width = dimensions.0 as c_int; (*size_hints).height = dimensions.1 as c_int; if let Some((min_width, min_height)) = window_attrs.min_dimensions { (*size_hints).flags |= ffi::PMinSize; (*size_hints).min_width = min_width as c_int; (*size_hints).min_height = min_height as c_int; } if let Some((max_width, max_height)) = window_attrs.max_dimensions { (*size_hints).flags |= ffi::PMaxSize; (*size_hints).max_width = max_width as c_int; (*size_hints).max_height = max_height as c_int; } if let Some((width_inc, height_inc)) = pl_attribs.resize_increments { (*size_hints).flags |= ffi::PResizeInc; (*size_hints).width_inc = width_inc as c_int; (*size_hints).height_inc = height_inc as c_int; } if let Some((base_width, base_height)) = pl_attribs.base_size { (*size_hints).flags |= ffi::PBaseSize; (*size_hints).base_width = base_width as c_int; (*size_hints).base_height = base_height as c_int; } unsafe { (xconn.xlib.XSetWMNormalHints)( xconn.display, x_window.window, size_hints.ptr, ); }//.queue(); } // Opt into handling window close unsafe { (xconn.xlib.XSetWMProtocols)( xconn.display, x_window.window, &ctx.wm_delete_window as *const _ as *mut _, 1, ); }//.queue(); // Set visibility (map window) if window_attrs.visible { unsafe { (xconn.xlib.XMapRaised)(xconn.display, x_window.window); }//.queue(); } // Attempt to make keyboard input repeat detectable unsafe { let mut supported_ptr = ffi::False; (xconn.xlib.XkbSetDetectableAutoRepeat)( xconn.display, ffi::True, &mut supported_ptr, ); if supported_ptr == ffi::False { return Err(OsError(format!("XkbSetDetectableAutoRepeat failed"))); } } // Select XInput2 events let mask = { let mut mask = ffi::XI_MotionMask | ffi::XI_ButtonPressMask | ffi::XI_ButtonReleaseMask //| ffi::XI_KeyPressMask //| ffi::XI_KeyReleaseMask | ffi::XI_EnterMask | ffi::XI_LeaveMask | ffi::XI_FocusInMask | ffi::XI_FocusOutMask; if window_attrs.multitouch { mask |= ffi::XI_TouchBeginMask | ffi::XI_TouchUpdateMask | ffi::XI_TouchEndMask; } mask }; unsafe { util::select_xinput_events( xconn, x_window.window, ffi::XIAllMasterDevices, mask, ) }.queue(); // These properties must be set after mapping window.set_maximized_inner(window_attrs.maximized).queue(); window.set_fullscreen_inner(window_attrs.fullscreen.clone()).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 = mem::uninitialized(); (xconn.xlib.XIfEvent)( // This will flush the request buffer IF it blocks. xconn.display, &mut event as *mut ffi::XEvent, Some(visibility_predicate), x_window.window as _, ); (xconn.xlib.XSetInputFocus)( xconn.display, x_window.window, ffi::RevertToParent, ffi::CurrentTime, ); } } } // We never want to give the user a broken window, since by then, it's too late to handle. unsafe { util::sync_with_server(xconn) } .map(|_| window) .map_err(|x_err| OsError( format!("X server returned error while building window: {:?}", x_err) )) } fn set_netwm( xconn: &Arc, window: ffi::Window, root: ffi::Window, properties: (c_long, c_long, c_long, c_long), operation: util::StateOperation ) -> util::Flusher { let state_atom = unsafe { util::get_atom(xconn, b"_NET_WM_STATE\0") } .expect("Failed to call XInternAtom (_NET_WM_STATE)"); unsafe { util::send_client_msg( xconn, window, root, state_atom, Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), ( operation as c_long, properties.0, properties.1, properties.2, properties.3, ) ) } } fn set_fullscreen_hint(&self, fullscreen: bool) -> util::Flusher { let xconn = &self.x.display; let fullscreen_atom = unsafe { util::get_atom(xconn, b"_NET_WM_STATE_FULLSCREEN\0") } .expect("Failed to call XInternAtom (_NET_WM_STATE_FULLSCREEN)"); Window2::set_netwm( xconn, self.x.window, self.x.root, (fullscreen_atom as c_long, 0, 0, 0), fullscreen.into(), ) } fn set_fullscreen_inner(&self, monitor: Option) -> util::Flusher { match monitor { None => { self.set_fullscreen_hint(false) }, Some(RootMonitorId { inner: PlatformMonitorId::X(monitor) }) => { let screenpos = monitor.get_position(); self.set_position(screenpos.0 as i32, screenpos.1 as i32); self.set_fullscreen_hint(true) } _ => unreachable!(), } } pub fn set_fullscreen(&self, monitor: Option) { self.set_fullscreen_inner(monitor) .flush() .expect("Failed to change window fullscreen state"); self.invalidate_cached_frame_extents(); } pub fn get_current_monitor(&self) -> X11MonitorId { let monitors = get_available_monitors(&self.x.display); let default = monitors[0].clone(); let (wx,wy) = match self.get_position() { Some(val) => (cmp::max(0,val.0) as u32, cmp::max(0,val.1) as u32), None=> return default, }; let (ww,wh) = match self.get_outer_size() { Some(val) => val, None=> return default, }; // Opposite corner coordinates let (wxo, wyo) = (wx+ww-1, wy+wh-1); // Find the monitor with the biggest overlap with the window let mut overlap = 0; let mut find = default; for monitor in monitors { let (mx, my) = monitor.get_position(); let mx = mx as u32; let my = my as u32; let (mw, mh) = monitor.get_dimensions(); let (mxo, myo) = (mx+mw-1, my+mh-1); let (ox, oy) = (cmp::max(wx, mx), cmp::max(wy, my)); let (oxo, oyo) = (cmp::min(wxo, mxo), cmp::min(wyo, myo)); let osize = if ox <= oxo || oy <= oyo { 0 } else { (oxo-ox)*(oyo-oy) }; if osize > overlap { overlap = osize; find = monitor; } } find } fn set_maximized_inner(&self, maximized: bool) -> util::Flusher { let xconn = &self.x.display; let horz_atom = unsafe { util::get_atom(xconn, b"_NET_WM_STATE_MAXIMIZED_HORZ\0") } .expect("Failed to call XInternAtom (_NET_WM_STATE_MAXIMIZED_HORZ)"); let vert_atom = unsafe { util::get_atom(xconn, b"_NET_WM_STATE_MAXIMIZED_VERT\0") } .expect("Failed to call XInternAtom (_NET_WM_STATE_MAXIMIZED_VERT)"); Window2::set_netwm( xconn, self.x.window, self.x.root, (horz_atom as c_long, vert_atom as c_long, 0, 0), maximized.into(), ) } pub fn set_maximized(&self, maximized: bool) { self.set_maximized_inner(maximized) .flush() .expect("Failed to change window maximization"); self.invalidate_cached_frame_extents(); } fn set_title_inner(&self, title: &str) -> util::Flusher { let xconn = &self.x.display; let wm_name_atom = unsafe { util::get_atom(xconn, b"_NET_WM_NAME\0") } .expect("Failed to call XInternAtom (_NET_WM_NAME)"); let utf8_atom = unsafe { util::get_atom(xconn, b"UTF8_STRING\0") } .expect("Failed to call XInternAtom (UTF8_STRING)"); let title = CString::new(title).expect("Window title contained null byte"); unsafe { (xconn.xlib.XStoreName)( xconn.display, self.x.window, title.as_ptr() as *const c_char, ); util::change_property( xconn, self.x.window, wm_name_atom, utf8_atom, util::Format::Char, util::PropMode::Replace, title.as_bytes_with_nul(), ) } } pub fn set_title(&self, title: &str) { self.set_title_inner(title) .flush() .expect("Failed to set window title"); } fn set_decorations_inner(&self, decorations: bool) -> util::Flusher { let xconn = &self.x.display; let wm_hints = unsafe { util::get_atom(xconn, b"_MOTIF_WM_HINTS\0") } .expect("Failed to call XInternAtom (_MOTIF_WM_HINTS)"); unsafe { util::change_property( xconn, self.x.window, wm_hints, wm_hints, util::Format::Long, util::PropMode::Replace, &[ util::MWM_HINTS_DECORATIONS, // flags 0, // functions decorations as c_ulong, // decorations 0, // input mode 0, // status ], ) } } pub fn set_decorations(&self, decorations: bool) { self.set_decorations_inner(decorations) .flush() .expect("Failed to set decoration state"); self.invalidate_cached_frame_extents(); } pub fn show(&self) { unsafe { (self.x.display.xlib.XMapRaised)(self.x.display.display, self.x.window); util::flush_requests(&self.x.display) .expect("Failed to call XMapRaised"); } } pub fn hide(&self) { unsafe { (self.x.display.xlib.XUnmapWindow)(self.x.display.display, self.x.window); util::flush_requests(&self.x.display) .expect("Failed to call XUnmapWindow"); } } fn update_cached_frame_extents(&self) { let extents = util::get_frame_extents_heuristic( &self.x.display, self.x.window, self.x.root, ); (*self.shared_state.lock()).frame_extents = Some(extents); } fn invalidate_cached_frame_extents(&self) { (*self.shared_state.lock()).frame_extents.take(); } #[inline] pub fn get_position(&self) -> Option<(i32, i32)> { let extents = (*self.shared_state.lock()).frame_extents.clone(); if let Some(extents) = extents { self.get_inner_position().map(|(x, y)| extents.inner_pos_to_outer(x, y) ) } else { self.update_cached_frame_extents(); self.get_position() } } #[inline] pub fn get_inner_position(&self) -> Option<(i32, i32)> { unsafe { util::translate_coords(&self.x.display, self.x.window, self.x.root )} .ok() .map(|coords| (coords.x_rel_root, coords.y_rel_root)) } pub fn set_position(&self, mut x: i32, mut y: i32) { // There are a few WMs that set client area position rather than window position, so // we'll translate for consistency. if util::wm_name_is_one_of(&["Enlightenment", "FVWM"]) { let extents = (*self.shared_state.lock()).frame_extents.clone(); if let Some(extents) = extents { x += extents.frame_extents.left as i32; y += extents.frame_extents.top as i32; } else { self.update_cached_frame_extents(); self.set_position(x, y) } } unsafe { (self.x.display.xlib.XMoveWindow)( self.x.display.display, self.x.window, x as c_int, y as c_int, ); util::flush_requests(&self.x.display) }.expect("Failed to call XMoveWindow"); } #[inline] pub fn get_inner_size(&self) -> Option<(u32, u32)> { unsafe { util::get_geometry(&self.x.display, self.x.window) } .ok() .map(|geo| (geo.width, geo.height)) } #[inline] pub fn get_outer_size(&self) -> Option<(u32, u32)> { let extents = (*self.shared_state.lock()).frame_extents.clone(); if let Some(extents) = extents { self.get_inner_size().map(|(w, h)| extents.inner_size_to_outer(w, h) ) } else { self.update_cached_frame_extents(); self.get_outer_size() } } #[inline] pub fn set_inner_size(&self, x: u32, y: u32) { unsafe { (self.x.display.xlib.XResizeWindow)( self.x.display.display, self.x.window, x as c_uint, y as c_uint, ); util::flush_requests(&self.x.display) }.expect("Failed to call XResizeWindow"); } unsafe fn update_normal_hints(&self, callback: F) -> Result<(), XError> where F: FnOnce(*mut ffi::XSizeHints) -> () { let xconn = &self.x.display; let size_hints = { let size_hints = (xconn.xlib.XAllocSizeHints)(); util::XSmartPointer::new(&xconn, size_hints) .expect("XAllocSizeHints returned null; out of memory") }; let mut flags: c_long = mem::uninitialized(); (xconn.xlib.XGetWMNormalHints)( xconn.display, self.x.window, size_hints.ptr, &mut flags, ); xconn.check_errors()?; callback(size_hints.ptr); (xconn.xlib.XSetWMNormalHints)( xconn.display, self.x.window, size_hints.ptr, ); util::flush_requests(xconn)?; Ok(()) } pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { unsafe { self.update_normal_hints(|size_hints| { if let Some((width, height)) = dimensions { (*size_hints).flags |= ffi::PMinSize; (*size_hints).min_width = width as c_int; (*size_hints).min_height = height as c_int; } else { (*size_hints).flags &= !ffi::PMinSize; } }) }.expect("Failed to call XSetWMNormalHints"); } pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) { unsafe { self.update_normal_hints(|size_hints| { if let Some((width, height)) = dimensions { (*size_hints).flags |= ffi::PMaxSize; (*size_hints).max_width = width as c_int; (*size_hints).max_height = height as c_int; } else { (*size_hints).flags &= !ffi::PMaxSize; } }) }.expect("Failed to call XSetWMNormalHints"); } #[inline] pub fn get_xlib_display(&self) -> *mut c_void { self.x.display.display as _ } #[inline] pub fn get_xlib_screen_id(&self) -> c_int { self.x.screen_id } #[inline] pub fn get_xlib_xconnection(&self) -> Arc { self.x.display.clone() } #[inline] pub fn platform_display(&self) -> *mut libc::c_void { self.x.display.display as _ } #[inline] pub fn get_xlib_window(&self) -> c_ulong { self.x.window } #[inline] pub fn platform_window(&self) -> *mut libc::c_void { self.x.window as _ } pub fn get_xcb_connection(&self) -> *mut c_void { unsafe { (self.x.display.xlib_xcb.XGetXCBConnection)(self.x.display.display) as *mut _ } } fn load_cursor(&self, name: &[u8]) -> ffi::Cursor { unsafe { (self.x.display.xcursor.XcursorLibraryLoadCursor)( self.x.display.display, name.as_ptr() as *const c_char, ) } } fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor { for name in names.iter() { let xcursor = self.load_cursor(name); if xcursor != 0 { return xcursor; } } 0 } fn get_cursor(&self, cursor: MouseCursor) -> ffi::Cursor { let load = |name: &[u8]| { self.load_cursor(name) }; let loadn = |names: &[&[u8]]| { self.load_first_existing_cursor(names) }; // Try multiple names in some cases where the name // differs on the desktop environments or themes. // // Try the better looking (or more suiting) names first. match cursor { MouseCursor::Alias => load(b"link\0"), MouseCursor::Arrow => load(b"arrow\0"), MouseCursor::Cell => load(b"plus\0"), MouseCursor::Copy => load(b"copy\0"), MouseCursor::Crosshair => load(b"crosshair\0"), MouseCursor::Default => load(b"left_ptr\0"), MouseCursor::Hand => loadn(&[b"hand2\0", b"hand1\0"]), MouseCursor::Help => load(b"question_arrow\0"), MouseCursor::Move => load(b"move\0"), MouseCursor::Grab => loadn(&[b"openhand\0", b"grab\0"]), MouseCursor::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]), MouseCursor::Progress => load(b"left_ptr_watch\0"), MouseCursor::AllScroll => load(b"all-scroll\0"), MouseCursor::ContextMenu => load(b"context-menu\0"), MouseCursor::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]), MouseCursor::NotAllowed => load(b"crossed_circle\0"), // Resize cursors MouseCursor::EResize => load(b"right_side\0"), MouseCursor::NResize => load(b"top_side\0"), MouseCursor::NeResize => load(b"top_right_corner\0"), MouseCursor::NwResize => load(b"top_left_corner\0"), MouseCursor::SResize => load(b"bottom_side\0"), MouseCursor::SeResize => load(b"bottom_right_corner\0"), MouseCursor::SwResize => load(b"bottom_left_corner\0"), MouseCursor::WResize => load(b"left_side\0"), MouseCursor::EwResize => load(b"h_double_arrow\0"), MouseCursor::NsResize => load(b"v_double_arrow\0"), MouseCursor::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]), MouseCursor::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]), MouseCursor::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]), MouseCursor::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]), MouseCursor::Text => loadn(&[b"text\0", b"xterm\0"]), MouseCursor::VerticalText => load(b"vertical-text\0"), MouseCursor::Wait => load(b"watch\0"), MouseCursor::ZoomIn => load(b"zoom-in\0"), MouseCursor::ZoomOut => load(b"zoom-out\0"), MouseCursor::NoneCursor => self.create_empty_cursor() .expect("Failed to create empty cursor"), } } fn update_cursor(&self, cursor: ffi::Cursor) { unsafe { (self.x.display.xlib.XDefineCursor)(self.x.display.display, self.x.window, cursor); if cursor != 0 { (self.x.display.xlib.XFreeCursor)(self.x.display.display, cursor); } util::flush_requests(&self.x.display).expect("Failed to set or free the cursor"); } } pub fn set_cursor(&self, cursor: MouseCursor) { let mut current_cursor = self.cursor.lock(); *current_cursor = cursor; if *self.cursor_state.lock() != CursorState::Hide { self.update_cursor(self.get_cursor(*current_cursor)); } } // TODO: This could maybe be cached. I don't think it's worth // the complexity, since cursor changes are not so common, // and this is just allocating a 1x1 pixmap... fn create_empty_cursor(&self) -> Option { let data = 0; let pixmap = unsafe { (self.x.display.xlib.XCreateBitmapFromData)( self.x.display.display, self.x.window, &data, 1, 1, ) }; if pixmap == 0 { // Failed to allocate return None; } let cursor = unsafe { // We don't care about this color, since it only fills bytes // in the pixmap which are not 0 in the mask. let dummy_color: ffi::XColor = mem::uninitialized(); let cursor = (self.x.display.xlib.XCreatePixmapCursor)( self.x.display.display, pixmap, pixmap, &dummy_color as *const _ as *mut _, &dummy_color as *const _ as *mut _, 0, 0, ); (self.x.display.xlib.XFreePixmap)(self.x.display.display, pixmap); cursor }; Some(cursor) } pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> { use CursorState::{ Grab, Normal, Hide }; let mut cursor_state = self.cursor_state.lock(); match (state, *cursor_state) { (Normal, Normal) | (Hide, Hide) | (Grab, Grab) => return Ok(()), _ => {}, } match *cursor_state { Grab => { unsafe { (self.x.display.xlib.XUngrabPointer)(self.x.display.display, ffi::CurrentTime); util::flush_requests(&self.x.display).expect("Failed to call XUngrabPointer"); } }, Normal => {}, Hide => self.update_cursor(self.get_cursor(*self.cursor.lock())), } match state { Normal => { *cursor_state = state; Ok(()) }, Hide => { *cursor_state = state; self.update_cursor( self.create_empty_cursor().expect("Failed to create empty cursor") ); Ok(()) }, Grab => { unsafe { // Ungrab before grabbing to prevent passive grabs // from causing AlreadyGrabbed (self.x.display.xlib.XUngrabPointer)(self.x.display.display, ffi::CurrentTime); match (self.x.display.xlib.XGrabPointer)( self.x.display.display, self.x.window, 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.x.window, 0, ffi::CurrentTime ) { ffi::GrabSuccess => { *cursor_state = state; Ok(()) }, ffi::AlreadyGrabbed | ffi::GrabInvalidTime | ffi::GrabNotViewable | ffi::GrabFrozen => Err("cursor could not be grabbed".to_string()), _ => unreachable!(), } } }, } } pub fn hidpi_factor(&self) -> f32 { unsafe { let x_px = (self.x.display.xlib.XDisplayWidth)(self.x.display.display, self.x.screen_id); let y_px = (self.x.display.xlib.XDisplayHeight)(self.x.display.display, self.x.screen_id); let x_mm = (self.x.display.xlib.XDisplayWidthMM)(self.x.display.display, self.x.screen_id); let y_mm = (self.x.display.xlib.XDisplayHeightMM)(self.x.display.display, self.x.screen_id); let ppmm = ((x_px as f32 * y_px as f32) / (x_mm as f32 * y_mm as f32)).sqrt(); ((ppmm * (12.0 * 25.4 / 96.0)).round() / 12.0).max(1.0) // quantize with 1/12 step size. } } pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { unsafe { (self.x.display.xlib.XWarpPointer)( self.x.display.display, 0, self.x.window, 0, 0, 0, 0, x, y, ); util::flush_requests(&self.x.display).map_err(|_| ()) } } #[inline] pub fn id(&self) -> WindowId { WindowId(self.x.window) } }