Move api module into platform::linux
This commit is contained in:
parent
d0ae5bda16
commit
4acf437221
16 changed files with 12 additions and 17 deletions
1004
src/platform/linux/x11/events.rs
Normal file
1004
src/platform/linux/x11/events.rs
Normal file
File diff suppressed because it is too large
Load diff
8
src/platform/linux/x11/ffi.rs
Normal file
8
src/platform/linux/x11/ffi.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
pub use x11_dl::keysym::*;
|
||||
pub use x11_dl::xcursor::*;
|
||||
pub use x11_dl::xf86vmode::*;
|
||||
pub use x11_dl::xlib::*;
|
||||
pub use x11_dl::xinput::*;
|
||||
pub use x11_dl::xinput2::*;
|
||||
pub use x11_dl::xlib_xcb::*;
|
||||
pub use x11_dl::error::OpenError;
|
||||
391
src/platform/linux/x11/input.rs
Normal file
391
src/platform/linux/x11/input.rs
Normal file
|
|
@ -0,0 +1,391 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use libc;
|
||||
use std::{mem, ptr};
|
||||
use std::ffi::CString;
|
||||
use std::slice::from_raw_parts;
|
||||
|
||||
use WindowAttributes;
|
||||
|
||||
use events::WindowEvent as Event;
|
||||
use events::ModifiersState;
|
||||
|
||||
use super::{events, ffi};
|
||||
use super::XConnection;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum AxisType {
|
||||
HorizontalScroll,
|
||||
VerticalScroll
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Axis {
|
||||
id: i32,
|
||||
device_id: i32,
|
||||
axis_number: i32,
|
||||
axis_type: AxisType,
|
||||
scroll_increment: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct AxisValue {
|
||||
device_id: i32,
|
||||
axis_number: i32,
|
||||
value: f64
|
||||
}
|
||||
|
||||
struct InputState {
|
||||
/// Last-seen cursor position within a window in (x, y)
|
||||
/// coordinates
|
||||
cursor_pos: (f64, f64),
|
||||
/// Last-seen positions of axes, used to report delta
|
||||
/// movements when a new absolute axis value is received
|
||||
axis_values: Vec<AxisValue>
|
||||
}
|
||||
|
||||
pub struct XInputEventHandler {
|
||||
display: Arc<XConnection>,
|
||||
window: ffi::Window,
|
||||
ic: ffi::XIC,
|
||||
axis_list: Vec<Axis>,
|
||||
current_state: InputState,
|
||||
multitouch: bool,
|
||||
}
|
||||
|
||||
impl XInputEventHandler {
|
||||
pub fn new(display: &Arc<XConnection>, window: ffi::Window, ic: ffi::XIC,
|
||||
window_attrs: &WindowAttributes) -> XInputEventHandler {
|
||||
// query XInput support
|
||||
let mut opcode: libc::c_int = 0;
|
||||
let mut event: libc::c_int = 0;
|
||||
let mut error: libc::c_int = 0;
|
||||
let xinput_str = CString::new("XInputExtension").unwrap();
|
||||
|
||||
unsafe {
|
||||
if (display.xlib.XQueryExtension)(display.display, xinput_str.as_ptr(), &mut opcode, &mut event, &mut error) == ffi::False {
|
||||
panic!("XInput not available")
|
||||
}
|
||||
}
|
||||
|
||||
let mut xinput_major_ver = ffi::XI_2_Major;
|
||||
let mut xinput_minor_ver = ffi::XI_2_Minor;
|
||||
|
||||
unsafe {
|
||||
if (display.xinput2.XIQueryVersion)(display.display, &mut xinput_major_ver, &mut xinput_minor_ver) != ffi::Success as libc::c_int {
|
||||
panic!("Unable to determine XInput version");
|
||||
}
|
||||
}
|
||||
|
||||
// specify the XInput events we want to receive.
|
||||
// Button clicks and mouse events are handled via XInput
|
||||
// events. Key presses are still handled via plain core
|
||||
// X11 events.
|
||||
let mut mask: [libc::c_uchar; 3] = [0; 3];
|
||||
let mut input_event_mask = ffi::XIEventMask {
|
||||
deviceid: ffi::XIAllMasterDevices,
|
||||
mask_len: mask.len() as i32,
|
||||
mask: mask.as_mut_ptr()
|
||||
};
|
||||
let events = &[
|
||||
ffi::XI_ButtonPress,
|
||||
ffi::XI_ButtonRelease,
|
||||
ffi::XI_Motion,
|
||||
ffi::XI_Enter,
|
||||
ffi::XI_Leave,
|
||||
ffi::XI_FocusIn,
|
||||
ffi::XI_FocusOut,
|
||||
ffi::XI_TouchBegin,
|
||||
ffi::XI_TouchUpdate,
|
||||
ffi::XI_TouchEnd,
|
||||
];
|
||||
for event in events {
|
||||
ffi::XISetMask(&mut mask, *event);
|
||||
}
|
||||
|
||||
unsafe {
|
||||
match (display.xinput2.XISelectEvents)(display.display, window, &mut input_event_mask, 1) {
|
||||
status if status as u8 == ffi::Success => (),
|
||||
err => panic!("Failed to select events {:?}", err)
|
||||
}
|
||||
}
|
||||
|
||||
XInputEventHandler {
|
||||
display: display.clone(),
|
||||
window: window,
|
||||
ic: ic,
|
||||
axis_list: read_input_axis_info(display),
|
||||
current_state: InputState {
|
||||
cursor_pos: (0.0, 0.0),
|
||||
axis_values: Vec::new()
|
||||
},
|
||||
multitouch: window_attrs.multitouch,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn translate_key_event(&self, event: &mut ffi::XKeyEvent) -> Vec<Event> {
|
||||
use events::WindowEvent::{KeyboardInput, ReceivedCharacter};
|
||||
use events::ElementState::{Pressed, Released};
|
||||
|
||||
let mut translated_events = Vec::new();
|
||||
|
||||
let state;
|
||||
if event.type_ == ffi::KeyPress {
|
||||
let raw_ev: *mut ffi::XKeyEvent = event;
|
||||
unsafe { (self.display.xlib.XFilterEvent)(mem::transmute(raw_ev), self.window) };
|
||||
state = Pressed;
|
||||
} else {
|
||||
state = Released;
|
||||
}
|
||||
|
||||
let mut kp_keysym = 0;
|
||||
|
||||
let mut ev_mods = ModifiersState::default();
|
||||
|
||||
let written = unsafe {
|
||||
use std::str;
|
||||
|
||||
let mut buffer: [u8; 16] = [mem::uninitialized(); 16];
|
||||
let raw_ev: *mut ffi::XKeyEvent = event;
|
||||
let count = (self.display.xlib.Xutf8LookupString)(self.ic, mem::transmute(raw_ev),
|
||||
mem::transmute(buffer.as_mut_ptr()),
|
||||
buffer.len() as libc::c_int, &mut kp_keysym, ptr::null_mut());
|
||||
|
||||
{
|
||||
// Translate x event state to mods
|
||||
let state = event.state;
|
||||
if (state & ffi::Mod1Mask) != 0 {
|
||||
ev_mods.alt = true;
|
||||
}
|
||||
|
||||
if (state & ffi::ShiftMask) != 0 {
|
||||
ev_mods.shift = true;
|
||||
}
|
||||
|
||||
if (state & ffi::ControlMask) != 0 {
|
||||
ev_mods.ctrl = true;
|
||||
}
|
||||
|
||||
if (state & ffi::Mod4Mask) != 0 {
|
||||
ev_mods.logo = true;
|
||||
}
|
||||
}
|
||||
|
||||
str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string()
|
||||
};
|
||||
|
||||
for chr in written.chars() {
|
||||
translated_events.push(ReceivedCharacter(chr));
|
||||
}
|
||||
|
||||
let mut keysym = unsafe {
|
||||
(self.display.xlib.XKeycodeToKeysym)(self.display.display, event.keycode as ffi::KeyCode, 0)
|
||||
};
|
||||
|
||||
if (ffi::XK_KP_Space as libc::c_ulong <= keysym) && (keysym <= ffi::XK_KP_9 as libc::c_ulong) {
|
||||
keysym = kp_keysym
|
||||
};
|
||||
|
||||
let vkey = events::keycode_to_element(keysym as libc::c_uint);
|
||||
|
||||
translated_events.push(KeyboardInput(state, event.keycode as u8, vkey, ev_mods));
|
||||
translated_events
|
||||
}
|
||||
|
||||
pub fn translate_event(&mut self, cookie: &ffi::XGenericEventCookie) -> Option<Event> {
|
||||
use events::WindowEvent::{Focused, MouseEntered, MouseInput, MouseLeft, MouseMoved, MouseWheel};
|
||||
use events::ElementState::{Pressed, Released};
|
||||
use events::MouseButton::{Left, Right, Middle};
|
||||
use events::MouseScrollDelta::LineDelta;
|
||||
use events::{Touch, TouchPhase};
|
||||
|
||||
match cookie.evtype {
|
||||
ffi::XI_ButtonPress | ffi::XI_ButtonRelease => {
|
||||
let event_data: &ffi::XIDeviceEvent = unsafe{mem::transmute(cookie.data)};
|
||||
if self.multitouch && (event_data.flags & ffi::XIPointerEmulated) != 0 {
|
||||
// Deliver multi-touch events instead of emulated mouse events.
|
||||
return None
|
||||
}
|
||||
let state = if cookie.evtype == ffi::XI_ButtonPress {
|
||||
Pressed
|
||||
} else {
|
||||
Released
|
||||
};
|
||||
match event_data.detail as u32 {
|
||||
ffi::Button1 => Some(MouseInput(state, Left)),
|
||||
ffi::Button2 => Some(MouseInput(state, Middle)),
|
||||
ffi::Button3 => Some(MouseInput(state, Right)),
|
||||
ffi::Button4 | ffi::Button5 => {
|
||||
if event_data.flags & ffi::XIPointerEmulated == 0 {
|
||||
// scroll event from a traditional wheel with
|
||||
// distinct 'clicks'
|
||||
let delta = if event_data.detail as u32 == ffi::Button4 {
|
||||
1.0
|
||||
} else {
|
||||
-1.0
|
||||
};
|
||||
Some(MouseWheel(LineDelta(0.0, delta), TouchPhase::Moved))
|
||||
} else {
|
||||
// emulated button event from a touch/smooth-scroll
|
||||
// event. Ignore these events and handle scrolling
|
||||
// via XI_Motion event handler instead
|
||||
None
|
||||
}
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
},
|
||||
ffi::XI_Motion => {
|
||||
let event_data: &ffi::XIDeviceEvent = unsafe{mem::transmute(cookie.data)};
|
||||
if self.multitouch && (event_data.flags & ffi::XIPointerEmulated) != 0 {
|
||||
// Deliver multi-touch events instead of emulated mouse events.
|
||||
return None
|
||||
}
|
||||
let axis_state = event_data.valuators;
|
||||
let mask = unsafe{ from_raw_parts(axis_state.mask, axis_state.mask_len as usize) };
|
||||
let mut axis_count = 0;
|
||||
|
||||
let mut scroll_delta = (0.0, 0.0);
|
||||
for axis_id in 0..axis_state.mask_len {
|
||||
if ffi::XIMaskIsSet(&mask, axis_id) {
|
||||
let axis_value = unsafe{*axis_state.values.offset(axis_count)};
|
||||
let delta = calc_scroll_deltas(event_data, axis_id, axis_value, &self.axis_list,
|
||||
&mut self.current_state.axis_values);
|
||||
scroll_delta.0 += delta.0;
|
||||
scroll_delta.1 += delta.1;
|
||||
axis_count += 1;
|
||||
}
|
||||
}
|
||||
|
||||
if scroll_delta.0.abs() > 0.0 || scroll_delta.1.abs() > 0.0 {
|
||||
Some(MouseWheel(LineDelta(scroll_delta.0 as f32, scroll_delta.1 as f32),
|
||||
TouchPhase::Moved))
|
||||
} else {
|
||||
let new_cursor_pos = (event_data.event_x, event_data.event_y);
|
||||
if new_cursor_pos != self.current_state.cursor_pos {
|
||||
self.current_state.cursor_pos = new_cursor_pos;
|
||||
Some(MouseMoved(new_cursor_pos.0 as i32, new_cursor_pos.1 as i32))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
ffi::XI_Enter => {
|
||||
// axis movements whilst the cursor is outside the window
|
||||
// will alter the absolute value of the axes. We only want to
|
||||
// report changes in the axis value whilst the cursor is above
|
||||
// our window however, so clear the previous axis state whenever
|
||||
// the cursor re-enters the window
|
||||
self.current_state.axis_values.clear();
|
||||
Some(MouseEntered)
|
||||
},
|
||||
ffi::XI_Leave => Some(MouseLeft),
|
||||
ffi::XI_FocusIn => Some(Focused(true)),
|
||||
ffi::XI_FocusOut => Some(Focused(false)),
|
||||
ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => {
|
||||
if !self.multitouch {
|
||||
return None
|
||||
}
|
||||
let event_data: &ffi::XIDeviceEvent = unsafe{mem::transmute(cookie.data)};
|
||||
let phase = match cookie.evtype {
|
||||
ffi::XI_TouchBegin => TouchPhase::Started,
|
||||
ffi::XI_TouchUpdate => TouchPhase::Moved,
|
||||
ffi::XI_TouchEnd => TouchPhase::Ended,
|
||||
_ => unreachable!()
|
||||
};
|
||||
Some(Event::Touch(Touch {
|
||||
phase: phase,
|
||||
location: (event_data.event_x, event_data.event_y),
|
||||
id: event_data.detail as u64,
|
||||
}))
|
||||
}
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_input_axis_info(display: &Arc<XConnection>) -> Vec<Axis> {
|
||||
let mut axis_list = Vec::new();
|
||||
let mut device_count = 0;
|
||||
|
||||
// Check all input devices for scroll axes.
|
||||
let devices = unsafe{
|
||||
(display.xinput2.XIQueryDevice)(display.display, ffi::XIAllDevices, &mut device_count)
|
||||
};
|
||||
for i in 0..device_count {
|
||||
let device = unsafe { *(devices.offset(i as isize)) };
|
||||
for k in 0..device.num_classes {
|
||||
let class = unsafe { *(device.classes.offset(k as isize)) };
|
||||
match unsafe { (*class)._type } {
|
||||
// Note that scroll axis
|
||||
// are reported both as 'XIScrollClass' and 'XIValuatorClass'
|
||||
// axes. For the moment we only care about scrolling axes.
|
||||
ffi::XIScrollClass => {
|
||||
let scroll_class: &ffi::XIScrollClassInfo = unsafe{mem::transmute(class)};
|
||||
axis_list.push(Axis{
|
||||
id: scroll_class.sourceid,
|
||||
device_id: device.deviceid,
|
||||
axis_number: scroll_class.number,
|
||||
axis_type: match scroll_class.scroll_type {
|
||||
ffi::XIScrollTypeHorizontal => AxisType::HorizontalScroll,
|
||||
ffi::XIScrollTypeVertical => AxisType::VerticalScroll,
|
||||
_ => { unreachable!() }
|
||||
},
|
||||
scroll_increment: scroll_class.increment,
|
||||
})
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe {
|
||||
(display.xinput2.XIFreeDeviceInfo)(devices);
|
||||
}
|
||||
|
||||
axis_list
|
||||
}
|
||||
|
||||
/// Given an input motion event for an axis and the previous
|
||||
/// state of the axes, return the horizontal/vertical
|
||||
/// scroll deltas
|
||||
fn calc_scroll_deltas(event: &ffi::XIDeviceEvent,
|
||||
axis_id: i32,
|
||||
axis_value: f64,
|
||||
axis_list: &[Axis],
|
||||
prev_axis_values: &mut Vec<AxisValue>) -> (f64, f64) {
|
||||
let prev_value_pos = prev_axis_values.iter().position(|prev_axis| {
|
||||
prev_axis.device_id == event.sourceid &&
|
||||
prev_axis.axis_number == axis_id
|
||||
});
|
||||
let delta = match prev_value_pos {
|
||||
Some(idx) => prev_axis_values[idx].value - axis_value,
|
||||
None => 0.0
|
||||
};
|
||||
|
||||
let new_axis_value = AxisValue{
|
||||
device_id: event.sourceid,
|
||||
axis_number: axis_id,
|
||||
value: axis_value
|
||||
};
|
||||
|
||||
match prev_value_pos {
|
||||
Some(idx) => prev_axis_values[idx] = new_axis_value,
|
||||
None => prev_axis_values.push(new_axis_value)
|
||||
}
|
||||
|
||||
let mut scroll_delta = (0.0, 0.0);
|
||||
|
||||
for axis in axis_list.iter() {
|
||||
if axis.id == event.sourceid &&
|
||||
axis.axis_number == axis_id {
|
||||
match axis.axis_type {
|
||||
AxisType::HorizontalScroll => scroll_delta.0 = delta / axis.scroll_increment,
|
||||
AxisType::VerticalScroll => scroll_delta.1 = delta / axis.scroll_increment
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scroll_delta
|
||||
}
|
||||
|
||||
13
src/platform/linux/x11/mod.rs
Normal file
13
src/platform/linux/x11/mod.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
||||
|
||||
pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor};
|
||||
pub use self::window::{Window, XWindow, PollEventsIterator, WaitEventsIterator, WindowProxy};
|
||||
pub use self::xdisplay::{XConnection, XNotSupported, XError};
|
||||
|
||||
pub mod ffi;
|
||||
|
||||
mod events;
|
||||
mod input;
|
||||
mod monitor;
|
||||
mod window;
|
||||
mod xdisplay;
|
||||
44
src/platform/linux/x11/monitor.rs
Normal file
44
src/platform/linux/x11/monitor.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::XConnection;
|
||||
use native_monitor::NativeMonitorId;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct MonitorId(pub Arc<XConnection>, pub u32);
|
||||
|
||||
pub fn get_available_monitors(x: &Arc<XConnection>) -> VecDeque<MonitorId> {
|
||||
let nb_monitors = unsafe { (x.xlib.XScreenCount)(x.display) };
|
||||
x.check_errors().expect("Failed to call XScreenCount");
|
||||
|
||||
let mut monitors = VecDeque::new();
|
||||
monitors.extend((0 .. nb_monitors).map(|i| MonitorId(x.clone(), i as u32)));
|
||||
monitors
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_primary_monitor(x: &Arc<XConnection>) -> MonitorId {
|
||||
let primary_monitor = unsafe { (x.xlib.XDefaultScreen)(x.display) };
|
||||
x.check_errors().expect("Failed to call XDefaultScreen");
|
||||
MonitorId(x.clone(), primary_monitor as u32)
|
||||
}
|
||||
|
||||
impl MonitorId {
|
||||
pub fn get_name(&self) -> Option<String> {
|
||||
let MonitorId(_, screen_num) = *self;
|
||||
Some(format!("Monitor #{}", screen_num))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_native_identifier(&self) -> NativeMonitorId {
|
||||
NativeMonitorId::Numeric(self.1)
|
||||
}
|
||||
|
||||
pub fn get_dimensions(&self) -> (u32, u32) {
|
||||
let screen = unsafe { (self.0.xlib.XScreenOfDisplay)(self.0.display, self.1 as i32) };
|
||||
let width = unsafe { (self.0.xlib.XWidthOfScreen)(screen) };
|
||||
let height = unsafe { (self.0.xlib.XHeightOfScreen)(screen) };
|
||||
self.0.check_errors().expect("Failed to get monitor dimensions");
|
||||
(width as u32, height as u32)
|
||||
}
|
||||
}
|
||||
976
src/platform/linux/x11/window.rs
Normal file
976
src/platform/linux/x11/window.rs
Normal file
|
|
@ -0,0 +1,976 @@
|
|||
use {WindowEvent as Event, MouseCursor};
|
||||
use CreationError;
|
||||
use CreationError::OsError;
|
||||
use libc;
|
||||
use std::borrow::Borrow;
|
||||
use std::{mem, ptr, cmp};
|
||||
use std::cell::Cell;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::os::raw::c_long;
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
use CursorState;
|
||||
use WindowAttributes;
|
||||
use platform::PlatformSpecificWindowBuilderAttributes;
|
||||
|
||||
use platform::MonitorId as PlatformMonitorId;
|
||||
|
||||
use super::input::XInputEventHandler;
|
||||
use super::{ffi};
|
||||
use super::{MonitorId, XConnection};
|
||||
|
||||
// XOpenIM doesn't seem to be thread-safe
|
||||
lazy_static! { // TODO: use a static mutex when that's possible, and put me back in my function
|
||||
static ref GLOBAL_XOPENIM_LOCK: Mutex<()> = Mutex::new(());
|
||||
}
|
||||
|
||||
// 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())
|
||||
}
|
||||
|
||||
struct WindowProxyData {
|
||||
display: Arc<XConnection>,
|
||||
window: ffi::Window,
|
||||
}
|
||||
|
||||
unsafe impl Send for WindowProxyData {}
|
||||
|
||||
pub struct XWindow {
|
||||
display: Arc<XConnection>,
|
||||
window: ffi::Window,
|
||||
is_fullscreen: bool,
|
||||
screen_id: libc::c_int,
|
||||
xf86_desk_mode: Option<ffi::XF86VidModeModeInfo>,
|
||||
ic: ffi::XIC,
|
||||
im: ffi::XIM,
|
||||
window_proxy_data: Arc<Mutex<Option<WindowProxyData>>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for XWindow {}
|
||||
unsafe impl Sync for XWindow {}
|
||||
|
||||
unsafe impl Send for Window {}
|
||||
unsafe impl Sync for Window {}
|
||||
|
||||
impl Drop for XWindow {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// Clear out the window proxy data arc, so that any window proxy objects
|
||||
// are no longer able to send messages to this window.
|
||||
*self.window_proxy_data.lock().unwrap() = None;
|
||||
|
||||
let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap();
|
||||
|
||||
if self.is_fullscreen {
|
||||
if let Some(mut xf86_desk_mode) = self.xf86_desk_mode {
|
||||
(self.display.xf86vmode.XF86VidModeSwitchToMode)(self.display.display, self.screen_id, &mut xf86_desk_mode);
|
||||
}
|
||||
(self.display.xf86vmode.XF86VidModeSetViewPort)(self.display.display, self.screen_id, 0, 0);
|
||||
}
|
||||
|
||||
(self.display.xlib.XDestroyIC)(self.ic);
|
||||
(self.display.xlib.XCloseIM)(self.im);
|
||||
(self.display.xlib.XDestroyWindow)(self.display.display, self.window);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WindowProxy {
|
||||
data: Arc<Mutex<Option<WindowProxyData>>>,
|
||||
}
|
||||
|
||||
impl WindowProxy {
|
||||
pub fn wakeup_event_loop(&self) {
|
||||
let window_proxy_data = self.data.lock().unwrap();
|
||||
|
||||
if let Some(ref data) = *window_proxy_data {
|
||||
let mut xev = ffi::XClientMessageEvent {
|
||||
type_: ffi::ClientMessage,
|
||||
window: data.window,
|
||||
format: 32,
|
||||
message_type: 0,
|
||||
serial: 0,
|
||||
send_event: 0,
|
||||
display: data.display.display,
|
||||
data: unsafe { mem::zeroed() },
|
||||
};
|
||||
|
||||
unsafe {
|
||||
(data.display.xlib.XSendEvent)(data.display.display, data.window, 0, 0, mem::transmute(&mut xev));
|
||||
(data.display.xlib.XFlush)(data.display.display);
|
||||
data.display.check_errors().expect("Failed to call XSendEvent after wakeup");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XEvents of type GenericEvent store their actual data
|
||||
// in an XGenericEventCookie data structure. This is a wrapper
|
||||
// to extract the cookie from a GenericEvent XEvent and release
|
||||
// the cookie data once it has been processed
|
||||
struct GenericEventCookie<'a> {
|
||||
display: &'a XConnection,
|
||||
cookie: ffi::XGenericEventCookie
|
||||
}
|
||||
|
||||
impl<'a> GenericEventCookie<'a> {
|
||||
fn from_event<'b>(display: &'b XConnection, event: ffi::XEvent) -> Option<GenericEventCookie<'b>> {
|
||||
unsafe {
|
||||
let mut cookie: ffi::XGenericEventCookie = From::from(event);
|
||||
if (display.xlib.XGetEventData)(display.display, &mut cookie) == ffi::True {
|
||||
Some(GenericEventCookie{display: display, cookie: cookie})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for GenericEventCookie<'a> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let xlib = &self.display.xlib;
|
||||
(xlib.XFreeEventData)(self.display.display, &mut self.cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PollEventsIterator<'a> {
|
||||
window: &'a Window
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PollEventsIterator<'a> {
|
||||
type Item = Event;
|
||||
|
||||
fn next(&mut self) -> Option<Event> {
|
||||
let xlib = &self.window.x.display.xlib;
|
||||
|
||||
loop {
|
||||
if let Some(ev) = self.window.pending_events.lock().unwrap().pop_front() {
|
||||
return Some(ev);
|
||||
}
|
||||
|
||||
let mut xev = unsafe { mem::uninitialized() };
|
||||
|
||||
// Get the next X11 event. XNextEvent will block if there's no
|
||||
// events available; checking the count first ensures an event will
|
||||
// be returned without blocking.
|
||||
//
|
||||
// Functions like XCheckTypedEvent can prevent events from being
|
||||
// popped if they are of the wrong type in which case winit would
|
||||
// enter a busy loop. To avoid that, XNextEvent is used to pop
|
||||
// events off the queue since it will accept any event type.
|
||||
unsafe {
|
||||
let count = (xlib.XPending)(self.window.x.display.display);
|
||||
if count == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let res = (xlib.XNextEvent)(self.window.x.display.display, &mut xev);
|
||||
|
||||
// Can res ever be none zero if count is > 0?
|
||||
assert!(res == 0);
|
||||
};
|
||||
|
||||
match xev.get_type() {
|
||||
ffi::MappingNotify => {
|
||||
unsafe { (xlib.XRefreshKeyboardMapping)(mem::transmute(&xev)); }
|
||||
self.window.x.display.check_errors().expect("Failed to call XRefreshKeyboardMapping");
|
||||
},
|
||||
|
||||
ffi::ClientMessage => {
|
||||
use events::WindowEvent::{Closed, Awakened};
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
let client_msg: &ffi::XClientMessageEvent = unsafe { mem::transmute(&xev) };
|
||||
|
||||
if client_msg.data.get_long(0) == self.window.wm_delete_window as libc::c_long {
|
||||
self.window.is_closed.store(true, Relaxed);
|
||||
return Some(Closed);
|
||||
} else {
|
||||
return Some(Awakened);
|
||||
}
|
||||
},
|
||||
|
||||
ffi::ConfigureNotify => {
|
||||
use events::WindowEvent::Resized;
|
||||
let cfg_event: &ffi::XConfigureEvent = unsafe { mem::transmute(&xev) };
|
||||
let (current_width, current_height) = self.window.current_size.get();
|
||||
if current_width != cfg_event.width || current_height != cfg_event.height {
|
||||
self.window.current_size.set((cfg_event.width, cfg_event.height));
|
||||
return Some(Resized(cfg_event.width as u32, cfg_event.height as u32));
|
||||
}
|
||||
},
|
||||
|
||||
ffi::Expose => {
|
||||
use events::WindowEvent::Refresh;
|
||||
return Some(Refresh);
|
||||
},
|
||||
|
||||
ffi::KeyPress | ffi::KeyRelease => {
|
||||
let mut event: &mut ffi::XKeyEvent = unsafe { mem::transmute(&mut xev) };
|
||||
let events = self.window.input_handler.lock().unwrap().translate_key_event(&mut event);
|
||||
for event in events {
|
||||
self.window.pending_events.lock().unwrap().push_back(event);
|
||||
}
|
||||
},
|
||||
|
||||
ffi::GenericEvent => {
|
||||
if let Some(cookie) = GenericEventCookie::from_event(self.window.x.display.borrow(), xev) {
|
||||
match cookie.cookie.evtype {
|
||||
ffi::XI_DeviceChanged...ffi::XI_LASTEVENT => {
|
||||
match self.window.input_handler.lock() {
|
||||
Ok(mut handler) => {
|
||||
match handler.translate_event(&cookie.cookie) {
|
||||
Some(event) => self.window.pending_events.lock().unwrap().push_back(event),
|
||||
None => {}
|
||||
}
|
||||
},
|
||||
Err(_) => {}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WaitEventsIterator<'a> {
|
||||
window: &'a Window,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WaitEventsIterator<'a> {
|
||||
type Item = Event;
|
||||
|
||||
fn next(&mut self) -> Option<Event> {
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::mem;
|
||||
|
||||
while !self.window.is_closed.load(Relaxed) {
|
||||
if let Some(ev) = self.window.pending_events.lock().unwrap().pop_front() {
|
||||
return Some(ev);
|
||||
}
|
||||
|
||||
// this will block until an event arrives, but doesn't remove
|
||||
// it from the queue
|
||||
let mut xev = unsafe { mem::uninitialized() };
|
||||
unsafe { (self.window.x.display.xlib.XPeekEvent)(self.window.x.display.display, &mut xev) };
|
||||
self.window.x.display.check_errors().expect("Failed to call XPeekEvent");
|
||||
|
||||
// calling poll_events()
|
||||
if let Some(ev) = self.window.poll_events().next() {
|
||||
return Some(ev);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
pub x: Arc<XWindow>,
|
||||
is_closed: AtomicBool,
|
||||
wm_delete_window: ffi::Atom,
|
||||
current_size: Cell<(libc::c_int, libc::c_int)>,
|
||||
/// Events that have been retreived with XLib but not dispatched with iterators yet
|
||||
pending_events: Mutex<VecDeque<Event>>,
|
||||
cursor_state: Mutex<CursorState>,
|
||||
input_handler: Mutex<XInputEventHandler>
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(display: &Arc<XConnection>, window_attrs: &WindowAttributes,
|
||||
pl_attribs: &PlatformSpecificWindowBuilderAttributes)
|
||||
-> Result<Window, CreationError>
|
||||
{
|
||||
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 => match window_attrs.monitor {
|
||||
Some(PlatformMonitorId::X(MonitorId(_, monitor))) => monitor as i32,
|
||||
_ => unsafe { (display.xlib.XDefaultScreen)(display.display) },
|
||||
}
|
||||
};
|
||||
|
||||
// finding the mode to switch to if necessary
|
||||
let (mode_to_switch_to, xf86_desk_mode) = unsafe {
|
||||
let mut mode_num: libc::c_int = mem::uninitialized();
|
||||
let mut modes: *mut *mut ffi::XF86VidModeModeInfo = mem::uninitialized();
|
||||
if (display.xf86vmode.XF86VidModeGetAllModeLines)(display.display, screen_id, &mut mode_num, &mut modes) == 0 {
|
||||
(None, None)
|
||||
} else {
|
||||
let xf86_desk_mode: ffi::XF86VidModeModeInfo = ptr::read(*modes.offset(0));
|
||||
let mode_to_switch_to = if window_attrs.monitor.is_some() {
|
||||
let matching_mode = (0 .. mode_num).map(|i| {
|
||||
let m: ffi::XF86VidModeModeInfo = ptr::read(*modes.offset(i as isize) as *const _); m
|
||||
}).find(|m| m.hdisplay == dimensions.0 as u16 && m.vdisplay == dimensions.1 as u16);
|
||||
if let Some(matching_mode) = matching_mode {
|
||||
Some(matching_mode)
|
||||
} else {
|
||||
let m = (0 .. mode_num).map(|i| {
|
||||
let m: ffi::XF86VidModeModeInfo = ptr::read(*modes.offset(i as isize) as *const _); m
|
||||
}).find(|m| m.hdisplay >= dimensions.0 as u16 && m.vdisplay >= dimensions.1 as u16);
|
||||
|
||||
match m {
|
||||
Some(m) => Some(m),
|
||||
None => return Err(OsError(format!("Could not find a suitable graphics mode")))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
(display.xlib.XFree)(modes as *mut _);
|
||||
(mode_to_switch_to, Some(xf86_desk_mode))
|
||||
}
|
||||
};
|
||||
|
||||
// getting the root window
|
||||
let root = unsafe { (display.xlib.XDefaultRootWindow)(display.display) };
|
||||
display.check_errors().expect("Failed to get root window");
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
// set visibility
|
||||
if window_attrs.visible {
|
||||
unsafe {
|
||||
(display.xlib.XMapRaised)(display.display, window);
|
||||
(display.xlib.XFlush)(display.display);
|
||||
}
|
||||
|
||||
display.check_errors().expect("Failed to set window visibility");
|
||||
}
|
||||
|
||||
// creating window, step 2
|
||||
let wm_delete_window = unsafe {
|
||||
let mut wm_delete_window = with_c_str("WM_DELETE_WINDOW", |delete_window|
|
||||
(display.xlib.XInternAtom)(display.display, delete_window, 0)
|
||||
);
|
||||
display.check_errors().expect("Failed to call XInternAtom");
|
||||
(display.xlib.XSetWMProtocols)(display.display, window, &mut wm_delete_window, 1);
|
||||
display.check_errors().expect("Failed to call XSetWMProtocols");
|
||||
(display.xlib.XFlush)(display.display);
|
||||
display.check_errors().expect("Failed to call XFlush");
|
||||
|
||||
wm_delete_window
|
||||
};
|
||||
|
||||
// creating IM
|
||||
let im = unsafe {
|
||||
let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap();
|
||||
|
||||
let im = (display.xlib.XOpenIM)(display.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
|
||||
if im.is_null() {
|
||||
return Err(OsError(format!("XOpenIM failed")));
|
||||
}
|
||||
im
|
||||
};
|
||||
|
||||
// creating input context
|
||||
let ic = unsafe {
|
||||
let ic = with_c_str("inputStyle", |input_style|
|
||||
with_c_str("clientWindow", |client_window|
|
||||
(display.xlib.XCreateIC)(
|
||||
im, input_style,
|
||||
ffi::XIMPreeditNothing | ffi::XIMStatusNothing, client_window,
|
||||
window, ptr::null::<()>()
|
||||
)
|
||||
)
|
||||
);
|
||||
if ic.is_null() {
|
||||
return Err(OsError(format!("XCreateIC failed")));
|
||||
}
|
||||
(display.xlib.XSetICFocus)(ic);
|
||||
display.check_errors().expect("Failed to call XSetICFocus");
|
||||
ic
|
||||
};
|
||||
|
||||
// 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")));
|
||||
}
|
||||
}
|
||||
|
||||
// Set ICCCM WM_CLASS property based on initial window title
|
||||
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, window, hint);
|
||||
display.check_errors().expect("Failed to call XSetClassHint");
|
||||
(display.xlib.XFree)(hint as *mut _);
|
||||
});
|
||||
}
|
||||
|
||||
let is_fullscreen = window_attrs.monitor.is_some();
|
||||
|
||||
if is_fullscreen {
|
||||
let state_atom = unsafe {
|
||||
with_c_str("_NET_WM_STATE", |state|
|
||||
(display.xlib.XInternAtom)(display.display, state, 0)
|
||||
)
|
||||
};
|
||||
display.check_errors().expect("Failed to call XInternAtom");
|
||||
let fullscreen_atom = unsafe {
|
||||
with_c_str("_NET_WM_STATE_FULLSCREEN", |state_fullscreen|
|
||||
(display.xlib.XInternAtom)(display.display, state_fullscreen, 0)
|
||||
)
|
||||
};
|
||||
display.check_errors().expect("Failed to call XInternAtom");
|
||||
|
||||
let client_message_event = ffi::XClientMessageEvent {
|
||||
type_: ffi::ClientMessage,
|
||||
serial: 0,
|
||||
send_event: 1, // true because we are sending this through `XSendEvent`
|
||||
display: display.display,
|
||||
window: window,
|
||||
message_type: state_atom, // the _NET_WM_STATE atom is sent to change the state of a window
|
||||
format: 32, // view `data` as `c_long`s
|
||||
data: {
|
||||
let mut data = ffi::ClientMessageData::new();
|
||||
// This first `long` is the action; `1` means add/set following property.
|
||||
data.set_long(0, 1);
|
||||
// This second `long` is the property to set (fullscreen)
|
||||
data.set_long(1, fullscreen_atom as c_long);
|
||||
data
|
||||
}
|
||||
};
|
||||
let mut x_event = ffi::XEvent::from(client_message_event);
|
||||
|
||||
unsafe {
|
||||
(display.xlib.XSendEvent)(
|
||||
display.display,
|
||||
root,
|
||||
0,
|
||||
ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask,
|
||||
&mut x_event as *mut _
|
||||
);
|
||||
display.check_errors().expect("Failed to call XSendEvent");
|
||||
}
|
||||
|
||||
if let Some(mut mode_to_switch_to) = mode_to_switch_to {
|
||||
unsafe {
|
||||
(display.xf86vmode.XF86VidModeSwitchToMode)(
|
||||
display.display,
|
||||
screen_id,
|
||||
&mut mode_to_switch_to
|
||||
);
|
||||
display.check_errors().expect("Failed to call XF86VidModeSwitchToMode");
|
||||
}
|
||||
}
|
||||
else {
|
||||
println!("[glutin] Unexpected state: `mode` is None creating fullscreen window");
|
||||
}
|
||||
unsafe {
|
||||
(display.xf86vmode.XF86VidModeSetViewPort)(display.display, screen_id, 0, 0);
|
||||
display.check_errors().expect("Failed to call XF86VidModeSetViewPort");
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// 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, window, &mut size_hints);
|
||||
display.check_errors().expect("Failed to call XSetNormalHints");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// creating the window object
|
||||
let window_proxy_data = WindowProxyData {
|
||||
display: display.clone(),
|
||||
window: window,
|
||||
};
|
||||
let window_proxy_data = Arc::new(Mutex::new(Some(window_proxy_data)));
|
||||
|
||||
let window = Window {
|
||||
x: Arc::new(XWindow {
|
||||
display: display.clone(),
|
||||
window: window,
|
||||
im: im,
|
||||
ic: ic,
|
||||
screen_id: screen_id,
|
||||
is_fullscreen: is_fullscreen,
|
||||
xf86_desk_mode: xf86_desk_mode,
|
||||
window_proxy_data: window_proxy_data,
|
||||
}),
|
||||
is_closed: AtomicBool::new(false),
|
||||
wm_delete_window: wm_delete_window,
|
||||
current_size: Cell::new((0, 0)),
|
||||
pending_events: Mutex::new(VecDeque::new()),
|
||||
cursor_state: Mutex::new(CursorState::Normal),
|
||||
input_handler: Mutex::new(XInputEventHandler::new(display, window, ic, window_attrs))
|
||||
};
|
||||
|
||||
window.set_title(&window_attrs.title);
|
||||
|
||||
if window_attrs.visible {
|
||||
unsafe {
|
||||
let ref x_window: &XWindow = window.x.borrow();
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
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 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 create_window_proxy(&self) -> WindowProxy {
|
||||
WindowProxy {
|
||||
data: self.x.window_proxy_data.clone()
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn poll_events(&self) -> PollEventsIterator {
|
||||
PollEventsIterator {
|
||||
window: self
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn wait_events(&self) -> WaitEventsIterator {
|
||||
WaitEventsIterator {
|
||||
window: self
|
||||
}
|
||||
}
|
||||
|
||||
#[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 _
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_window_resize_callback(&mut self, _: Option<fn(u32, u32)>) {
|
||||
}
|
||||
|
||||
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 mut 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(|_| ())
|
||||
}
|
||||
}
|
||||
}
|
||||
147
src/platform/linux/x11/xdisplay.rs
Normal file
147
src/platform/linux/x11/xdisplay.rs
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
use std::ptr;
|
||||
use std::fmt;
|
||||
use std::error::Error;
|
||||
use std::ffi::CString;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use libc;
|
||||
|
||||
use super::ffi;
|
||||
use super::super::dlopen;
|
||||
|
||||
/// A connection to an X server.
|
||||
pub struct XConnection {
|
||||
pub xlib: ffi::Xlib,
|
||||
pub xf86vmode: ffi::Xf86vmode,
|
||||
pub xcursor: ffi::Xcursor,
|
||||
pub xinput2: ffi::XInput2,
|
||||
pub xlib_xcb: ffi::Xlib_xcb,
|
||||
pub display: *mut ffi::Display,
|
||||
pub latest_error: Mutex<Option<XError>>,
|
||||
}
|
||||
|
||||
unsafe impl Send for XConnection {}
|
||||
unsafe impl Sync for XConnection {}
|
||||
|
||||
pub type XErrorHandler = Option<unsafe extern fn(*mut ffi::Display, *mut ffi::XErrorEvent) -> libc::c_int>;
|
||||
|
||||
impl XConnection {
|
||||
pub fn new(error_handler: XErrorHandler) -> Result<XConnection, XNotSupported> {
|
||||
// opening the libraries
|
||||
let xlib = try!(ffi::Xlib::open());
|
||||
let xcursor = try!(ffi::Xcursor::open());
|
||||
let xf86vmode = try!(ffi::Xf86vmode::open());
|
||||
let xinput2 = try!(ffi::XInput2::open());
|
||||
let xlib_xcb = try!(ffi::Xlib_xcb::open());
|
||||
|
||||
unsafe { (xlib.XInitThreads)() };
|
||||
unsafe { (xlib.XSetErrorHandler)(error_handler) };
|
||||
|
||||
// calling XOpenDisplay
|
||||
let display = unsafe {
|
||||
let display = (xlib.XOpenDisplay)(ptr::null());
|
||||
if display.is_null() {
|
||||
return Err(XNotSupported::XOpenDisplayFailed);
|
||||
}
|
||||
display
|
||||
};
|
||||
|
||||
Ok(XConnection {
|
||||
xlib: xlib,
|
||||
xf86vmode: xf86vmode,
|
||||
xcursor: xcursor,
|
||||
xinput2: xinput2,
|
||||
xlib_xcb: xlib_xcb,
|
||||
display: display,
|
||||
latest_error: Mutex::new(None),
|
||||
})
|
||||
}
|
||||
|
||||
/// Checks whether an error has been triggered by the previous function calls.
|
||||
#[inline]
|
||||
pub fn check_errors(&self) -> Result<(), XError> {
|
||||
let error = self.latest_error.lock().unwrap().take();
|
||||
|
||||
if let Some(error) = error {
|
||||
Err(error)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Ignores any previous error.
|
||||
#[inline]
|
||||
pub fn ignore_error(&self) {
|
||||
*self.latest_error.lock().unwrap() = None;
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for XConnection {
|
||||
#[inline]
|
||||
fn drop(&mut self) {
|
||||
unsafe { (self.xlib.XCloseDisplay)(self.display) };
|
||||
}
|
||||
}
|
||||
|
||||
/// Error triggered by xlib.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct XError {
|
||||
pub description: String,
|
||||
pub error_code: u8,
|
||||
pub request_code: u8,
|
||||
pub minor_code: u8,
|
||||
}
|
||||
|
||||
impl Error for XError {
|
||||
#[inline]
|
||||
fn description(&self) -> &str {
|
||||
&self.description
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for XError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
write!(formatter, "X error: {} (code: {}, request code: {}, minor code: {})",
|
||||
self.description, self.error_code, self.request_code, self.minor_code)
|
||||
}
|
||||
}
|
||||
|
||||
/// Error returned if this system doesn't have XLib or can't create an X connection.
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum XNotSupported {
|
||||
/// Failed to load one or several shared libraries.
|
||||
LibraryOpenError(ffi::OpenError),
|
||||
/// Connecting to the X server with `XOpenDisplay` failed.
|
||||
XOpenDisplayFailed, // TODO: add better message
|
||||
}
|
||||
|
||||
impl From<ffi::OpenError> for XNotSupported {
|
||||
#[inline]
|
||||
fn from(err: ffi::OpenError) -> XNotSupported {
|
||||
XNotSupported::LibraryOpenError(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for XNotSupported {
|
||||
#[inline]
|
||||
fn description(&self) -> &str {
|
||||
match *self {
|
||||
XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries",
|
||||
XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server",
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn cause(&self) -> Option<&Error> {
|
||||
match *self {
|
||||
XNotSupported::LibraryOpenError(ref err) => Some(err),
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for XNotSupported {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter) -> Result<(), fmt::Error> {
|
||||
formatter.write_str(self.description())
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue