winit-sonoma-fix/src/platform/linux/x11/window.rs
Francesca Sunshine 8f18dab061
x11: Send window maximization hints correctly (#363)
Previously, the maximization hints were being sent as two separate client messages: one for
horizontal, and one for vertical. That resulted in the window only being maximized
horizontally (at least with my WM). The corrected client message sets both of these hints at
once.

In the process of implementing that, the relevant components were refactored to use the util
module, as we gradually move towards a hopeful future of a more readable X11 backend.
2017-12-15 14:37:09 -05:00

773 lines
29 KiB
Rust

use MouseCursor;
use CreationError;
use CreationError::OsError;
use libc;
use std::borrow::Borrow;
use std::{mem, cmp};
use std::sync::{Arc, Mutex};
use std::os::raw::{c_int, c_long, c_uchar};
use std::thread;
use std::time::Duration;
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, WindowId, EventsLoop};
// TODO: remove me
fn with_c_str<F, T>(s: &str, f: F) -> T where F: FnOnce(*const libc::c_char) -> T {
use std::ffi::CString;
let c_str = CString::new(s.as_bytes().to_vec()).unwrap();
f(c_str.as_ptr())
}
#[derive(Debug)]
enum StateOperation {
Remove = 0, // _NET_WM_STATE_REMOVE
Add = 1, // _NET_WM_STATE_ADD
#[allow(dead_code)]
Toggle = 2, // _NET_WM_STATE_TOGGLE
}
impl From<bool> for StateOperation {
fn from(b: bool) -> Self {
if b {
StateOperation::Add
} else {
StateOperation::Remove
}
}
}
pub struct XWindow {
display: Arc<XConnection>,
window: ffi::Window,
root: ffi::Window,
screen_id: i32,
}
unsafe impl Send for XWindow {}
unsafe impl Sync for XWindow {}
unsafe impl Send for Window2 {}
unsafe impl Sync for Window2 {}
pub struct Window2 {
pub x: Arc<XWindow>,
cursor_state: Mutex<CursorState>,
}
impl Window2 {
pub fn new(ctx: &EventsLoop, window_attrs: &WindowAttributes,
pl_attribs: &PlatformSpecificWindowBuilderAttributes)
-> Result<Window2, CreationError>
{
let display = &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 { (display.xlib.XDefaultScreen)(display.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;
(display.xlib.XCreateColormap)(display.display, root, visual, ffi::AllocNone)
}
} else { 0 };
swa.event_mask = ffi::ExposureMask | ffi::StructureNotifyMask |
ffi::VisibilityChangeMask | ffi::KeyPressMask | ffi::PointerMotionMask |
ffi::KeyReleaseMask | ffi::ButtonPressMask |
ffi::ButtonReleaseMask | ffi::KeymapStateMask;
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 {
let win = (display.xlib.XCreateWindow)(display.display, root, 0, 0, dimensions.0 as libc::c_uint,
dimensions.1 as libc::c_uint, 0,
match pl_attribs.visual_infos {
Some(vi) => vi.depth,
None => ffi::CopyFromParent
},
ffi::InputOutput as libc::c_uint,
match pl_attribs.visual_infos {
Some(vi) => vi.visual,
None => ffi::CopyFromParent as *mut _
},
window_attributes,
&mut set_win_attr);
display.check_errors().expect("Failed to call XCreateWindow");
win
};
let window = Window2 {
x: Arc::new(XWindow {
display: display.clone(),
window,
root,
screen_id,
}),
cursor_state: Mutex::new(CursorState::Normal),
};
// Title must be set before mapping, lest some tiling window managers briefly pick up on
// the initial un-titled window state
window.set_title(&window_attrs.title);
window.set_decorations(window_attrs.decorations);
{
let ref x_window: &XWindow = window.x.borrow();
// Enable drag and drop
unsafe {
let atom = util::get_atom(display, b"XdndAware\0")
.expect("Failed to call XInternAtom (XdndAware)");
let version = &5; // Latest version; hasn't changed since 2002
(display.xlib.XChangeProperty)(
display.display,
x_window.window,
atom,
ffi::XA_ATOM,
32,
ffi::PropModeReplace,
version,
1
);
display.check_errors().expect("Failed to set drag and drop properties");
}
// Set ICCCM WM_CLASS property based on initial window title
// Must be done *before* mapping the window by ICCCM 4.1.2.5
unsafe {
with_c_str(&*window_attrs.title, |c_name| {
let hint = (display.xlib.XAllocClassHint)();
(*hint).res_name = c_name as *mut libc::c_char;
(*hint).res_class = c_name as *mut libc::c_char;
(display.xlib.XSetClassHint)(display.display, x_window.window, hint);
display.check_errors().expect("Failed to call XSetClassHint");
(display.xlib.XFree)(hint as *mut _);
});
}
// set size hints
let mut size_hints: ffi::XSizeHints = unsafe { mem::zeroed() };
size_hints.flags = ffi::PSize;
size_hints.width = dimensions.0 as i32;
size_hints.height = dimensions.1 as i32;
if let Some(dimensions) = window_attrs.min_dimensions {
size_hints.flags |= ffi::PMinSize;
size_hints.min_width = dimensions.0 as i32;
size_hints.min_height = dimensions.1 as i32;
}
if let Some(dimensions) = window_attrs.max_dimensions {
size_hints.flags |= ffi::PMaxSize;
size_hints.max_width = dimensions.0 as i32;
size_hints.max_height = dimensions.1 as i32;
}
unsafe {
(display.xlib.XSetNormalHints)(display.display, x_window.window, &mut size_hints);
display.check_errors().expect("Failed to call XSetNormalHints");
}
// Opt into handling window close
unsafe {
(display.xlib.XSetWMProtocols)(display.display, x_window.window, &ctx.wm_delete_window as *const _ as *mut _, 1);
display.check_errors().expect("Failed to call XSetWMProtocols");
(display.xlib.XFlush)(display.display);
display.check_errors().expect("Failed to call XFlush");
}
// Set visibility (map window)
if window_attrs.visible {
unsafe {
(display.xlib.XMapRaised)(display.display, x_window.window);
(display.xlib.XFlush)(display.display);
}
display.check_errors().expect("Failed to set window visibility");
}
// Attempt to make keyboard input repeat detectable
unsafe {
let mut supported_ptr = ffi::False;
(display.xlib.XkbSetDetectableAutoRepeat)(display.display, ffi::True, &mut supported_ptr);
if supported_ptr == ffi::False {
return Err(OsError(format!("XkbSetDetectableAutoRepeat failed")));
}
}
// Select XInput2 events
{
let 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 { ffi::XI_TouchBeginMask | ffi::XI_TouchUpdateMask | ffi::XI_TouchEndMask } else { 0 };
unsafe {
let mut event_mask = ffi::XIEventMask{
deviceid: ffi::XIAllMasterDevices,
mask: mem::transmute::<*const i32, *mut c_uchar>(&mask as *const i32),
mask_len: mem::size_of_val(&mask) as c_int,
};
(display.xinput2.XISelectEvents)(display.display, x_window.window,
&mut event_mask as *mut ffi::XIEventMask, 1);
};
}
// These properties must be set after mapping
window.set_maximized(window_attrs.maximized);
window.set_fullscreen(window_attrs.fullscreen.clone());
if window_attrs.visible {
unsafe {
// XSetInputFocus generates an error if the window is not visible,
// therefore we wait until it's the case.
loop {
let mut window_attributes = mem::uninitialized();
(display.xlib.XGetWindowAttributes)(display.display, x_window.window, &mut window_attributes);
display.check_errors().expect("Failed to call XGetWindowAttributes");
if window_attributes.map_state == ffi::IsViewable {
(display.xlib.XSetInputFocus)(
display.display,
x_window.window,
ffi::RevertToParent,
ffi::CurrentTime
);
display.check_errors().expect("Failed to call XSetInputFocus");
break;
}
// Wait about a frame to avoid too-busy waiting
thread::sleep(Duration::from_millis(16));
}
}
}
}
// returning
Ok(window)
}
fn set_netwm(
xconn: &Arc<XConnection>,
window: ffi::Window,
root: ffi::Window,
properties: (c_long, c_long, c_long, c_long),
operation: StateOperation
) {
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,
)
)
}.expect("Failed to send NET_WM hint.");
}
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
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);
}
_ => {
eprintln!("[winit] Something's broken, got an unknown fullscreen state in X11");
}
}
}
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
}
pub fn set_maximized(&self, maximized: bool) {
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()
);
}
fn set_fullscreen_hint(&self, fullscreen: bool) {
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()
);
}
pub fn set_title(&self, title: &str) {
let wm_name = unsafe {
(self.x.display.xlib.XInternAtom)(self.x.display.display, b"_NET_WM_NAME\0".as_ptr() as *const _, 0)
};
self.x.display.check_errors().expect("Failed to call XInternAtom");
let wm_utf8_string = unsafe {
(self.x.display.xlib.XInternAtom)(self.x.display.display, b"UTF8_STRING\0".as_ptr() as *const _, 0)
};
self.x.display.check_errors().expect("Failed to call XInternAtom");
with_c_str(title, |c_title| unsafe {
(self.x.display.xlib.XStoreName)(self.x.display.display, self.x.window, c_title);
let len = title.as_bytes().len();
(self.x.display.xlib.XChangeProperty)(self.x.display.display, self.x.window,
wm_name, wm_utf8_string, 8, ffi::PropModeReplace,
c_title as *const u8, len as libc::c_int);
(self.x.display.xlib.XFlush)(self.x.display.display);
});
self.x.display.check_errors().expect("Failed to set window title");
}
pub fn set_decorations(&self, decorations: bool) {
#[repr(C)]
struct MotifWindowHints {
flags: u32,
functions: u32,
decorations: u32,
input_mode: i32,
status: u32,
}
let wm_hints = unsafe {
(self.x.display.xlib.XInternAtom)(self.x.display.display, b"_MOTIF_WM_HINTS\0".as_ptr() as *const _, 0)
};
self.x.display.check_errors().expect("Failed to call XInternAtom");
if !decorations {
let hints = MotifWindowHints {
flags: 2, // MWM_HINTS_DECORATIONS
functions: 0,
decorations: 0,
input_mode: 0,
status: 0,
};
unsafe {
(self.x.display.xlib.XChangeProperty)(
self.x.display.display, self.x.window,
wm_hints, wm_hints, 32 /* Size of elements in struct */,
ffi::PropModeReplace, &hints as *const MotifWindowHints as *const u8,
5 /* Number of elements in struct */);
(self.x.display.xlib.XFlush)(self.x.display.display);
}
} else {
unsafe {
(self.x.display.xlib.XDeleteProperty)(self.x.display.display, self.x.window, wm_hints);
(self.x.display.xlib.XFlush)(self.x.display.display);
}
}
self.x.display.check_errors().expect("Failed to set decorations");
}
pub fn show(&self) {
unsafe {
(self.x.display.xlib.XMapRaised)(self.x.display.display, self.x.window);
(self.x.display.xlib.XFlush)(self.x.display.display);
self.x.display.check_errors().expect("Failed to call XMapRaised");
}
}
pub fn hide(&self) {
unsafe {
(self.x.display.xlib.XUnmapWindow)(self.x.display.display, self.x.window);
(self.x.display.xlib.XFlush)(self.x.display.display);
self.x.display.check_errors().expect("Failed to call XUnmapWindow");
}
}
fn get_geometry(&self) -> Option<(i32, i32, u32, u32, u32)> {
unsafe {
use std::mem;
let mut root: ffi::Window = mem::uninitialized();
let mut x: libc::c_int = mem::uninitialized();
let mut y: libc::c_int = mem::uninitialized();
let mut width: libc::c_uint = mem::uninitialized();
let mut height: libc::c_uint = mem::uninitialized();
let mut border: libc::c_uint = mem::uninitialized();
let mut depth: libc::c_uint = mem::uninitialized();
if (self.x.display.xlib.XGetGeometry)(self.x.display.display, self.x.window,
&mut root, &mut x, &mut y, &mut width, &mut height,
&mut border, &mut depth) == 0
{
return None;
}
Some((x as i32, y as i32, width as u32, height as u32, border as u32))
}
}
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
self.get_geometry().map(|(x, y, _, _, _)| (x, y))
}
pub fn set_position(&self, x: i32, y: i32) {
unsafe { (self.x.display.xlib.XMoveWindow)(self.x.display.display, self.x.window, x as libc::c_int, y as libc::c_int); }
self.x.display.check_errors().expect("Failed to call XMoveWindow");
}
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
self.get_geometry().map(|(_, _, w, h, _)| (w, h))
}
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
self.get_geometry().map(|(_, _, w, h, b)| (w + b, h + b)) // TODO: is this really outside?
}
#[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 libc::c_uint, y as libc::c_uint); }
self.x.display.check_errors().expect("Failed to call XResizeWindow");
}
#[inline]
pub fn get_xlib_display(&self) -> *mut libc::c_void {
self.x.display.display as *mut libc::c_void
}
#[inline]
pub fn get_xlib_screen_id(&self) -> *mut libc::c_void {
self.x.screen_id as *mut libc::c_void
}
#[inline]
pub fn get_xlib_xconnection(&self) -> Arc<XConnection> {
self.x.display.clone()
}
#[inline]
pub fn platform_display(&self) -> *mut libc::c_void {
self.x.display.display as *mut libc::c_void
}
#[inline]
pub fn get_xlib_window(&self) -> *mut libc::c_void {
self.x.window as *mut libc::c_void
}
#[inline]
pub fn platform_window(&self) -> *mut libc::c_void {
self.x.window as *mut libc::c_void
}
pub fn get_xcb_connection(&self) -> *mut libc::c_void {
unsafe {
(self.x.display.xlib_xcb.XGetXCBConnection)(self.get_xlib_display() as *mut _) as *mut _
}
}
pub fn set_cursor(&self, cursor: MouseCursor) {
unsafe {
let load = |name: &str| {
self.load_cursor(name)
};
let loadn = |names: &[&str]| {
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.
let xcursor = match cursor {
MouseCursor::Alias => load("link"),
MouseCursor::Arrow => load("arrow"),
MouseCursor::Cell => load("plus"),
MouseCursor::Copy => load("copy"),
MouseCursor::Crosshair => load("crosshair"),
MouseCursor::Default => load("left_ptr"),
MouseCursor::Hand => loadn(&["hand2", "hand1"]),
MouseCursor::Help => load("question_arrow"),
MouseCursor::Move => load("move"),
MouseCursor::Grab => loadn(&["openhand", "grab"]),
MouseCursor::Grabbing => loadn(&["closedhand", "grabbing"]),
MouseCursor::Progress => load("left_ptr_watch"),
MouseCursor::AllScroll => load("all-scroll"),
MouseCursor::ContextMenu => load("context-menu"),
MouseCursor::NoDrop => loadn(&["no-drop", "circle"]),
MouseCursor::NotAllowed => load("crossed_circle"),
// Resize cursors
MouseCursor::EResize => load("right_side"),
MouseCursor::NResize => load("top_side"),
MouseCursor::NeResize => load("top_right_corner"),
MouseCursor::NwResize => load("top_left_corner"),
MouseCursor::SResize => load("bottom_side"),
MouseCursor::SeResize => load("bottom_right_corner"),
MouseCursor::SwResize => load("bottom_left_corner"),
MouseCursor::WResize => load("left_side"),
MouseCursor::EwResize => load("h_double_arrow"),
MouseCursor::NsResize => load("v_double_arrow"),
MouseCursor::NwseResize => loadn(&["bd_double_arrow", "size_bdiag"]),
MouseCursor::NeswResize => loadn(&["fd_double_arrow", "size_fdiag"]),
MouseCursor::ColResize => loadn(&["split_h", "h_double_arrow"]),
MouseCursor::RowResize => loadn(&["split_v", "v_double_arrow"]),
MouseCursor::Text => loadn(&["text", "xterm"]),
MouseCursor::VerticalText => load("vertical-text"),
MouseCursor::Wait => load("watch"),
MouseCursor::ZoomIn => load("zoom-in"),
MouseCursor::ZoomOut => load("zoom-out"),
MouseCursor::NoneCursor => self.create_empty_cursor(),
};
(self.x.display.xlib.XDefineCursor)(self.x.display.display, self.x.window, xcursor);
if xcursor != 0 {
(self.x.display.xlib.XFreeCursor)(self.x.display.display, xcursor);
}
self.x.display.check_errors().expect("Failed to set or free the cursor");
}
}
fn load_cursor(&self, name: &str) -> ffi::Cursor {
use std::ffi::CString;
unsafe {
let c_string = CString::new(name.as_bytes()).unwrap();
(self.x.display.xcursor.XcursorLibraryLoadCursor)(self.x.display.display, c_string.as_ptr())
}
}
fn load_first_existing_cursor(&self, names :&[&str]) -> ffi::Cursor {
for name in names.iter() {
let xcursor = self.load_cursor(name);
if xcursor != 0 {
return xcursor;
}
}
0
}
// 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) -> ffi::Cursor {
use std::mem;
let data = 0;
unsafe {
let pixmap = (self.x.display.xlib.XCreateBitmapFromData)(self.x.display.display, self.x.window, &data, 1, 1);
if pixmap == 0 {
// Failed to allocate
return 0;
}
// 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
}
}
pub fn set_cursor_state(&self, state: CursorState) -> Result<(), String> {
use CursorState::{ Grab, Normal, Hide };
let mut cursor_state = self.cursor_state.lock().unwrap();
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);
self.x.display.check_errors().expect("Failed to call XUngrabPointer");
}
},
Normal => {},
Hide => {
// NB: Calling XDefineCursor with None (aka 0)
// as a value resets the cursor to the default.
unsafe {
(self.x.display.xlib.XDefineCursor)(self.x.display.display, self.x.window, 0);
}
},
}
*cursor_state = state;
match state {
Normal => Ok(()),
Hide => {
unsafe {
let cursor = self.create_empty_cursor();
(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);
}
self.x.display.check_errors().expect("Failed to call XDefineCursor or free the empty cursor");
}
Ok(())
},
Grab => {
unsafe {
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 libc::c_uint,
ffi::GrabModeAsync, ffi::GrabModeAsync,
self.x.window, 0, ffi::CurrentTime
) {
ffi::GrabSuccess => 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);
self.x.display.check_errors().map_err(|_| ())
}
}
#[inline]
pub fn id(&self) -> WindowId { WindowId(self.x.window) }
}