use std::{cmp, env, mem}; use std::ffi::CString; use std::os::raw::*; use std::path::Path; use std::sync::Arc; use libc; use parking_lot::Mutex; use {CursorState, Icon, MouseCursor, WindowAttributes}; use CreationError::{self, OsError}; use platform::MonitorId as PlatformMonitorId; use platform::PlatformSpecificWindowBuilderAttributes; use platform::x11::MonitorId as X11MonitorId; use platform::x11::monitor::get_monitor_for_window; use window::MonitorId as RootMonitorId; 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 _ } #[derive(Debug, Default)] pub struct SharedState { pub multitouch: bool, pub cursor_pos: Option<(f64, f64)>, pub size: Option<(u32, u32)>, pub position: Option<(i32, i32)>, pub inner_position: Option<(i32, i32)>, pub inner_position_rel_parent: Option<(i32, i32)>, pub last_monitor: Option, pub dpi_adjusted: Option<(f64, f64)>, pub frame_extents: Option, pub min_dimensions: Option<(u32, u32)>, pub max_dimensions: Option<(u32, u32)>, } unsafe impl Send for UnownedWindow {} unsafe impl Sync for UnownedWindow {} pub struct UnownedWindow { pub xconn: Arc, // never changes xwindow: ffi::Window, // never changes root: ffi::Window, // never changes screen_id: i32, // never changes cursor: Mutex, cursor_state: Mutex, pub multitouch: bool, // never changes pub shared_state: Mutex, } impl UnownedWindow { pub fn new( event_loop: &EventsLoop, window_attrs: WindowAttributes, pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { let xconn = &event_loop.xconn; let root = event_loop.root; 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) }, }; // 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; swa.override_redirect = pl_attribs.override_redirect as c_int; swa }; let mut window_attributes = ffi::CWBorderPixel | ffi::CWColormap | ffi::CWEventMask; if pl_attribs.override_redirect { window_attributes |= ffi::CWOverrideRedirect; } // finally creating the window let xwindow = 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 ffi::Visual, }, window_attributes, &mut set_win_attr, ) }; let window = UnownedWindow { xconn: Arc::clone(xconn), xwindow, root, screen_id, cursor: Default::default(), cursor_state: Default::default(), multitouch: window_attrs.multitouch, shared_state: Default::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(); { // Enable drag and drop (TODO: extend API to make this toggleable) unsafe { let dnd_aware_atom = xconn.get_atom_unchecked(b"XdndAware\0"); let version = &[5 as c_ulong]; // Latest version; hasn't changed since 2002 xconn.change_property( window.xwindow, dnd_aware_atom, ffi::XA_ATOM, util::PropMode::Replace, version, ) }.queue(); // WM_CLASS must be set *before* mapping the window, as per ICCCM! { let (class, instance) = if let Some((instance, class)) = pl_attribs.class { let instance = CString::new(instance.as_str()) .expect("`WM_CLASS` instance contained null byte"); let class = CString::new(class.as_str()) .expect("`WM_CLASS` class contained null byte"); (instance, class) } else { let class = env::args() .next() .as_ref() // Default to the name of the binary (via argv[0]) .and_then(|path| Path::new(path).file_name()) .and_then(|bin_name| bin_name.to_str()) .map(|bin_name| bin_name.to_owned()) .or_else(|| Some(window_attrs.title.clone())) .and_then(|string| CString::new(string.as_str()).ok()) .expect("Default `WM_CLASS` class contained null byte"); // This environment variable is extraordinarily unlikely to actually be used... let instance = env::var("RESOURCE_NAME") .ok() .and_then(|instance| CString::new(instance.as_str()).ok()) .or_else(|| Some(class.clone())) .expect("Default `WM_CLASS` instance contained null byte"); (instance, class) }; let mut class_hint = xconn.alloc_class_hint(); (*class_hint).res_name = class.as_ptr() as *mut c_char; (*class_hint).res_class = instance.as_ptr() as *mut c_char; unsafe { (xconn.xlib.XSetClassHint)( xconn.display, window.xwindow, class_hint.ptr, ); }//.queue(); } window.set_pid().map(|flusher| flusher.queue()); if pl_attribs.x11_window_type != Default::default() { window.set_window_type(pl_attribs.x11_window_type).queue(); } // set size hints { (*window.shared_state.lock()).min_dimensions = window_attrs.min_dimensions; (*window.shared_state.lock()).max_dimensions = window_attrs.max_dimensions; let mut min_dimensions = window_attrs.min_dimensions; let mut max_dimensions = window_attrs.max_dimensions; if !window_attrs.resizable && !util::wm_name_is_one_of(&["Xfwm4"]) { max_dimensions = Some(dimensions); min_dimensions = Some(dimensions); } let mut size_hints = xconn.alloc_size_hints(); (*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)) = 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)) = 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, window.xwindow, size_hints.ptr, ); }//.queue(); } // Set window icons if let Some(icon) = window_attrs.window_icon { window.set_icon_inner(icon).queue(); } // Opt into handling window close unsafe { (xconn.xlib.XSetWMProtocols)( xconn.display, window.xwindow, &event_loop.wm_delete_window as *const ffi::Atom as *mut ffi::Atom, 1, ); }//.queue(); // Set visibility (map window) if window_attrs.visible { unsafe { (xconn.xlib.XMapRaised)(xconn.display, window.xwindow); }//.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 }; xconn.select_xinput_events(window.xwindow, ffi::XIAllMasterDevices, mask).queue(); // These properties must be set after mapping if window_attrs.maximized { window.set_maximized_inner(window_attrs.maximized).queue(); } if window_attrs.fullscreen.is_some() { window.set_fullscreen_inner(window_attrs.fullscreen.clone()).queue(); } if window_attrs.always_on_top { window.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 = 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), 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. xconn.sync_with_server() .map(|_| window) .map_err(|x_err| OsError( format!("X server returned error while building window: {:?}", x_err) )) } fn set_pid(&self) -> Option { let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") }; let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") }; unsafe { let (hostname, hostname_length) = { // 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is // the limit defined by OpenBSD. const MAXHOSTNAMELEN: usize = 256; let mut hostname: [c_char; MAXHOSTNAMELEN] = mem::uninitialized(); let status = libc::gethostname(hostname.as_mut_ptr(), hostname.len()); if status != 0 { return None; } hostname[MAXHOSTNAMELEN - 1] = '\0' as c_char; // a little extra safety let hostname_length = libc::strlen(hostname.as_ptr()); (hostname, hostname_length as usize) }; self.xconn.change_property( self.xwindow, pid_atom, ffi::XA_CARDINAL, util::PropMode::Replace, &[libc::getpid() as util::Cardinal], ).queue(); let flusher = self.xconn.change_property( self.xwindow, client_machine_atom, ffi::XA_STRING, util::PropMode::Replace, &hostname[0..hostname_length], ); Some(flusher) } } fn set_window_type(&self, window_type: util::WindowType) -> util::Flusher { let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_WINDOW_TYPE\0") }; let window_type_atom = window_type.as_atom(&self.xconn); self.xconn.change_property( self.xwindow, hint_atom, ffi::XA_ATOM, util::PropMode::Replace, &[window_type_atom], ) } pub fn set_urgent(&self, is_urgent: bool) { let mut wm_hints = self.xconn.get_wm_hints(self.xwindow).expect("`XGetWMHints` failed"); if is_urgent { (*wm_hints).flags |= ffi::XUrgencyHint; } else { (*wm_hints).flags &= !ffi::XUrgencyHint; } self.xconn.set_wm_hints(self.xwindow, wm_hints).flush().expect("Failed to set urgency hint"); } fn set_netwm( &self, operation: util::StateOperation, properties: (c_long, c_long, c_long, c_long), ) -> util::Flusher { let state_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE\0") }; self.xconn.send_client_msg( self.xwindow, self.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 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)) } 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(); } fn get_rect(&self) -> Option { // TODO: This might round-trip more times than needed. if let (Some(position), Some(size)) = (self.get_position(), self.get_outer_size()) { Some(util::Rect::new(position, size)) } else { None } } pub fn get_current_monitor(&self) -> X11MonitorId { get_monitor_for_window(&self.xconn, self.get_rect()).to_owned() } fn set_maximized_inner(&self, maximized: bool) -> util::Flusher { let horz_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_HORZ\0") }; let vert_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_VERT\0") }; self.set_netwm(maximized.into(), (horz_atom as c_long, vert_atom as c_long, 0, 0)) } 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 wm_name_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_NAME\0") }; let utf8_atom = unsafe { self.xconn.get_atom_unchecked(b"UTF8_STRING\0") }; let title = CString::new(title).expect("Window title contained null byte"); unsafe { (self.xconn.xlib.XStoreName)( self.xconn.display, self.xwindow, title.as_ptr() as *const c_char, ); self.xconn.change_property( self.xwindow, wm_name_atom, utf8_atom, 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 wm_hints = unsafe { self.xconn.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") }; self.xconn.change_property( self.xwindow, wm_hints, wm_hints, 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(); } fn set_always_on_top_inner(&self, always_on_top: bool) -> util::Flusher { let above_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_ABOVE\0") }; self.set_netwm(always_on_top.into(), (above_atom as c_long, 0, 0, 0)) } pub fn set_always_on_top(&self, always_on_top: bool) { self.set_always_on_top_inner(always_on_top) .flush() .expect("Failed to set always-on-top state"); } fn set_icon_inner(&self, icon: Icon) -> util::Flusher { let icon_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_ICON\0") }; let data = icon.to_cardinals(); self.xconn.change_property( self.xwindow, icon_atom, ffi::XA_CARDINAL, util::PropMode::Replace, data.as_slice(), ) } fn unset_icon_inner(&self) -> util::Flusher { let icon_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_ICON\0") }; let empty_data: [util::Cardinal; 0] = []; self.xconn.change_property( self.xwindow, icon_atom, ffi::XA_CARDINAL, util::PropMode::Replace, &empty_data, ) } pub fn set_window_icon(&self, icon: Option) { match icon { Some(icon) => self.set_icon_inner(icon), None => self.unset_icon_inner(), }.flush().expect("Failed to set icons"); } pub fn show(&self) { unsafe { (self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow); self.xconn.flush_requests() .expect("Failed to call XMapRaised"); } } pub fn hide(&self) { unsafe { (self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow); self.xconn.flush_requests() .expect("Failed to call XUnmapWindow"); } } fn update_cached_frame_extents(&self) { let extents = self.xconn.get_frame_extents_heuristic(self.xwindow, self.root); (*self.shared_state.lock()).frame_extents = Some(extents); } pub 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)> { self.xconn.translate_coords(self.xwindow, self.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.xconn.xlib.XMoveWindow)( self.xconn.display, self.xwindow, x as c_int, y as c_int, ); self.xconn.flush_requests() }.expect("Failed to call XMoveWindow"); } #[inline] pub fn get_inner_size(&self) -> Option<(u32, u32)> { self.xconn.get_geometry(self.xwindow) .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, width: u32, height: u32) { unsafe { (self.xconn.xlib.XResizeWindow)( self.xconn.display, self.xwindow, width as c_uint, height as c_uint, ); self.xconn.flush_requests() }.expect("Failed to call XResizeWindow"); } unsafe fn update_normal_hints(&self, callback: F) -> Result<(), XError> where F: FnOnce(*mut ffi::XSizeHints) -> () { let size_hints = self.xconn.alloc_size_hints(); let mut flags: c_long = mem::uninitialized(); (self.xconn.xlib.XGetWMNormalHints)( self.xconn.display, self.xwindow, size_hints.ptr, &mut flags, ); self.xconn.check_errors()?; callback(size_hints.ptr); (self.xconn.xlib.XSetWMNormalHints)( self.xconn.display, self.xwindow, size_hints.ptr, ); self.xconn.flush_requests()?; Ok(()) } pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) { (*self.shared_state.lock()).min_dimensions = dimensions; 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)>) { (*self.shared_state.lock()).max_dimensions = dimensions; 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"); } pub fn set_resizable(&self, resizable: bool) { if util::wm_name_is_one_of(&["Xfwm4"]) { // Making the window unresizable on Xfwm prevents further changes to `WM_NORMAL_HINTS` from being detected. // This makes it impossible for resizing to be re-enabled, and also breaks DPI scaling. As such, we choose // the lesser of two evils and do nothing. return; } if resizable { let min_dimensions = (*self.shared_state.lock()).min_dimensions; let max_dimensions = (*self.shared_state.lock()).max_dimensions; self.set_min_dimensions(min_dimensions); self.set_max_dimensions(max_dimensions); } else { unsafe { self.update_normal_hints(|size_hints| { (*size_hints).flags |= ffi::PMinSize | ffi::PMaxSize; if let Some((width, height)) = self.get_inner_size() { (*size_hints).min_width = width as c_int; (*size_hints).min_height = height as c_int; (*size_hints).max_width = width as c_int; (*size_hints).max_height = height as c_int; } }) }.expect("Failed to call XSetWMNormalHints"); } } #[inline] pub fn get_xlib_display(&self) -> *mut c_void { self.xconn.display as _ } #[inline] pub fn get_xlib_screen_id(&self) -> c_int { self.screen_id } #[inline] pub fn get_xlib_xconnection(&self) -> Arc { Arc::clone(&self.xconn) } #[inline] pub fn platform_display(&self) -> *mut libc::c_void { self.xconn.display as _ } #[inline] pub fn get_xlib_window(&self) -> c_ulong { self.xwindow } #[inline] pub fn platform_window(&self) -> *mut libc::c_void { self.xwindow as _ } pub fn get_xcb_connection(&self) -> *mut c_void { unsafe { (self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _ } } fn load_cursor(&self, name: &[u8]) -> ffi::Cursor { unsafe { (self.xconn.xcursor.XcursorLibraryLoadCursor)( self.xconn.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.xconn.xlib.XDefineCursor)(self.xconn.display, self.xwindow, cursor); if cursor != 0 { (self.xconn.xlib.XFreeCursor)(self.xconn.display, cursor); } self.xconn.flush_requests().expect("Failed to set or free the cursor"); } } pub fn set_cursor(&self, cursor: MouseCursor) { *self.cursor.lock() = cursor; if *self.cursor_state.lock() != CursorState::Hide { self.update_cursor(self.get_cursor(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.xconn.xlib.XCreateBitmapFromData)( self.xconn.display, self.xwindow, &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.xconn.xlib.XCreatePixmapCursor)( self.xconn.display, pixmap, pixmap, &dummy_color as *const _ as *mut _, &dummy_color as *const _ as *mut _, 0, 0, ); (self.xconn.xlib.XFreePixmap)(self.xconn.display, pixmap); cursor }; Some(cursor) } pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> { use CursorState::*; let mut cursor_state_lock = self.cursor_state.lock(); match (state, *cursor_state_lock) { (Normal, Normal) | (Hide, Hide) | (Grab, Grab) => return Ok(()), _ => {}, } match *cursor_state_lock { Grab => { unsafe { (self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime); self.xconn.flush_requests().expect("Failed to call XUngrabPointer"); } }, Normal => {}, Hide => self.update_cursor(self.get_cursor(*self.cursor.lock())), } match state { Normal => { *cursor_state_lock = state; Ok(()) }, Hide => { *cursor_state_lock = 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.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime); match (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 ) { ffi::GrabSuccess => { *cursor_state_lock = 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 { self.get_current_monitor().hidpi_factor } pub fn set_cursor_position(&self, x: i32, y: i32) -> Result<(), ()> { unsafe { (self.xconn.xlib.XWarpPointer)( self.xconn.display, 0, self.xwindow, 0, 0, 0, 0, x, y, ); self.xconn.flush_requests().map_err(|_| ()) } } #[inline] pub fn id(&self) -> WindowId { WindowId(self.xwindow) } }