winit-sonoma-fix/src/platform/linux/x11/window.rs

1267 lines
47 KiB
Rust
Raw Normal View History

use MouseCursor;
use CreationError;
use CreationError::OsError;
use libc;
use std::borrow::Borrow;
use std::{mem, cmp, ptr};
use std::sync::{Arc, Mutex};
use std::os::raw::{c_int, c_long, c_uchar, c_uint, c_ulong, c_void};
use std::thread;
use std::time::Duration;
use CursorState;
use WindowAttributes;
use platform::PlatformSpecificWindowBuilderAttributes;
2015-09-24 17:11:59 +10:00
use platform::MonitorId as PlatformMonitorId;
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 18:33:46 +10:00
use platform::x11::MonitorId as X11MonitorId;
use window::MonitorId as RootMonitorId;
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 18:33:46 +10:00
use platform::x11::monitor::get_available_monitors;
use super::{ffi, util, XConnection, XError, 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,
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 18:33:46 +10:00
screen_id: i32,
}
impl XWindow {
/// Get parent window of `child`
///
/// This method can return None if underlying xlib call fails.
///
/// # Unsafety
///
/// `child` must be a valid `Window`.
unsafe fn get_parent_window(&self, child: ffi::Window) -> Option<ffi::Window> {
let mut root: ffi::Window = mem::uninitialized();
let mut parent: ffi::Window = mem::uninitialized();
let mut children: *mut ffi::Window = ptr::null_mut();
let mut nchildren: libc::c_uint = mem::uninitialized();
let res = (self.display.xlib.XQueryTree)(
self.display.display,
child,
&mut root,
&mut parent,
&mut children,
&mut nchildren
);
if res == 0 {
return None;
}
// The list of children isn't used
if children != ptr::null_mut() {
(self.display.xlib.XFree)(children as *mut _);
}
Some(parent)
}
}
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: Mutex<MouseCursor>,
cursor_state: Mutex<CursorState>,
supported_hints: Vec<ffi::Atom>,
wm_name: Option<String>,
}
fn get_supported_hints(xwin: &Arc<XWindow>) -> Vec<ffi::Atom> {
let supported_atom = unsafe { util::get_atom(&xwin.display, b"_NET_SUPPORTED\0") }
.expect("Failed to call XInternAtom (_NET_SUPPORTED)");
unsafe {
util::get_property(
&xwin.display,
xwin.root,
supported_atom,
ffi::XA_ATOM,
)
}.unwrap_or_else(|_| Vec::with_capacity(0))
}
fn get_wm_name(xwin: &Arc<XWindow>, _supported_hints: &[ffi::Atom]) -> Option<String> {
let check_atom = unsafe { util::get_atom(&xwin.display, b"_NET_SUPPORTING_WM_CHECK\0") }
.expect("Failed to call XInternAtom (_NET_SUPPORTING_WM_CHECK)");
let wm_name_atom = unsafe { util::get_atom(&xwin.display, b"_NET_WM_NAME\0") }
.expect("Failed to call XInternAtom (_NET_WM_NAME)");
// Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite
// it working and being supported. This has been reported upstream, but due to the
// inavailability of time machines, we'll just try to get _NET_SUPPORTING_WM_CHECK
// regardless of whether or not the WM claims to support it.
//
// Blackbox 0.70 also incorrectly reports not supporting this, though that appears to be fixed
// in 0.72.
/*if !supported_hints.contains(&check_atom) {
return None;
}*/
// IceWM (1.3.x and earlier) doesn't report supporting _NET_WM_NAME, but will nonetheless
// provide us with a value for it. Note that the unofficial 1.4 fork of IceWM works fine.
/*if !supported_hints.contains(&wm_name_atom) {
return None;
}*/
// Of the WMs tested, only xmonad and dwm fail to provide a WM name.
// Querying this property on the root window will give us the ID of a child window created by
// the WM.
let root_window_wm_check = {
let result = unsafe {
util::get_property(
&xwin.display,
xwin.root,
check_atom,
ffi::XA_WINDOW,
)
};
let wm_check = result
.ok()
.and_then(|wm_check| wm_check.get(0).cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
};
// Querying the same property on the child window we were given, we should get this child
// window's ID again.
let child_window_wm_check = {
let result = unsafe {
util::get_property(
&xwin.display,
root_window_wm_check,
check_atom,
ffi::XA_WINDOW,
)
};
let wm_check = result
.ok()
.and_then(|wm_check| wm_check.get(0).cloned());
if let Some(wm_check) = wm_check {
wm_check
} else {
return None;
}
};
// These values should be the same.
if root_window_wm_check != child_window_wm_check {
return None;
}
// All of that work gives us a window ID that we can get the WM name from.
let wm_name = {
let utf8_string_atom = unsafe { util::get_atom(&xwin.display, b"UTF8_STRING\0") }
.unwrap_or(ffi::XA_STRING);
let result = unsafe {
util::get_property(
&xwin.display,
root_window_wm_check,
wm_name_atom,
utf8_string_atom,
)
};
// IceWM requires this. IceWM was also the only WM tested that returns a null-terminated
// string. For more fun trivia, IceWM is also unique in including version and uname
// information in this string (this means you'll have to be careful if you want to match
// against it, though).
// The unofficial 1.4 fork of IceWM still includes the extra details, but properly
// returns a UTF8 string that isn't null-terminated.
let no_utf8 = if let Err(ref err) = result {
err.is_actual_property_type(ffi::XA_STRING)
} else {
false
};
if no_utf8 {
unsafe {
util::get_property(
&xwin.display,
root_window_wm_check,
wm_name_atom,
ffi::XA_STRING,
)
}
} else {
result
}
}.ok();
wm_name.and_then(|wm_name| String::from_utf8(wm_name).ok())
}
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,
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 18:33:46 +10:00
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 {
2015-06-15 07:20:32 +10:00
swa.background_pixel = 0;
}
swa.override_redirect = 0;
swa
};
let mut window_attributes = ffi::CWBorderPixel | ffi::CWColormap | ffi::CWEventMask;
2015-06-15 07:20:32 +10:00
if window_attrs.transparent {
2015-06-15 07:20:32 +10:00
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);
2015-12-24 20:57:08 +11:00
display.check_errors().expect("Failed to call XCreateWindow");
win
};
let x_window = Arc::new(XWindow {
display: display.clone(),
window,
root,
screen_id,
});
// These values will cease to be correct if the user replaces the WM during the life of
// the window, so hopefully they don't do that.
let supported_hints = get_supported_hints(&x_window);
let wm_name = get_wm_name(&x_window, &supported_hints);
let window = Window2 {
x: x_window,
cursor: Mutex::new(MouseCursor::Default),
cursor_state: Mutex::new(CursorState::Normal),
supported_hints,
wm_name,
};
// 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");
}
2015-12-24 20:57:08 +11:00
// 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 = {
let size_hints = unsafe { (display.xlib.XAllocSizeHints)() };
util::XSmartPointer::new(&display, size_hints)
.expect("XAllocSizeHints returned null; out of memory")
};
(*size_hints).flags = ffi::PSize;
(*size_hints).width = dimensions.0 as c_int;
(*size_hints).height = dimensions.1 as c_int;
if let Some(dimensions) = window_attrs.min_dimensions {
(*size_hints).flags |= ffi::PMinSize;
(*size_hints).min_width = dimensions.0 as c_int;
(*size_hints).min_height = dimensions.1 as c_int;
}
if let Some(dimensions) = window_attrs.max_dimensions {
(*size_hints).flags |= ffi::PMaxSize;
(*size_hints).max_width = dimensions.0 as c_int;
(*size_hints).max_height = dimensions.1 as c_int;
}
unsafe {
(display.xlib.XSetWMNormalHints)(
display.display,
x_window.window,
size_hints.ptr,
);
}
display.check_errors().expect("Failed to call XSetWMNormalHints");
}
// 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);
}
2015-10-26 18:40:37 +11:00
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.");
}
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 18:33:46 +10:00
pub fn set_fullscreen(&self, monitor: Option<RootMonitorId>) {
match monitor {
None => {
self.set_fullscreen_hint(false);
},
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 18:33:46 +10:00
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");
}
}
}
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 18:33:46 +10:00
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;
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 18:33:46 +10:00
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()
);
}
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 18:33:46 +10:00
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()
);
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 18:33:46 +10:00
}
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);
2015-12-24 20:57:08 +11:00
});
self.x.display.check_errors().expect("Failed to set window title");
2015-12-24 20:57:08 +11:00
}
pub fn set_decorations(&self, decorations: bool) {
#[repr(C)]
struct MotifWindowHints {
flags: c_ulong,
functions: c_ulong,
decorations: c_ulong,
input_mode: c_long,
status: c_ulong,
}
let wm_hints = unsafe { util::get_atom(&self.x.display, b"_MOTIF_WM_HINTS\0") }
.expect("Failed to call XInternAtom (_MOTIF_WM_HINTS)");
let hints = MotifWindowHints {
flags: 2, // MWM_HINTS_DECORATIONS
functions: 0,
decorations: decorations as _,
input_mode: 0,
status: 0,
};
unsafe {
(self.x.display.xlib.XChangeProperty)(
self.x.display.display,
self.x.window,
wm_hints,
wm_hints,
32, // struct members are longs
ffi::PropModeReplace,
&hints as *const _ as *const u8,
5 // struct has 5 members
);
(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);
2015-12-24 20:57:08 +11:00
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);
2015-12-24 20:57:08 +11:00
self.x.display.check_errors().expect("Failed to call XUnmapWindow");
}
}
fn get_frame_extents(&self) -> Option<util::FrameExtents> {
let extents_atom = unsafe { util::get_atom(&self.x.display, b"_NET_FRAME_EXTENTS\0") }
.expect("Failed to call XInternAtom (_NET_FRAME_EXTENTS)");
if !self.supported_hints.contains(&extents_atom) {
return None;
}
// Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't
// support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to
// be unsupported by many smaller WMs.
let extents: Option<Vec<c_ulong>> = unsafe {
util::get_property(
&self.x.display,
self.x.window,
extents_atom,
ffi::XA_CARDINAL,
)
}.ok();
extents.and_then(|extents| {
if extents.len() >= 4 {
Some(util::FrameExtents {
left: extents[0],
right: extents[1],
top: extents[2],
bottom: extents[3],
})
} else {
None
}
})
}
fn is_top_level(&self, id: ffi::Window) -> Option<bool> {
let client_list_atom = unsafe { util::get_atom(&self.x.display, b"_NET_CLIENT_LIST\0") }
.expect("Failed to call XInternAtom (_NET_CLIENT_LIST)");
if !self.supported_hints.contains(&client_list_atom) {
return None;
}
let client_list: Option<Vec<ffi::Window>> = unsafe {
util::get_property(
&self.x.display,
self.x.root,
client_list_atom,
ffi::XA_WINDOW,
)
}.ok();
client_list.map(|client_list| {
client_list.contains(&id)
})
}
fn get_geometry(&self) -> Option<util::WindowGeometry> {
// Position relative to root window.
// With rare exceptions, this is the position of a nested window. Cases where the window
// isn't nested are outlined in the comments throghout this function, but in addition to
// that, fullscreen windows sometimes aren't nested.
let (inner_x_rel_root, inner_y_rel_root, child) = unsafe {
let mut inner_x_rel_root: c_int = mem::uninitialized();
let mut inner_y_rel_root: c_int = mem::uninitialized();
let mut child: ffi::Window = mem::uninitialized();
(self.x.display.xlib.XTranslateCoordinates)(
self.x.display.display,
self.x.window,
self.x.root,
0,
0,
&mut inner_x_rel_root,
&mut inner_y_rel_root,
&mut child,
);
(inner_x_rel_root, inner_y_rel_root, child)
};
let (inner_x, inner_y, width, height, border) = unsafe {
let mut root: ffi::Window = mem::uninitialized();
// The same caveat outlined in the comment above for XTranslateCoordinates applies
// here as well. The only difference is that this position is relative to the parent
// window, rather than the root window.
let mut inner_x: c_int = mem::uninitialized();
let mut inner_y: c_int = mem::uninitialized();
// The width and height here are for the client area.
let mut width: c_uint = mem::uninitialized();
let mut height: c_uint = mem::uninitialized();
// xmonad and dwm were the only WMs tested that use the border return at all.
// The majority of WMs seem to simply fill it with 0 unconditionally.
let mut border: c_uint = mem::uninitialized();
let mut depth: c_uint = mem::uninitialized();
let status = (self.x.display.xlib.XGetGeometry)(
self.x.display.display,
self.x.window,
&mut root,
&mut inner_x,
&mut inner_y,
&mut width,
&mut height,
&mut border,
&mut depth,
);
if status == 0 {
return None;
}
(inner_x, inner_y, width, height, border)
};
// The first condition is only false for un-nested windows, but isn't always false for
// un-nested windows. Mutter/Muffin/Budgie and Marco present a mysterious discrepancy:
// when y is on the range [0, 2] and if the window has been unfocused since being
// undecorated (or was undecorated upon construction), the first condition is true,
// requiring us to rely on the second condition.
let nested = !(self.x.window == child || self.is_top_level(child) == Some(true));
// Hopefully the WM supports EWMH, allowing us to get exact info on the window frames.
if let Some(mut extents) = self.get_frame_extents() {
// Mutter/Muffin/Budgie and Marco preserve their decorated frame extents when
// decorations are disabled, but since the window becomes un-nested, it's easy to
// catch.
if !nested {
extents = util::FrameExtents::new(0, 0, 0, 0);
}
// The difference between the nested window's position and the outermost window's
// position is equivalent to the frame size. In most scenarios, this is equivalent to
// manually climbing the hierarchy as is done in the case below. Here's a list of
// known discrepancies:
// * Mutter/Muffin/Budgie gives decorated windows a margin of 9px (only 7px on top) in
// addition to a 1px semi-transparent border. The margin can be easily observed by
// using a screenshot tool to get a screenshot of a selected window, and is
// presumably used for drawing drop shadows. Getting window geometry information
// via hierarchy-climbing results in this margin being included in both the
// position and outer size, so a window positioned at (0, 0) would be reported as
// having a position (-10, -8).
// * Compiz has a drop shadow margin just like Mutter/Muffin/Budgie, though it's 10px
// on all sides, and there's no additional border.
// * Enlightenment otherwise gets a y position equivalent to inner_y_rel_root.
// Without decorations, there's no difference. This is presumably related to
// Enlightenment's fairly unique concept of window position; it interprets
// positions given to XMoveWindow as a client area position rather than a position
// of the overall window.
let abs_x = inner_x_rel_root - extents.left as c_int;
let abs_y = inner_y_rel_root - extents.top as c_int;
Some(util::WindowGeometry {
x: abs_x,
y: abs_y,
width,
height,
frame: extents,
})
} else if nested {
// If the position value we have is for a nested window used as the client area, we'll
// just climb up the hierarchy and get the geometry of the outermost window we're
// nested in.
let window = {
let root = self.x.root;
let mut window = self.x.window;
loop {
let candidate = unsafe {
self.x.get_parent_window(window).unwrap()
};
if candidate == root {
break window;
}
window = candidate;
}
};
let (outer_x, outer_y, outer_width, outer_height) = unsafe {
let mut root: ffi::Window = mem::uninitialized();
let mut outer_x: c_int = mem::uninitialized();
let mut outer_y: c_int = mem::uninitialized();
let mut outer_width: c_uint = mem::uninitialized();
let mut outer_height: c_uint = mem::uninitialized();
let mut border: c_uint = mem::uninitialized();
let mut depth: c_uint = mem::uninitialized();
let status = (self.x.display.xlib.XGetGeometry)(
self.x.display.display,
window,
&mut root,
&mut outer_x,
&mut outer_y,
&mut outer_width,
&mut outer_height,
&mut border,
&mut depth,
);
if status == 0 {
return None;
}
(outer_x, outer_y, outer_width, outer_height)
};
// Since we have the geometry of the outermost window and the geometry of the client
// area, we can figure out what's in between.
let frame = {
let diff_x = outer_width.saturating_sub(width);
let diff_y = outer_height.saturating_sub(height);
let offset_y = inner_y_rel_root.saturating_sub(outer_y) as c_uint;
let left = diff_x / 2;
let right = left;
let top = offset_y;
let bottom = diff_y.saturating_sub(offset_y);
util::FrameExtents::new(left.into(), right.into(), top.into(), bottom.into())
};
Some(util::WindowGeometry {
x: outer_x,
y: outer_y,
width,
height,
frame,
})
} else {
// This is the case for xmonad and dwm, AKA the only WMs tested that supplied a
// border value. This is convenient, since we can use it to get an accurate frame.
let frame = util::FrameExtents::from_border(border.into());
Some(util::WindowGeometry {
x: inner_x,
y: inner_y,
width,
height,
frame,
})
}
}
2015-09-21 22:42:05 +10:00
#[inline]
pub fn get_position(&self) -> Option<(i32, i32)> {
self.get_geometry().map(|geo| geo.get_position())
}
#[inline]
pub fn get_inner_position(&self) -> Option<(i32, i32)> {
self.get_geometry().map(|geo| geo.get_inner_position())
}
pub fn set_position(&self, mut x: i32, mut y: i32) {
if let Some(ref wm_name) = self.wm_name {
// There are a few WMs that set client area position rather than window position, so
// we'll translate for consistency.
if ["Enlightenment", "FVWM"].contains(&wm_name.as_str()) {
if let Some(extents) = self.get_frame_extents() {
x += extents.left as i32;
y += extents.top as i32;
}
}
}
unsafe {
(self.x.display.xlib.XMoveWindow)(
self.x.display.display,
self.x.window,
x as c_int,
y as c_int,
);
}
2015-12-24 20:57:08 +11:00
self.x.display.check_errors().expect("Failed to call XMoveWindow");
}
2015-09-21 22:42:05 +10:00
#[inline]
pub fn get_inner_size(&self) -> Option<(u32, u32)> {
self.get_geometry().map(|geo| geo.get_inner_size())
}
2015-09-21 22:42:05 +10:00
#[inline]
pub fn get_outer_size(&self) -> Option<(u32, u32)> {
self.get_geometry().map(|geo| geo.get_outer_size())
}
2015-09-21 22:42:05 +10:00
#[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); }
2015-12-24 20:57:08 +11:00
self.x.display.check_errors().expect("Failed to call XResizeWindow");
}
unsafe fn update_normal_hints<F>(&self, callback: F) -> Result<(), XError>
where F: FnOnce(*mut ffi::XSizeHints) -> ()
{
let xconn = &self.x.display;
let size_hints = {
let size_hints = (xconn.xlib.XAllocSizeHints)();
util::XSmartPointer::new(&xconn, size_hints)
.expect("XAllocSizeHints returned null; out of memory")
};
let mut flags: c_long = mem::uninitialized();
(xconn.xlib.XGetWMNormalHints)(
xconn.display,
self.x.window,
size_hints.ptr,
&mut flags,
);
xconn.check_errors()?;
callback(size_hints.ptr);
(xconn.xlib.XSetWMNormalHints)(
xconn.display,
self.x.window,
size_hints.ptr,
);
xconn.check_errors()?;
Ok(())
}
pub fn set_min_dimensions(&self, dimensions: Option<(u32, u32)>) {
unsafe {
self.update_normal_hints(|size_hints| {
if let Some((width, height)) = dimensions {
(*size_hints).flags |= ffi::PMinSize;
(*size_hints).min_width = width as c_int;
(*size_hints).min_height = height as c_int;
} else {
(*size_hints).flags &= !ffi::PMinSize;
}
})
}.expect("Failed to call XSetWMNormalHints");
}
pub fn set_max_dimensions(&self, dimensions: Option<(u32, u32)>) {
unsafe {
self.update_normal_hints(|size_hints| {
if let Some((width, height)) = dimensions {
(*size_hints).flags |= ffi::PMaxSize;
(*size_hints).max_width = width as c_int;
(*size_hints).max_height = height as c_int;
} else {
(*size_hints).flags &= !ffi::PMaxSize;
}
})
}.expect("Failed to call XSetWMNormalHints");
}
#[inline]
pub fn get_xlib_display(&self) -> *mut c_void {
self.x.display.display as _
}
2016-10-20 03:11:02 +11:00
#[inline]
pub fn get_xlib_screen_id(&self) -> c_int {
self.x.screen_id
2016-10-20 03:11:02 +11:00
}
#[inline]
pub fn get_xlib_xconnection(&self) -> Arc<XConnection> {
self.x.display.clone()
}
2015-09-21 22:42:05 +10:00
#[inline]
pub fn platform_display(&self) -> *mut libc::c_void {
self.x.display.display as _
}
#[inline]
pub fn get_xlib_window(&self) -> c_ulong {
self.x.window
}
2015-09-21 22:42:05 +10:00
#[inline]
pub fn platform_window(&self) -> *mut libc::c_void {
self.x.window as _
}
pub fn get_xcb_connection(&self) -> *mut c_void {
unsafe {
(self.x.display.xlib_xcb.XGetXCBConnection)(self.get_xlib_display() as *mut _) as *mut _
}
}
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
}
fn get_cursor(&self, cursor: MouseCursor) -> ffi::Cursor {
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.
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(),
}
}
fn update_cursor(&self, cursor: ffi::Cursor) {
unsafe {
(self.x.display.xlib.XDefineCursor)(self.x.display.display, self.x.window, cursor);
if cursor != 0 {
(self.x.display.xlib.XFreeCursor)(self.x.display.display, cursor);
}
self.x.display.check_errors().expect("Failed to set or free the cursor");
}
}
pub fn set_cursor(&self, cursor: MouseCursor) {
let mut current_cursor = self.cursor.lock().unwrap();
*current_cursor = cursor;
if *self.cursor_state.lock().unwrap() != CursorState::Hide {
self.update_cursor(self.get_cursor(*current_cursor));
}
}
// TODO: This could maybe be cached. I don't think it's worth
// the complexity, since cursor changes are not so common,
// and this is just allocating a 1x1 pixmap...
fn create_empty_cursor(&self) -> 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> {
2016-03-22 05:42:54 +11:00
use CursorState::{ Grab, Normal, Hide };
let mut cursor_state = self.cursor_state.lock().unwrap();
match (state, *cursor_state) {
2016-03-22 05:42:54 +11:00
(Normal, Normal) | (Hide, Hide) | (Grab, Grab) => return Ok(()),
_ => {},
}
match *cursor_state {
Grab => {
unsafe {
(self.x.display.xlib.XUngrabPointer)(self.x.display.display, ffi::CurrentTime);
2015-12-24 20:57:08 +11:00
self.x.display.check_errors().expect("Failed to call XUngrabPointer");
}
},
2016-03-22 05:42:54 +11:00
Normal => {},
Hide => self.update_cursor(self.get_cursor(*self.cursor.lock().unwrap())),
2016-03-22 05:42:54 +11:00
}
2016-03-22 05:42:54 +11:00
match state {
Normal => {
*cursor_state = state;
Ok(())
},
2016-03-22 05:42:54 +11:00
Hide => {
*cursor_state = state;
self.update_cursor(self.create_empty_cursor());
2016-03-22 05:42:54 +11:00
Ok(())
},
Grab => {
unsafe {
// Ungrab before grabbing to prevent passive grabs
// from causing AlreadyGrabbed
(self.x.display.xlib.XUngrabPointer)(self.x.display.display, ffi::CurrentTime);
match (self.x.display.xlib.XGrabPointer)(
self.x.display.display, self.x.window, ffi::True,
(ffi::ButtonPressMask | ffi::ButtonReleaseMask | ffi::EnterWindowMask |
ffi::LeaveWindowMask | ffi::PointerMotionMask | ffi::PointerMotionHintMask |
ffi::Button1MotionMask | ffi::Button2MotionMask | ffi::Button3MotionMask |
ffi::Button4MotionMask | ffi::Button5MotionMask | ffi::ButtonMotionMask |
ffi::KeymapStateMask) as libc::c_uint,
ffi::GrabModeAsync, ffi::GrabModeAsync,
self.x.window, 0, ffi::CurrentTime
) {
ffi::GrabSuccess => {
*cursor_state = state;
Ok(())
},
ffi::AlreadyGrabbed | ffi::GrabInvalidTime |
ffi::GrabNotViewable | ffi::GrabFrozen
=> Err("cursor could not be grabbed".to_string()),
_ => unreachable!(),
}
}
},
}
}
pub fn hidpi_factor(&self) -> f32 {
unsafe {
Move fullscreen modes to not touch physical resolutions (#270) * Fix X11 screen resolution change using XrandR The previous XF86 resolution switching was broken and everything seems to have moved on to xrandr. Use that instead while cleaning up the code a bit as well. * Use XRandR for actual multiscreen support in X11 * Use actual monitor names in X11 * Get rid of ptr::read usage in X11 * Use a bog standard Vec instead of VecDeque * Get rid of the XRandR mode switching stuff Wayland has made the decision that apps shouldn't change screen resolutions and just take the screens as they've been setup. In the modern world where GPU scaling is cheap and LCD panels are scaling anyway it makes no sense to make "physical" resolution changes when software should be taking care of it. This massively simplifies the code and makes it easier to extend to more niche setups like MST and videowalls. * Rename fullscreen options to match new semantics * Implement XRandR 1.5 support * Get rid of the FullScreen enum Moving to just having two states None and Some(MonitorId) and then being able to set full screen in the current monitor with something like: window.set_fullscreen(Some(window.current_monitor())); * Implement Window::get_current_monitor() Do it by iterating over the available monitors and finding which has the biggest overlap with the window. For this MonitorId needs a new get_position() that needs to be implemented for all platforms. * Add unimplemented get_position() to all MonitorId * Make get_current_monitor() platform specific * Add unimplemented get_current_monitor() to all * Implement proper primary monitor selection in X11 * Shut up some warnings * Remove libxxf86vm package from travis Since we're no longer using XF86 there's no need to keep the package around for CI. * Don't use new struct syntax * Fix indentation * Adjust Android/iOS fullscreen/maximized On Android and iOS we can assume single screen apps that are already fullscreen and maximized so there are a few methods that are implemented by just returning a fixed value or not doing anything. * Mark OSX/Win fullscreen/maximized unimplemented()! These would be safe as no-ops but we should make it explicit so there is more of an incentive to actually implement them.
2017-09-07 18:33:46 +10:00
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);
2015-12-24 20:57:08 +11:00
self.x.display.check_errors().map_err(|_| ())
}
}
#[inline]
pub fn id(&self) -> WindowId { WindowId(self.x.window) }
}