mirror of
https://github.com/rust-windowing/winit.git
synced 2026-06-27 07:03:15 -04:00
988 lines
36 KiB
Rust
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) }
|
|
}
|