Files
winit/src/platform/linux/x11/window.rs
2018-05-29 07:48:47 -04:00

988 lines
36 KiB
Rust

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<X11MonitorId>,
pub dpi_adjusted: Option<(f64, f64)>,
pub frame_extents: Option<util::FrameExtentsHeuristic>,
}
unsafe impl Send for UnownedWindow {}
unsafe impl Sync for UnownedWindow {}
pub struct UnownedWindow {
pub xconn: Arc<XConnection>, // never changes
xwindow: ffi::Window, // never changes
root: ffi::Window, // never changes
screen_id: i32, // never changes
cursor: Mutex<MouseCursor>,
cursor_state: Mutex<CursorState>,
pub multitouch: bool, // never changes
pub shared_state: Mutex<SharedState>,
}
impl UnownedWindow {
pub fn new(
event_loop: &EventsLoop,
window_attrs: WindowAttributes,
pl_attribs: PlatformSpecificWindowBuilderAttributes,
) -> Result<UnownedWindow, CreationError> {
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;
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;
}
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
{
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)) = 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,
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<util::Flusher> {
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<RootMonitorId>) -> 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<RootMonitorId>) {
self.set_fullscreen_inner(monitor)
.flush()
.expect("Failed to change window fullscreen state");
self.invalidate_cached_frame_extents();
}
fn get_rect(&self) -> Option<util::Rect> {
// 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<Icon>) {
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<F>(&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)>) {
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.xconn.display as _
}
#[inline]
pub fn get_xlib_screen_id(&self) -> c_int {
self.screen_id
}
#[inline]
pub fn get_xlib_xconnection(&self) -> Arc<XConnection> {
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<ffi::Cursor> {
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) }
}