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

1196 lines
50 KiB
Rust
Raw Normal View History

#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
2015-04-24 17:51:23 +10:00
2015-09-24 17:11:59 +10:00
pub use self::monitor::{MonitorId, get_available_monitors, get_primary_monitor};
pub use self::window::{Window2, XWindow};
2015-12-24 20:57:08 +11:00
pub use self::xdisplay::{XConnection, XNotSupported, XError};
2015-04-25 02:52:07 +10:00
pub mod ffi;
use platform::PlatformSpecificWindowBuilderAttributes;
2017-07-01 19:20:13 +10:00
use {CreationError, Event, EventsLoopClosed, WindowEvent, DeviceEvent,
KeyboardInput, ControlFlow};
use events::ModifiersState;
use std::{mem, ptr, slice};
use std::sync::{Arc, Mutex, Weak};
use std::sync::atomic::{self, AtomicBool};
use std::collections::HashMap;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_long, c_uchar, c_ulong};
use libc;
mod events;
mod monitor;
mod window;
mod xdisplay;
mod dnd;
mod util;
use self::dnd::{Dnd, DndState};
// API TRANSITION
//
// We don't use the gen_api_transistion!() macro but rather do the expansion manually:
//
// As this module is nested into platform/linux, its code is not _exactly_ the same as
// the one generated by the macro.
pub struct EventsLoop {
display: Arc<XConnection>,
wm_delete_window: ffi::Atom,
dnd: Dnd,
windows: Arc<Mutex<HashMap<WindowId, WindowData>>>,
devices: Mutex<HashMap<DeviceId, Device>>,
xi2ext: XExtension,
pending_wakeup: Arc<AtomicBool>,
root: ffi::Window,
// A dummy, `InputOnly` window that we can use to receive wakeup events and interrupt blocking
// `XNextEvent` calls.
wakeup_dummy_window: ffi::Window,
}
2017-10-26 05:03:57 +11:00
#[derive(Clone)]
pub struct EventsLoopProxy {
pending_wakeup: Weak<AtomicBool>,
display: Weak<XConnection>,
wakeup_dummy_window: ffi::Window,
}
impl EventsLoop {
pub fn new(display: Arc<XConnection>) -> EventsLoop {
let wm_delete_window = unsafe { (display.xlib.XInternAtom)(display.display, b"WM_DELETE_WINDOW\0".as_ptr() as *const c_char, 0) };
display.check_errors().expect("Failed to call XInternAtom");
let dnd = Dnd::new(Arc::clone(&display))
.expect("Failed to call XInternAtoms when initializing drag and drop");
let xi2ext = unsafe {
let mut result = XExtension {
opcode: mem::uninitialized(),
first_event_id: mem::uninitialized(),
first_error_id: mem::uninitialized(),
};
let res = (display.xlib.XQueryExtension)(
display.display,
b"XInputExtension\0".as_ptr() as *const c_char,
&mut result.opcode as *mut c_int,
&mut result.first_event_id as *mut c_int,
&mut result.first_error_id as *mut c_int);
if res == ffi::False {
panic!("X server missing XInput extension");
}
result
};
unsafe {
let mut xinput_major_ver = ffi::XI_2_Major;
let mut xinput_minor_ver = ffi::XI_2_Minor;
if (display.xinput2.XIQueryVersion)(display.display, &mut xinput_major_ver, &mut xinput_minor_ver) != ffi::Success as libc::c_int {
panic!("X server has XInput extension {}.{} but does not support XInput2", xinput_major_ver, xinput_minor_ver);
}
}
let root = unsafe { (display.xlib.XDefaultRootWindow)(display.display) };
let wakeup_dummy_window = unsafe {
let (x, y, w, h) = (10, 10, 10, 10);
let (border_w, border_px, background_px) = (0, 0, 0);
(display.xlib.XCreateSimpleWindow)(display.display, root, x, y, w, h,
border_w, border_px, background_px)
};
let result = EventsLoop {
pending_wakeup: Arc::new(AtomicBool::new(false)),
display,
wm_delete_window,
dnd,
windows: Arc::new(Mutex::new(HashMap::new())),
devices: Mutex::new(HashMap::new()),
xi2ext,
root,
wakeup_dummy_window,
};
{
// Register for device hotplug events
let mask = ffi::XI_HierarchyChangedMask;
unsafe {
let mut event_mask = ffi::XIEventMask{
deviceid: ffi::XIAllDevices,
mask: &mask as *const _ as *mut c_uchar,
mask_len: mem::size_of_val(&mask) as c_int,
};
(result.display.xinput2.XISelectEvents)(result.display.display, root,
&mut event_mask as *mut ffi::XIEventMask, 1);
}
result.init_device(ffi::XIAllDevices);
}
result
}
/// Returns the `XConnection` of this events loop.
#[inline]
pub fn x_connection(&self) -> &Arc<XConnection> {
&self.display
}
pub fn create_proxy(&self) -> EventsLoopProxy {
EventsLoopProxy {
pending_wakeup: Arc::downgrade(&self.pending_wakeup),
display: Arc::downgrade(&self.display),
wakeup_dummy_window: self.wakeup_dummy_window,
}
}
pub fn poll_events<F>(&mut self, mut callback: F)
where F: FnMut(Event)
{
let mut xev = unsafe { mem::uninitialized() };
loop {
// Get next event
unsafe {
// Ensure XNextEvent won't block
let count = (self.display.xlib.XPending)(self.display.display);
if count == 0 {
break;
}
(self.display.xlib.XNextEvent)(self.display.display, &mut xev);
}
self.process_event(&mut xev, &mut callback);
}
}
pub fn run_forever<F>(&mut self, mut callback: F)
where F: FnMut(Event) -> ControlFlow
{
self.pending_wakeup.store(false, atomic::Ordering::Relaxed);
let mut xev = unsafe { mem::uninitialized() };
loop {
unsafe { (self.display.xlib.XNextEvent)(self.display.display, &mut xev) }; // Blocks as necessary
let mut control_flow = ControlFlow::Continue;
// Track whether or not `Break` was returned when processing the event.
{
let mut cb = |event| {
if let ControlFlow::Break = callback(event) {
control_flow = ControlFlow::Break;
}
};
self.process_event(&mut xev, &mut cb);
}
if let ControlFlow::Break = control_flow {
break;
}
}
}
fn process_event<F>(&mut self, xev: &mut ffi::XEvent, mut callback: F)
where F: FnMut(Event)
{
let xlib = &self.display.xlib;
// Handle dead keys and other input method funtimes
if ffi::True == unsafe { (self.display.xlib.XFilterEvent)(xev, { let xev: &ffi::XAnyEvent = xev.as_ref(); xev.window }) } {
return;
}
match xev.get_type() {
ffi::MappingNotify => {
unsafe { (xlib.XRefreshKeyboardMapping)(xev.as_mut()); }
self.display.check_errors().expect("Failed to call XRefreshKeyboardMapping");
}
ffi::ClientMessage => {
let client_msg: &ffi::XClientMessageEvent = xev.as_ref();
let window = client_msg.window;
let window_id = mkwid(window);
if client_msg.data.get_long(0) as ffi::Atom == self.wm_delete_window {
callback(Event::WindowEvent { window_id, event: WindowEvent::Closed })
} else if client_msg.message_type == self.dnd.atoms.enter {
let source_window = client_msg.data.get_long(0) as c_ulong;
let flags = client_msg.data.get_long(1);
let version = flags >> 24;
self.dnd.version = Some(version);
let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1;
if !has_more_types {
let type_list = vec![
client_msg.data.get_long(2) as c_ulong,
client_msg.data.get_long(3) as c_ulong,
client_msg.data.get_long(4) as c_ulong
];
self.dnd.type_list = Some(type_list);
} else if let Ok(more_types) = unsafe { self.dnd.get_type_list(source_window) } {
self.dnd.type_list = Some(more_types);
}
} else if client_msg.message_type == self.dnd.atoms.position {
// This event occurs every time the mouse moves while a file's being dragged
// over our window. We emit HoveredFile in response; while the Mac OS X backend
// does that upon a drag entering, XDnD doesn't have access to the actual drop
// data until this event. For parity with other platforms, we only emit
// HoveredFile the first time, though if winit's API is later extended to
// supply position updates with HoveredFile or another event, implementing
// that here would be trivial.
let source_window = client_msg.data.get_long(0) as c_ulong;
// Equivalent to (x << shift) | y
// where shift = mem::size_of::<c_short>() * 8
// Note that coordinates are in "desktop space", not "window space"
// (in x11 parlance, they're root window coordinates)
//let packed_coordinates = client_msg.data.get_long(2);
//let shift = mem::size_of::<libc::c_short>() * 8;
//let x = packed_coordinates >> shift;
//let y = packed_coordinates & !(x << shift);
// By our own state flow, version should never be None at this point.
let version = self.dnd.version.unwrap_or(5);
// Action is specified in versions 2 and up, though we don't need it anyway.
//let action = client_msg.data.get_long(4);
let accepted = if let Some(ref type_list) = self.dnd.type_list {
type_list.contains(&self.dnd.atoms.uri_list)
} else {
false
};
if accepted {
self.dnd.source_window = Some(source_window);
unsafe {
if self.dnd.result.is_none() {
let time = if version >= 1 {
client_msg.data.get_long(3) as c_ulong
} else {
// In version 0, time isn't specified
ffi::CurrentTime
};
// This results in the SelectionNotify event below
self.dnd.convert_selection(window, time);
}
self.dnd.send_status(window, source_window, DndState::Accepted)
.expect("Failed to send XDnD status message.");
}
} else {
unsafe {
self.dnd.send_status(window, source_window, DndState::Rejected)
.expect("Failed to send XDnD status message.");
self.dnd.send_finished(window, source_window, DndState::Rejected)
.expect("Failed to send XDnD finished message.");
}
self.dnd.reset();
}
} else if client_msg.message_type == self.dnd.atoms.drop {
if let Some(source_window) = self.dnd.source_window {
if let Some(Ok(ref path_list)) = self.dnd.result {
for path in path_list {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::DroppedFile(path.clone()),
});
}
}
unsafe {
self.dnd.send_finished(window, source_window, DndState::Accepted)
.expect("Failed to send XDnD finished message.");
}
}
self.dnd.reset();
} else if client_msg.message_type == self.dnd.atoms.leave {
self.dnd.reset();
callback(Event::WindowEvent {
window_id,
event: WindowEvent::HoveredFileCancelled,
});
} else if self.pending_wakeup.load(atomic::Ordering::Relaxed) {
self.pending_wakeup.store(false, atomic::Ordering::Relaxed);
callback(Event::Awakened);
}
}
ffi::SelectionNotify => {
let xsel: &ffi::XSelectionEvent = xev.as_ref();
let window = xsel.requestor;
let window_id = mkwid(window);
if xsel.property == self.dnd.atoms.selection {
let mut result = None;
// This is where we receive data from drag and drop
if let Ok(mut data) = unsafe { self.dnd.read_data(window) } {
let parse_result = self.dnd.parse_data(&mut data);
if let Ok(ref path_list) = parse_result {
for path in path_list {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::HoveredFile(path.clone()),
});
}
}
result = Some(parse_result);
}
self.dnd.result = result;
}
}
ffi::ConfigureNotify => {
let xev: &ffi::XConfigureEvent = xev.as_ref();
let window = xev.window;
let window_id = mkwid(window);
let new_size = (xev.width, xev.height);
let new_position = (xev.x, xev.y);
// Gymnastics to ensure self.windows isn't locked when we invoke callback
let (resized, moved) = {
let mut windows = self.windows.lock().unwrap();
let window_data = windows.get_mut(&WindowId(window)).unwrap();
if window_data.config.is_none() {
window_data.config = Some(WindowConfig::new(xev));
(true, true)
} else {
let window_state = window_data.config.as_mut().unwrap();
(if window_state.size != new_size {
window_state.size = new_size;
true
} else { false },
if window_state.position != new_position {
window_state.position = new_position;
true
} else { false })
}
};
if resized {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Resized(xev.width as u32, xev.height as u32),
});
}
if moved {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Moved(xev.x as i32, xev.y as i32),
});
}
}
ffi::Expose => {
let xev: &ffi::XExposeEvent = xev.as_ref();
let window = xev.window;
let window_id = mkwid(window);
callback(Event::WindowEvent { window_id, event: WindowEvent::Refresh });
}
// FIXME: Use XInput2 + libxkbcommon for keyboard input!
ffi::KeyPress | ffi::KeyRelease => {
use events::ElementState::{Pressed, Released};
let state;
if xev.get_type() == ffi::KeyPress {
state = Pressed;
} else {
state = Released;
}
let xkev: &mut ffi::XKeyEvent = xev.as_mut();
let window = xkev.window;
let window_id = mkwid(window);
let modifiers = ModifiersState {
alt: xkev.state & ffi::Mod1Mask != 0,
shift: xkev.state & ffi::ShiftMask != 0,
ctrl: xkev.state & ffi::ControlMask != 0,
logo: xkev.state & ffi::Mod4Mask != 0,
2017-07-15 23:25:19 +10:00
};
2017-05-10 02:50:16 +10:00
let keysym = unsafe {
2017-09-22 00:09:07 +10:00
let mut keysym = 0;
(self.display.xlib.XLookupString)(xkev, ptr::null_mut(), 0, &mut keysym, ptr::null_mut());
keysym
};
let vkey = events::keysym_to_element(keysym as libc::c_uint);
callback(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput {
// Typical virtual core keyboard ID. xinput2 needs to be used to get a reliable value.
device_id: mkdid(3),
input: KeyboardInput {
state: state,
scancode: xkev.keycode - 8,
virtual_keycode: vkey,
modifiers,
},
}});
if state == Pressed {
let written = unsafe {
use std::str;
const INIT_BUFF_SIZE: usize = 16;
let mut windows = self.windows.lock().unwrap();
let window_data = windows.get_mut(&WindowId(window)).unwrap();
/* buffer allocated on heap instead of stack, due to the possible
* reallocation */
let mut buffer: Vec<u8> = vec![mem::uninitialized(); INIT_BUFF_SIZE];
let mut keysym: ffi::KeySym = 0;
let mut status: ffi::Status = 0;
let mut count = (self.display.xlib.Xutf8LookupString)(window_data.ic, xkev,
mem::transmute(buffer.as_mut_ptr()),
buffer.len() as libc::c_int,
&mut keysym, &mut status);
/* buffer overflowed, dynamically reallocate */
if status == ffi::XBufferOverflow {
buffer = vec![mem::uninitialized(); count as usize];
count = (self.display.xlib.Xutf8LookupString)(window_data.ic, xkev,
mem::transmute(buffer.as_mut_ptr()),
buffer.len() as libc::c_int,
&mut keysym, &mut status);
}
str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string()
};
for chr in written.chars() {
let event = Event::WindowEvent {
window_id,
event: WindowEvent::ReceivedCharacter(chr),
};
callback(event);
}
}
}
ffi::GenericEvent => {
let guard = if let Some(e) = GenericEventCookie::from_event(&self.display, *xev) { e } else { return };
let xev = &guard.cookie;
if self.xi2ext.opcode != xev.extension {
return;
}
use events::WindowEvent::{Focused, CursorEntered, MouseInput, CursorLeft, CursorMoved, MouseWheel, AxisMotion};
use events::ElementState::{Pressed, Released};
use events::MouseButton::{Left, Right, Middle, Other};
use events::MouseScrollDelta::LineDelta;
use events::{Touch, TouchPhase};
match xev.evtype {
ffi::XI_ButtonPress | ffi::XI_ButtonRelease => {
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
let window_id = mkwid(xev.event);
let device_id = mkdid(xev.deviceid);
if (xev.flags & ffi::XIPointerEmulated) != 0 && self.windows.lock().unwrap().get(&WindowId(xev.event)).unwrap().multitouch {
// Deliver multi-touch events instead of emulated mouse events.
return;
}
let modifiers = ModifiersState::from(xev.mods);
let state = if xev.evtype == ffi::XI_ButtonPress {
Pressed
} else {
Released
};
match xev.detail as u32 {
ffi::Button1 => callback(Event::WindowEvent {
window_id,
event: MouseInput {
device_id,
state,
button: Left,
modifiers,
},
}),
ffi::Button2 => callback(Event::WindowEvent {
window_id,
event: MouseInput {
device_id,
state,
button: Middle,
modifiers,
},
}),
ffi::Button3 => callback(Event::WindowEvent {
window_id,
event: MouseInput {
device_id,
state,
button: Right,
modifiers,
},
}),
// Suppress emulated scroll wheel clicks, since we handle the real motion events for those.
// In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in
// turn) as axis motion, so we don't otherwise special-case these button presses.
4 | 5 | 6 | 7 => if xev.flags & ffi::XIPointerEmulated == 0 {
callback(Event::WindowEvent {
window_id,
event: MouseWheel {
device_id,
delta: match xev.detail {
4 => LineDelta(0.0, 1.0),
5 => LineDelta(0.0, -1.0),
6 => LineDelta(-1.0, 0.0),
7 => LineDelta(1.0, 0.0),
_ => unreachable!(),
},
phase: TouchPhase::Moved,
modifiers,
},
});
},
x => callback(Event::WindowEvent {
window_id,
event: MouseInput {
device_id,
state,
button: Other(x as u8),
modifiers,
},
}),
}
}
ffi::XI_Motion => {
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
let device_id = mkdid(xev.deviceid);
let window_id = mkwid(xev.event);
let new_cursor_pos = (xev.event_x, xev.event_y);
let modifiers = ModifiersState::from(xev.mods);
// Gymnastics to ensure self.windows isn't locked when we invoke callback
if {
let mut windows = self.windows.lock().unwrap();
let window_data = windows.get_mut(&WindowId(xev.event)).unwrap();
if Some(new_cursor_pos) != window_data.cursor_pos {
window_data.cursor_pos = Some(new_cursor_pos);
true
} else { false }
} {
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id,
position: new_cursor_pos,
modifiers,
},
});
}
// More gymnastics, for self.devices
let mut events = Vec::new();
{
let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) };
let mut devices = self.devices.lock().unwrap();
let physical_device = devices.get_mut(&DeviceId(xev.sourceid)).unwrap();
let mut value = xev.valuators.values;
for i in 0..xev.valuators.mask_len*8 {
if ffi::XIMaskIsSet(mask, i) {
let x = unsafe { *value };
if let Some(&mut (_, ref mut info)) = physical_device.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == i) {
let delta = (x - info.position) / info.increment;
info.position = x;
events.push(Event::WindowEvent {
window_id,
event: MouseWheel {
device_id,
delta: match info.orientation {
ScrollOrientation::Horizontal => LineDelta(delta as f32, 0.0),
// X11 vertical scroll coordinates are opposite to winit's
ScrollOrientation::Vertical => LineDelta(0.0, -delta as f32),
},
phase: TouchPhase::Moved,
modifiers,
},
});
} else {
events.push(Event::WindowEvent {
window_id,
event: AxisMotion {
device_id,
axis: i as u32,
value: unsafe { *value },
},
});
}
value = unsafe { value.offset(1) };
}
}
}
for event in events {
callback(event);
}
}
ffi::XI_Enter => {
let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) };
2017-07-09 17:47:52 +10:00
let window_id = mkwid(xev.event);
let device_id = mkdid(xev.deviceid);
2017-07-09 17:47:52 +10:00
let mut devices = self.devices.lock().unwrap();
let physical_device = devices.get_mut(&DeviceId(xev.sourceid)).unwrap();
for info in DeviceInfo::get(&self.display, ffi::XIAllDevices).iter() {
if info.deviceid == xev.sourceid {
physical_device.reset_scroll_position(info);
}
}
callback(Event::WindowEvent {
window_id,
event: CursorEntered { device_id },
});
2017-07-09 17:47:52 +10:00
let new_cursor_pos = (xev.event_x, xev.event_y);
// The mods field on this event isn't actually useful, so we have to
// query the pointer device.
let modifiers = unsafe {
util::query_pointer(
&self.display,
xev.event,
xev.deviceid,
).expect("Failed to query pointer device")
}.get_modifier_state();
callback(Event::WindowEvent { window_id, event: CursorMoved {
device_id,
position: new_cursor_pos,
modifiers,
}})
}
ffi::XI_Leave => {
let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) };
callback(Event::WindowEvent {
window_id: mkwid(xev.event),
event: CursorLeft { device_id: mkdid(xev.deviceid) },
});
}
ffi::XI_FocusIn => {
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
let window_id = mkwid(xev.event);
unsafe {
let mut windows = self.windows.lock().unwrap();
let window_data = windows.get_mut(&WindowId(xev.event)).unwrap();
(self.display.xlib.XSetICFocus)(window_data.ic);
}
callback(Event::WindowEvent { window_id, event: Focused(true) });
// The deviceid for this event is for a keyboard instead of a pointer, so
// we have to do a little extra work.
let device_info = DeviceInfo::get(&self.display, xev.deviceid);
// For master devices, the attachment field contains the ID of the paired
// master device; for the master keyboard, the attachment is the master
// pointer, and vice versa.
let pointer_id = unsafe { (*device_info.info) }.attachment;
callback(Event::WindowEvent {
window_id,
event: CursorMoved {
device_id: mkdid(pointer_id),
position: (xev.event_x, xev.event_y),
modifiers: ModifiersState::from(xev.mods),
}
});
}
ffi::XI_FocusOut => {
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
unsafe {
let mut windows = self.windows.lock().unwrap();
let window_data = windows.get_mut(&WindowId(xev.event)).unwrap();
(self.display.xlib.XUnsetICFocus)(window_data.ic);
}
callback(Event::WindowEvent {
window_id: mkwid(xev.event),
event: Focused(false),
})
}
ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => {
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
let window_id = mkwid(xev.event);
let phase = match xev.evtype {
ffi::XI_TouchBegin => TouchPhase::Started,
ffi::XI_TouchUpdate => TouchPhase::Moved,
ffi::XI_TouchEnd => TouchPhase::Ended,
_ => unreachable!()
};
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Touch(Touch {
device_id: mkdid(xev.deviceid),
phase,
location: (xev.event_x, xev.event_y),
id: xev.detail as u64,
},
)})
}
ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => {
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
if xev.flags & ffi::XIPointerEmulated == 0 {
callback(Event::DeviceEvent { device_id: mkdid(xev.deviceid), event: DeviceEvent::Button {
2017-07-01 19:20:13 +10:00
button: xev.detail as u32,
state: match xev.evtype {
ffi::XI_RawButtonPress => Pressed,
ffi::XI_RawButtonRelease => Released,
_ => unreachable!(),
},
}});
}
}
ffi::XI_RawMotion => {
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
let did = mkdid(xev.deviceid);
let mask = unsafe { slice::from_raw_parts(xev.valuators.mask, xev.valuators.mask_len as usize) };
let mut value = xev.raw_values;
let mut mouse_delta = (0.0, 0.0);
let mut scroll_delta = (0.0, 0.0);
for i in 0..xev.valuators.mask_len*8 {
if ffi::XIMaskIsSet(mask, i) {
let x = unsafe { *value };
// We assume that every XInput2 device with analog axes is a pointing device emitting
// relative coordinates.
match i {
0 => mouse_delta.0 = x,
1 => mouse_delta.1 = x,
2 => scroll_delta.0 = x as f32,
3 => scroll_delta.1 = x as f32,
_ => {},
}
callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::Motion {
2017-07-01 19:20:13 +10:00
axis: i as u32,
value: x,
}});
value = unsafe { value.offset(1) };
}
}
if mouse_delta != (0.0, 0.0) {
callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseMotion {
delta: mouse_delta,
}});
}
if scroll_delta != (0.0, 0.0) {
callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::MouseWheel {
delta: LineDelta(scroll_delta.0, scroll_delta.1),
}});
}
}
ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => {
// TODO: Use xkbcommon for keysym and text decoding
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
let xkeysym = unsafe { (self.display.xlib.XKeycodeToKeysym)(self.display.display, xev.detail as ffi::KeyCode, 0) };
callback(Event::DeviceEvent { device_id: mkdid(xev.deviceid), event: DeviceEvent::Key(KeyboardInput {
scancode: (xev.detail - 8) as u32,
virtual_keycode: events::keysym_to_element(xkeysym as libc::c_uint),
state: match xev.evtype {
ffi::XI_RawKeyPress => Pressed,
ffi::XI_RawKeyRelease => Released,
_ => unreachable!(),
},
modifiers: ModifiersState::default(),
})});
}
ffi::XI_HierarchyChanged => {
let xev: &ffi::XIHierarchyEvent = unsafe { &*(xev.data as *const _) };
for info in unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) } {
if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) {
self.init_device(info.deviceid);
callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Added });
} else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved) {
callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Removed });
let mut devices = self.devices.lock().unwrap();
devices.remove(&DeviceId(info.deviceid));
}
}
}
_ => {}
}
}
_ => {}
}
}
fn init_device(&self, device: c_int) {
let mut devices = self.devices.lock().unwrap();
for info in DeviceInfo::get(&self.display, device).iter() {
devices.insert(DeviceId(info.deviceid), Device::new(&self, info));
}
}
}
impl EventsLoopProxy {
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
// Update the `EventsLoop`'s `pending_wakeup` flag.
let display = match (self.pending_wakeup.upgrade(), self.display.upgrade()) {
(Some(wakeup), Some(display)) => {
wakeup.store(true, atomic::Ordering::Relaxed);
display
},
_ => return Err(EventsLoopClosed),
};
// Push an event on the X event queue so that methods run_forever will advance.
//
// NOTE: This code (and the following `XSendEvent` code) is taken from the old
// `WindowProxy::wakeup` implementation. The code assumes that X11 is thread safe. Is this
// true?
let mut xev = ffi::XClientMessageEvent {
type_: ffi::ClientMessage,
window: self.wakeup_dummy_window,
format: 32,
message_type: 0,
serial: 0,
send_event: 0,
display: display.display,
data: unsafe { mem::zeroed() },
};
unsafe {
let propagate = false as i32;
let event_mask = 0;
let xevent = &mut xev as *mut ffi::XClientMessageEvent as *mut ffi::XEvent;
(display.xlib.XSendEvent)(display.display, self.wakeup_dummy_window, propagate, event_mask, xevent);
(display.xlib.XFlush)(display.display);
display.check_errors().expect("Failed to call XSendEvent after wakeup");
}
Ok(())
}
}
struct DeviceInfo<'a> {
display: &'a XConnection,
info: *const ffi::XIDeviceInfo,
count: usize,
}
impl<'a> DeviceInfo<'a> {
fn get(display: &'a XConnection, device: c_int) -> Self {
unsafe {
let mut count = mem::uninitialized();
let info = (display.xinput2.XIQueryDevice)(display.display, device, &mut count);
DeviceInfo {
display: display,
info: info,
count: count as usize,
}
}
}
}
impl<'a> Drop for DeviceInfo<'a> {
fn drop(&mut self) {
unsafe { (self.display.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
}
}
impl<'a> ::std::ops::Deref for DeviceInfo<'a> {
type Target = [ffi::XIDeviceInfo];
fn deref(&self) -> &Self::Target {
unsafe { slice::from_raw_parts(self.info, self.count) }
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WindowId(ffi::Window);
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId(c_int);
pub struct Window {
pub window: Arc<Window2>,
display: Weak<XConnection>,
windows: Weak<Mutex<HashMap<WindowId, WindowData>>>,
}
impl ::std::ops::Deref for Window {
type Target = Window2;
#[inline]
fn deref(&self) -> &Window2 {
&*self.window
}
}
// 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(());
}
impl Window {
pub fn new(x_events_loop: &EventsLoop,
window: &::WindowAttributes,
pl_attribs: &PlatformSpecificWindowBuilderAttributes)
-> Result<Self, CreationError>
{
let win = ::std::sync::Arc::new(try!(Window2::new(&x_events_loop, window, pl_attribs)));
// creating IM
let im = unsafe {
let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap();
let im = (x_events_loop.display.xlib.XOpenIM)(x_events_loop.display.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
if im.is_null() {
panic!("XOpenIM failed");
}
im
};
// creating input context
let ic = unsafe {
let ic = (x_events_loop.display.xlib.XCreateIC)(im,
b"inputStyle\0".as_ptr() as *const _,
ffi::XIMPreeditNothing | ffi::XIMStatusNothing, b"clientWindow\0".as_ptr() as *const _,
win.id().0, ptr::null::<()>());
if ic.is_null() {
panic!("XCreateIC failed");
}
(x_events_loop.display.xlib.XSetICFocus)(ic);
x_events_loop.display.check_errors().expect("Failed to call XSetICFocus");
ic
};
x_events_loop.windows.lock().unwrap().insert(win.id(), WindowData {
im: im,
ic: ic,
2017-07-13 03:26:11 +10:00
ic_spot: ffi::XPoint {x: 0, y: 0},
config: None,
multitouch: window.multitouch,
cursor_pos: None,
});
Ok(Window {
window: win,
windows: Arc::downgrade(&x_events_loop.windows),
display: Arc::downgrade(&x_events_loop.display),
})
}
#[inline]
pub fn id(&self) -> WindowId {
self.window.id()
}
#[inline]
pub fn send_xim_spot(&self, x: i16, y: i16) {
if let (Some(windows), Some(display)) = (self.windows.upgrade(), self.display.upgrade()) {
let nspot = ffi::XPoint{x: x, y: y};
let mut windows = windows.lock().unwrap();
let w = windows.get_mut(&self.window.id()).unwrap();
2017-07-13 03:26:11 +10:00
if w.ic_spot.x == x && w.ic_spot.y == y {
return
}
2017-07-13 03:26:11 +10:00
w.ic_spot = nspot;
unsafe {
let preedit_attr = (display.xlib.XVaCreateNestedList)
(0, b"spotLocation\0", &nspot, ptr::null::<()>());
(display.xlib.XSetICValues)(w.ic, b"preeditAttributes\0",
preedit_attr, ptr::null::<()>());
(display.xlib.XFree)(preedit_attr);
}
}
}
}
impl Drop for Window {
fn drop(&mut self) {
if let (Some(windows), Some(display)) = (self.windows.upgrade(), self.display.upgrade()) {
let mut windows = windows.lock().unwrap();
let w = windows.remove(&self.window.id()).unwrap();
let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap();
unsafe {
(display.xlib.XDestroyIC)(w.ic);
(display.xlib.XCloseIM)(w.im);
}
}
}
}
/// State maintained for translating window-related events
struct WindowData {
config: Option<WindowConfig>,
im: ffi::XIM,
ic: ffi::XIC,
2017-07-13 03:26:11 +10:00
ic_spot: ffi::XPoint,
multitouch: bool,
cursor_pos: Option<(f64, f64)>,
}
// Required by ffi members
unsafe impl Send for WindowData {}
struct WindowConfig {
size: (c_int, c_int),
position: (c_int, c_int),
}
impl WindowConfig {
fn new(event: &ffi::XConfigureEvent) -> Self {
WindowConfig {
size: (event.width, event.height),
position: (event.x, event.y),
}
}
}
/// 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);
}
}
}
#[derive(Debug, Copy, Clone)]
struct XExtension {
opcode: c_int,
first_event_id: c_int,
first_error_id: c_int,
}
fn mkwid(w: ffi::Window) -> ::WindowId { ::WindowId(::platform::WindowId::X(WindowId(w))) }
fn mkdid(w: c_int) -> ::DeviceId { ::DeviceId(::platform::DeviceId::X(DeviceId(w))) }
#[derive(Debug)]
struct Device {
name: String,
scroll_axes: Vec<(i32, ScrollAxis)>,
}
#[derive(Debug, Copy, Clone)]
struct ScrollAxis {
increment: f64,
orientation: ScrollOrientation,
position: f64,
}
#[derive(Debug, Copy, Clone)]
enum ScrollOrientation {
Vertical,
Horizontal,
}
impl Device {
fn new(el: &EventsLoop, info: &ffi::XIDeviceInfo) -> Self
{
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
2017-07-09 17:47:52 +10:00
let mut scroll_axes = Vec::new();
2017-07-09 17:47:52 +10:00
if Device::physical_device(info) {
// Register for global raw events
let mask = ffi::XI_RawMotionMask
| ffi::XI_RawButtonPressMask | ffi::XI_RawButtonReleaseMask
| ffi::XI_RawKeyPressMask | ffi::XI_RawKeyReleaseMask;
unsafe {
let mut event_mask = ffi::XIEventMask{
deviceid: info.deviceid,
mask: &mask as *const _ as *mut c_uchar,
mask_len: mem::size_of_val(&mask) as c_int,
};
(el.display.xinput2.XISelectEvents)(el.display.display, el.root, &mut event_mask as *mut ffi::XIEventMask, 1);
}
// Identify scroll axes
2017-07-09 17:47:52 +10:00
for class_ptr in Device::classes(info) {
let class = unsafe { &**class_ptr };
match class._type {
ffi::XIScrollClass => {
let info = unsafe { mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class) };
scroll_axes.push((info.number, ScrollAxis {
increment: info.increment,
orientation: match info.scroll_type {
ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal,
ffi::XIScrollTypeVertical => ScrollOrientation::Vertical,
_ => { unreachable!() }
},
position: 0.0,
}));
}
_ => {}
}
}
2017-07-09 17:47:52 +10:00
}
let mut device = Device {
name: name.into_owned(),
scroll_axes: scroll_axes,
};
device.reset_scroll_position(info);
device
}
fn reset_scroll_position(&mut self, info: &ffi::XIDeviceInfo) {
if Device::physical_device(info) {
for class_ptr in Device::classes(info) {
let class = unsafe { &**class_ptr };
match class._type {
ffi::XIValuatorClass => {
let info = unsafe { mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class) };
2017-07-09 17:47:52 +10:00
if let Some(&mut (_, ref mut axis)) = self.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == info.number) {
axis.position = info.value;
}
}
_ => {}
}
}
}
2017-07-09 17:47:52 +10:00
}
2017-07-09 17:47:52 +10:00
#[inline]
fn physical_device(info: &ffi::XIDeviceInfo) -> bool {
info._use == ffi::XISlaveKeyboard || info._use == ffi::XISlavePointer || info._use == ffi::XIFloatingSlave
}
#[inline]
fn classes(info: &ffi::XIDeviceInfo) -> &[*const ffi::XIAnyClassInfo] {
unsafe { slice::from_raw_parts(info.classes as *const *const ffi::XIAnyClassInfo, info.num_classes as usize) }
}
}