2016-03-02 13:06:13 +11:00
|
|
|
#![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};
|
2017-09-07 01:32:24 +10:00
|
|
|
pub use self::window::{Window2, XWindow};
|
2015-12-24 20:57:08 +11:00
|
|
|
pub use self::xdisplay::{XConnection, XNotSupported, XError};
|
2015-04-24 22:22:57 +10:00
|
|
|
|
2015-04-25 02:52:07 +10:00
|
|
|
pub mod ffi;
|
2015-04-24 22:22:57 +10:00
|
|
|
|
2017-03-04 07:41:51 +11:00
|
|
|
use platform::PlatformSpecificWindowBuilderAttributes;
|
2017-07-01 19:20:13 +10:00
|
|
|
use {CreationError, Event, EventsLoopClosed, WindowEvent, DeviceEvent,
|
2017-06-02 21:19:45 +10:00
|
|
|
KeyboardInput, ControlFlow};
|
2018-01-08 21:06:02 +11:00
|
|
|
use events::ModifiersState;
|
2017-03-04 07:41:51 +11:00
|
|
|
|
2017-04-23 06:52:35 +10:00
|
|
|
use std::{mem, ptr, slice};
|
|
|
|
use std::sync::{Arc, Mutex, Weak};
|
2017-05-25 23:19:13 +10:00
|
|
|
use std::sync::atomic::{self, AtomicBool};
|
2017-04-23 06:52:35 +10:00
|
|
|
use std::collections::HashMap;
|
|
|
|
use std::ffi::CStr;
|
2018-01-08 21:06:02 +11:00
|
|
|
use std::os::raw::{c_char, c_int, c_long, c_uchar, c_ulong};
|
2017-04-23 06:52:35 +10:00
|
|
|
|
2018-01-08 21:06:02 +11:00
|
|
|
use libc;
|
2017-03-04 07:41:51 +11:00
|
|
|
|
2015-05-17 19:19:06 +10:00
|
|
|
mod events;
|
|
|
|
mod monitor;
|
|
|
|
mod window;
|
|
|
|
mod xdisplay;
|
2017-12-13 22:22:03 +11:00
|
|
|
mod dnd;
|
|
|
|
mod util;
|
|
|
|
|
|
|
|
use self::dnd::{Dnd, DndState};
|
2017-03-04 07:41:51 +11:00
|
|
|
|
|
|
|
// 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 {
|
2017-04-23 06:52:35 +10:00
|
|
|
display: Arc<XConnection>,
|
|
|
|
wm_delete_window: ffi::Atom,
|
2017-12-13 22:22:03 +11:00
|
|
|
dnd: Dnd,
|
2017-06-02 21:19:45 +10:00
|
|
|
windows: Arc<Mutex<HashMap<WindowId, WindowData>>>,
|
2017-04-23 06:52:35 +10:00
|
|
|
devices: Mutex<HashMap<DeviceId, Device>>,
|
|
|
|
xi2ext: XExtension,
|
2017-05-25 23:19:13 +10:00
|
|
|
pending_wakeup: Arc<AtomicBool>,
|
2017-04-23 06:52:35 +10:00
|
|
|
root: ffi::Window,
|
2017-06-17 22:59:56 +10:00
|
|
|
// A dummy, `InputOnly` window that we can use to receive wakeup events and interrupt blocking
|
|
|
|
// `XNextEvent` calls.
|
|
|
|
wakeup_dummy_window: ffi::Window,
|
2017-03-04 07:41:51 +11:00
|
|
|
}
|
|
|
|
|
2017-10-26 05:03:57 +11:00
|
|
|
#[derive(Clone)]
|
2017-05-25 23:19:13 +10:00
|
|
|
pub struct EventsLoopProxy {
|
|
|
|
pending_wakeup: Weak<AtomicBool>,
|
2017-05-31 18:07:51 +10:00
|
|
|
display: Weak<XConnection>,
|
2017-06-17 22:59:56 +10:00
|
|
|
wakeup_dummy_window: ffi::Window,
|
2017-05-25 23:19:13 +10:00
|
|
|
}
|
|
|
|
|
2017-03-04 07:41:51 +11:00
|
|
|
impl EventsLoop {
|
2017-04-23 06:52:35 +10:00
|
|
|
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");
|
|
|
|
|
2017-12-13 22:22:03 +11:00
|
|
|
let dnd = Dnd::new(Arc::clone(&display))
|
|
|
|
.expect("Failed to call XInternAtoms when initializing drag and drop");
|
|
|
|
|
2017-04-23 06:52:35 +10:00
|
|
|
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) };
|
|
|
|
|
2017-06-17 22:59:56 +10:00
|
|
|
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)
|
|
|
|
};
|
|
|
|
|
2017-04-23 06:52:35 +10:00
|
|
|
let result = EventsLoop {
|
2017-05-25 23:19:13 +10:00
|
|
|
pending_wakeup: Arc::new(AtomicBool::new(false)),
|
2017-12-13 22:22:03 +11:00
|
|
|
display,
|
|
|
|
wm_delete_window,
|
|
|
|
dnd,
|
2017-06-02 21:19:45 +10:00
|
|
|
windows: Arc::new(Mutex::new(HashMap::new())),
|
2017-04-23 06:52:35 +10:00
|
|
|
devices: Mutex::new(HashMap::new()),
|
2017-12-13 22:22:03 +11:00
|
|
|
xi2ext,
|
|
|
|
root,
|
|
|
|
wakeup_dummy_window,
|
2017-04-23 06:52:35 +10:00
|
|
|
};
|
|
|
|
|
|
|
|
{
|
|
|
|
// 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);
|
2017-03-04 07:41:51 +11:00
|
|
|
}
|
2017-04-23 06:52:35 +10:00
|
|
|
|
|
|
|
result
|
2017-03-04 07:41:51 +11:00
|
|
|
}
|
|
|
|
|
2017-09-01 19:04:57 +10:00
|
|
|
/// Returns the `XConnection` of this events loop.
|
|
|
|
#[inline]
|
|
|
|
pub fn x_connection(&self) -> &Arc<XConnection> {
|
|
|
|
&self.display
|
|
|
|
}
|
|
|
|
|
2017-05-25 23:19:13 +10:00
|
|
|
pub fn create_proxy(&self) -> EventsLoopProxy {
|
|
|
|
EventsLoopProxy {
|
|
|
|
pending_wakeup: Arc::downgrade(&self.pending_wakeup),
|
2017-05-31 18:07:51 +10:00
|
|
|
display: Arc::downgrade(&self.display),
|
2017-06-17 22:59:56 +10:00
|
|
|
wakeup_dummy_window: self.wakeup_dummy_window,
|
2017-05-25 23:19:13 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-09 22:55:48 +10:00
|
|
|
pub fn poll_events<F>(&mut self, mut callback: F)
|
2017-04-23 06:52:35 +10:00
|
|
|
where F: FnMut(Event)
|
2017-03-04 07:41:51 +11:00
|
|
|
{
|
2017-04-23 06:52:35 +10:00
|
|
|
let mut xev = unsafe { mem::uninitialized() };
|
2017-05-10 02:20:35 +10:00
|
|
|
loop {
|
|
|
|
// Get next event
|
|
|
|
unsafe {
|
|
|
|
// Ensure XNextEvent won't block
|
2017-12-13 22:22:03 +11:00
|
|
|
let count = (self.display.xlib.XPending)(self.display.display);
|
2017-05-10 02:20:35 +10:00
|
|
|
if count == 0 {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-12-13 22:22:03 +11:00
|
|
|
(self.display.xlib.XNextEvent)(self.display.display, &mut xev);
|
2017-05-10 02:20:35 +10:00
|
|
|
}
|
|
|
|
self.process_event(&mut xev, &mut callback);
|
2017-03-04 07:41:51 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-06-02 21:19:45 +10:00
|
|
|
pub fn run_forever<F>(&mut self, mut callback: F)
|
|
|
|
where F: FnMut(Event) -> ControlFlow
|
2017-03-04 07:41:51 +11:00
|
|
|
{
|
2017-05-31 18:07:51 +10:00
|
|
|
self.pending_wakeup.store(false, atomic::Ordering::Relaxed);
|
2017-03-04 07:41:51 +11:00
|
|
|
|
2017-04-23 06:52:35 +10:00
|
|
|
let mut xev = unsafe { mem::uninitialized() };
|
|
|
|
|
2017-03-04 07:41:51 +11:00
|
|
|
loop {
|
2017-12-13 22:22:03 +11:00
|
|
|
unsafe { (self.display.xlib.XNextEvent)(self.display.display, &mut xev) }; // Blocks as necessary
|
2017-05-31 18:07:51 +10:00
|
|
|
|
2017-06-02 21:19:45 +10:00
|
|
|
let mut control_flow = ControlFlow::Continue;
|
|
|
|
|
2017-06-20 21:25:53 +10:00
|
|
|
// Track whether or not `Break` was returned when processing the event.
|
2017-06-02 21:19:45 +10:00
|
|
|
{
|
|
|
|
let mut cb = |event| {
|
2017-06-20 21:25:53 +10:00
|
|
|
if let ControlFlow::Break = callback(event) {
|
|
|
|
control_flow = ControlFlow::Break;
|
2017-06-02 21:19:45 +10:00
|
|
|
}
|
|
|
|
};
|
2017-11-13 07:56:57 +11:00
|
|
|
|
2017-06-02 21:19:45 +10:00
|
|
|
self.process_event(&mut xev, &mut cb);
|
|
|
|
}
|
|
|
|
|
2017-06-20 21:25:53 +10:00
|
|
|
if let ControlFlow::Break = control_flow {
|
2017-03-04 07:41:51 +11:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-04-23 06:52:35 +10:00
|
|
|
|
2017-12-13 22:22:03 +11:00
|
|
|
fn process_event<F>(&mut self, xev: &mut ffi::XEvent, mut callback: F)
|
2017-04-23 06:52:35 +10:00
|
|
|
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();
|
|
|
|
|
2018-01-08 21:06:02 +11:00
|
|
|
let window = client_msg.window;
|
|
|
|
let window_id = mkwid(window);
|
|
|
|
|
2017-04-23 06:52:35 +10:00
|
|
|
if client_msg.data.get_long(0) as ffi::Atom == self.wm_delete_window {
|
2018-01-08 21:06:02 +11:00
|
|
|
callback(Event::WindowEvent { window_id, event: WindowEvent::Closed })
|
2017-12-13 22:22:03 +11:00
|
|
|
} 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);
|
2017-06-17 22:59:56 +10:00
|
|
|
}
|
2017-12-13 22:22:03 +11:00
|
|
|
} 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
|
2018-01-08 21:06:02 +11:00
|
|
|
self.dnd.convert_selection(window, time);
|
2017-12-13 22:22:03 +11:00
|
|
|
}
|
2018-01-08 21:06:02 +11:00
|
|
|
self.dnd.send_status(window, source_window, DndState::Accepted)
|
2017-12-16 06:37:09 +11:00
|
|
|
.expect("Failed to send XDnD status message.");
|
2017-12-13 22:22:03 +11:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
unsafe {
|
2018-01-08 21:06:02 +11:00
|
|
|
self.dnd.send_status(window, source_window, DndState::Rejected)
|
2017-12-16 06:37:09 +11:00
|
|
|
.expect("Failed to send XDnD status message.");
|
2018-01-08 21:06:02 +11:00
|
|
|
self.dnd.send_finished(window, source_window, DndState::Rejected)
|
2017-12-16 06:37:09 +11:00
|
|
|
.expect("Failed to send XDnD finished message.");
|
2017-12-13 22:22:03 +11:00
|
|
|
}
|
|
|
|
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 {
|
2018-01-08 21:06:02 +11:00
|
|
|
window_id,
|
2017-12-13 22:22:03 +11:00
|
|
|
event: WindowEvent::DroppedFile(path.clone()),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unsafe {
|
2018-01-08 21:06:02 +11:00
|
|
|
self.dnd.send_finished(window, source_window, DndState::Accepted)
|
2017-12-16 06:37:09 +11:00
|
|
|
.expect("Failed to send XDnD finished message.");
|
2017-12-13 22:22:03 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
self.dnd.reset();
|
|
|
|
} else if client_msg.message_type == self.dnd.atoms.leave {
|
|
|
|
self.dnd.reset();
|
|
|
|
callback(Event::WindowEvent {
|
2018-01-08 21:06:02 +11:00
|
|
|
window_id,
|
2017-12-13 22:22:03 +11:00
|
|
|
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();
|
2018-01-08 21:06:02 +11:00
|
|
|
|
|
|
|
let window = xsel.requestor;
|
|
|
|
let window_id = mkwid(window);
|
|
|
|
|
2017-12-13 22:22:03 +11:00
|
|
|
if xsel.property == self.dnd.atoms.selection {
|
|
|
|
let mut result = None;
|
|
|
|
|
|
|
|
// This is where we receive data from drag and drop
|
2018-01-08 21:06:02 +11:00
|
|
|
if let Ok(mut data) = unsafe { self.dnd.read_data(window) } {
|
2017-12-13 22:22:03 +11:00
|
|
|
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 {
|
2018-01-08 21:06:02 +11:00
|
|
|
window_id,
|
2017-12-13 22:22:03 +11:00
|
|
|
event: WindowEvent::HoveredFile(path.clone()),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
result = Some(parse_result);
|
|
|
|
}
|
|
|
|
|
|
|
|
self.dnd.result = result;
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ffi::ConfigureNotify => {
|
|
|
|
let xev: &ffi::XConfigureEvent = xev.as_ref();
|
2018-01-08 21:06:02 +11:00
|
|
|
|
|
|
|
let window = xev.window;
|
|
|
|
let window_id = mkwid(window);
|
|
|
|
|
|
|
|
let new_size = (xev.width, xev.height);
|
|
|
|
let new_position = (xev.x, xev.y);
|
2017-04-23 06:52:35 +10:00
|
|
|
// Gymnastics to ensure self.windows isn't locked when we invoke callback
|
|
|
|
let (resized, moved) = {
|
|
|
|
let mut windows = self.windows.lock().unwrap();
|
2018-01-08 21:06:02 +11:00
|
|
|
let window_data = windows.get_mut(&WindowId(window)).unwrap();
|
2017-04-23 06:52:35 +10:00
|
|
|
if window_data.config.is_none() {
|
|
|
|
window_data.config = Some(WindowConfig::new(xev));
|
|
|
|
(true, true)
|
|
|
|
} else {
|
2018-01-08 21:06:02 +11:00
|
|
|
let window_state = window_data.config.as_mut().unwrap();
|
|
|
|
(if window_state.size != new_size {
|
|
|
|
window_state.size = new_size;
|
2017-04-23 06:52:35 +10:00
|
|
|
true
|
|
|
|
} else { false },
|
2018-01-08 21:06:02 +11:00
|
|
|
if window_state.position != new_position {
|
|
|
|
window_state.position = new_position;
|
2017-04-23 06:52:35 +10:00
|
|
|
true
|
|
|
|
} else { false })
|
|
|
|
}
|
|
|
|
};
|
|
|
|
if resized {
|
2018-01-08 21:06:02 +11:00
|
|
|
callback(Event::WindowEvent {
|
|
|
|
window_id,
|
|
|
|
event: WindowEvent::Resized(xev.width as u32, xev.height as u32),
|
|
|
|
});
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
if moved {
|
2018-01-08 21:06:02 +11:00
|
|
|
callback(Event::WindowEvent {
|
|
|
|
window_id,
|
|
|
|
event: WindowEvent::Moved(xev.x as i32, xev.y as i32),
|
|
|
|
});
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
ffi::Expose => {
|
2018-01-08 21:06:02 +11:00
|
|
|
let xev: &ffi::XExposeEvent = xev.as_ref();
|
|
|
|
|
|
|
|
let window = xev.window;
|
|
|
|
let window_id = mkwid(window);
|
|
|
|
|
|
|
|
callback(Event::WindowEvent { window_id, event: WindowEvent::Refresh });
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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();
|
|
|
|
|
2018-01-08 21:06:02 +11:00
|
|
|
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-04-23 06:52:35 +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
|
2017-05-08 14:16:48 +10:00
|
|
|
};
|
|
|
|
|
|
|
|
let vkey = events::keysym_to_element(keysym as libc::c_uint);
|
|
|
|
|
2018-01-08 21:06:02 +11:00
|
|
|
callback(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput {
|
2017-05-08 14:16:48 +10:00
|
|
|
// Typical virtual core keyboard ID. xinput2 needs to be used to get a reliable value.
|
|
|
|
device_id: mkdid(3),
|
|
|
|
input: KeyboardInput {
|
|
|
|
state: state,
|
2017-09-25 15:25:36 +10:00
|
|
|
scancode: xkev.keycode - 8,
|
2017-05-08 14:16:48 +10:00
|
|
|
virtual_keycode: vkey,
|
2018-01-08 21:06:02 +11:00
|
|
|
modifiers,
|
2017-05-08 14:16:48 +10:00
|
|
|
},
|
|
|
|
}});
|
|
|
|
|
2017-04-23 06:52:35 +10:00
|
|
|
if state == Pressed {
|
|
|
|
let written = unsafe {
|
|
|
|
use std::str;
|
|
|
|
|
2017-07-02 02:24:35 +10:00
|
|
|
const INIT_BUFF_SIZE: usize = 16;
|
2017-04-23 06:52:35 +10:00
|
|
|
let mut windows = self.windows.lock().unwrap();
|
2018-01-08 21:06:02 +11:00
|
|
|
let window_data = windows.get_mut(&WindowId(window)).unwrap();
|
2017-07-02 02:24:35 +10:00
|
|
|
/* 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,
|
2017-04-23 06:52:35 +10:00
|
|
|
mem::transmute(buffer.as_mut_ptr()),
|
2017-07-02 02:24:35 +10:00
|
|
|
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);
|
|
|
|
}
|
2017-04-23 06:52:35 +10:00
|
|
|
|
|
|
|
str::from_utf8(&buffer[..count as usize]).unwrap_or("").to_string()
|
|
|
|
};
|
|
|
|
|
|
|
|
for chr in written.chars() {
|
2017-06-02 21:19:45 +10:00
|
|
|
let event = Event::WindowEvent {
|
2018-01-08 21:06:02 +11:00
|
|
|
window_id,
|
2017-06-02 21:19:45 +10:00
|
|
|
event: WindowEvent::ReceivedCharacter(chr),
|
|
|
|
};
|
|
|
|
callback(event);
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2017-11-13 07:56:57 +11:00
|
|
|
use events::WindowEvent::{Focused, CursorEntered, MouseInput, CursorLeft, CursorMoved, MouseWheel, AxisMotion};
|
2017-04-23 06:52:35 +10:00
|
|
|
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 _) };
|
2018-01-08 21:06:02 +11:00
|
|
|
let window_id = mkwid(xev.event);
|
|
|
|
let device_id = mkdid(xev.deviceid);
|
2017-04-23 06:52:35 +10:00
|
|
|
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;
|
|
|
|
}
|
2017-12-27 08:46:28 +11:00
|
|
|
|
2018-01-08 21:06:02 +11:00
|
|
|
let modifiers = ModifiersState::from(xev.mods);
|
2017-12-27 08:46:28 +11:00
|
|
|
|
2017-04-23 06:52:35 +10:00
|
|
|
let state = if xev.evtype == ffi::XI_ButtonPress {
|
|
|
|
Pressed
|
|
|
|
} else {
|
|
|
|
Released
|
|
|
|
};
|
|
|
|
match xev.detail as u32 {
|
2018-01-08 21:06:02 +11:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
}),
|
2017-04-23 06:52:35 +10:00
|
|
|
|
|
|
|
// 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.
|
2017-07-31 04:40:52 +10:00
|
|
|
4 | 5 | 6 | 7 => if xev.flags & ffi::XIPointerEmulated == 0 {
|
2018-01-08 21:06:02 +11:00
|
|
|
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,
|
2017-07-31 04:40:52 +10:00
|
|
|
},
|
2018-01-08 21:06:02 +11:00
|
|
|
});
|
2017-07-31 04:40:52 +10:00
|
|
|
},
|
2017-04-23 06:52:35 +10:00
|
|
|
|
2018-01-08 21:06:02 +11:00
|
|
|
x => callback(Event::WindowEvent {
|
|
|
|
window_id,
|
|
|
|
event: MouseInput {
|
|
|
|
device_id,
|
|
|
|
state,
|
|
|
|
button: Other(x as u8),
|
|
|
|
modifiers,
|
|
|
|
},
|
|
|
|
}),
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
ffi::XI_Motion => {
|
|
|
|
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
2018-01-08 21:06:02 +11:00
|
|
|
let device_id = mkdid(xev.deviceid);
|
|
|
|
let window_id = mkwid(xev.event);
|
2017-04-23 06:52:35 +10:00
|
|
|
let new_cursor_pos = (xev.event_x, xev.event_y);
|
2018-01-08 21:06:02 +11:00
|
|
|
|
|
|
|
let modifiers = ModifiersState::from(xev.mods);
|
|
|
|
|
2017-04-23 06:52:35 +10:00
|
|
|
// 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 }
|
|
|
|
} {
|
2018-01-08 21:06:02 +11:00
|
|
|
callback(Event::WindowEvent {
|
|
|
|
window_id,
|
|
|
|
event: CursorMoved {
|
|
|
|
device_id,
|
|
|
|
position: new_cursor_pos,
|
|
|
|
modifiers,
|
|
|
|
},
|
|
|
|
});
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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) {
|
2017-11-13 07:56:57 +11:00
|
|
|
let x = unsafe { *value };
|
2017-04-23 06:52:35 +10:00
|
|
|
if let Some(&mut (_, ref mut info)) = physical_device.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == i) {
|
2017-11-13 07:56:57 +11:00
|
|
|
let delta = (x - info.position) / info.increment;
|
|
|
|
info.position = x;
|
2018-01-08 21:06:02 +11:00
|
|
|
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,
|
2017-04-23 06:52:35 +10:00
|
|
|
},
|
2018-01-08 21:06:02 +11:00
|
|
|
});
|
2017-04-23 06:52:35 +10:00
|
|
|
} else {
|
2018-01-08 21:06:02 +11:00
|
|
|
events.push(Event::WindowEvent {
|
|
|
|
window_id,
|
|
|
|
event: AxisMotion {
|
|
|
|
device_id,
|
|
|
|
axis: i as u32,
|
|
|
|
value: unsafe { *value },
|
|
|
|
},
|
|
|
|
});
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
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
|
|
|
|
2018-01-08 21:06:02 +11:00
|
|
|
let window_id = mkwid(xev.event);
|
|
|
|
let device_id = mkdid(xev.deviceid);
|
2017-12-27 08:46:28 +11:00
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2018-01-08 21:06:02 +11:00
|
|
|
callback(Event::WindowEvent {
|
|
|
|
window_id,
|
|
|
|
event: CursorEntered { device_id },
|
|
|
|
});
|
2017-07-09 17:47:52 +10:00
|
|
|
|
2017-11-27 07:43:13 +11:00
|
|
|
let new_cursor_pos = (xev.event_x, xev.event_y);
|
2018-01-08 21:06:02 +11:00
|
|
|
// 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,
|
2017-12-27 08:46:28 +11:00
|
|
|
position: new_cursor_pos,
|
2018-01-08 21:06:02 +11:00
|
|
|
modifiers,
|
2017-11-27 07:43:13 +11:00
|
|
|
}})
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
ffi::XI_Leave => {
|
|
|
|
let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) };
|
2018-01-08 21:06:02 +11:00
|
|
|
|
|
|
|
callback(Event::WindowEvent {
|
|
|
|
window_id: mkwid(xev.event),
|
|
|
|
event: CursorLeft { device_id: mkdid(xev.deviceid) },
|
|
|
|
});
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
ffi::XI_FocusIn => {
|
|
|
|
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
|
2017-12-27 08:46:28 +11:00
|
|
|
|
2018-01-08 21:06:02 +11:00
|
|
|
let window_id = mkwid(xev.event);
|
2017-12-27 08:46:28 +11:00
|
|
|
|
2017-07-02 02:24:35 +10:00
|
|
|
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);
|
|
|
|
}
|
2018-01-08 21:06:02 +11:00
|
|
|
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),
|
|
|
|
}
|
|
|
|
});
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
ffi::XI_FocusOut => {
|
|
|
|
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
|
2017-07-02 02:24:35 +10:00
|
|
|
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);
|
|
|
|
}
|
2018-01-08 21:06:02 +11:00
|
|
|
callback(Event::WindowEvent {
|
|
|
|
window_id: mkwid(xev.event),
|
|
|
|
event: Focused(false),
|
|
|
|
})
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => {
|
|
|
|
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
2018-01-08 21:06:02 +11:00
|
|
|
let window_id = mkwid(xev.event);
|
2017-04-23 06:52:35 +10:00
|
|
|
let phase = match xev.evtype {
|
|
|
|
ffi::XI_TouchBegin => TouchPhase::Started,
|
|
|
|
ffi::XI_TouchUpdate => TouchPhase::Moved,
|
|
|
|
ffi::XI_TouchEnd => TouchPhase::Ended,
|
|
|
|
_ => unreachable!()
|
|
|
|
};
|
2018-01-08 21:06:02 +11:00
|
|
|
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,
|
|
|
|
},
|
|
|
|
)})
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
2017-04-23 06:52:35 +10:00
|
|
|
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) };
|
2017-11-13 07:56:57 +11:00
|
|
|
let mut value = xev.raw_values;
|
|
|
|
let mut mouse_delta = (0.0, 0.0);
|
|
|
|
let mut scroll_delta = (0.0, 0.0);
|
2017-04-23 06:52:35 +10:00
|
|
|
for i in 0..xev.valuators.mask_len*8 {
|
|
|
|
if ffi::XIMaskIsSet(mask, i) {
|
2017-11-13 07:56:57 +11:00
|
|
|
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,
|
|
|
|
_ => {},
|
|
|
|
}
|
2017-04-23 06:52:35 +10:00
|
|
|
callback(Event::DeviceEvent { device_id: did, event: DeviceEvent::Motion {
|
2017-07-01 19:20:13 +10:00
|
|
|
axis: i as u32,
|
2017-11-13 07:56:57 +11:00
|
|
|
value: x,
|
2017-04-23 06:52:35 +10:00
|
|
|
}});
|
|
|
|
value = unsafe { value.offset(1) };
|
|
|
|
}
|
|
|
|
}
|
2017-11-13 07:56:57 +11:00
|
|
|
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),
|
|
|
|
}});
|
|
|
|
}
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
2017-09-25 15:25:36 +10:00
|
|
|
scancode: (xev.detail - 8) as u32,
|
2017-04-23 06:52:35 +10:00
|
|
|
virtual_keycode: events::keysym_to_element(xkeysym as libc::c_uint),
|
|
|
|
state: match xev.evtype {
|
|
|
|
ffi::XI_RawKeyPress => Pressed,
|
|
|
|
ffi::XI_RawKeyRelease => Released,
|
|
|
|
_ => unreachable!(),
|
|
|
|
},
|
2018-01-08 21:06:02 +11:00
|
|
|
modifiers: ModifiersState::default(),
|
2017-04-23 06:52:35 +10:00
|
|
|
})});
|
|
|
|
}
|
|
|
|
|
|
|
|
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));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-25 23:19:13 +10:00
|
|
|
impl EventsLoopProxy {
|
|
|
|
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
|
|
|
|
// Update the `EventsLoop`'s `pending_wakeup` flag.
|
2017-05-31 18:07:51 +10:00
|
|
|
let display = match (self.pending_wakeup.upgrade(), self.display.upgrade()) {
|
|
|
|
(Some(wakeup), Some(display)) => {
|
|
|
|
wakeup.store(true, atomic::Ordering::Relaxed);
|
|
|
|
display
|
|
|
|
},
|
|
|
|
_ => return Err(EventsLoopClosed),
|
|
|
|
};
|
|
|
|
|
2017-06-17 22:59:56 +10:00
|
|
|
// 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?
|
2017-05-31 18:07:51 +10:00
|
|
|
let mut xev = ffi::XClientMessageEvent {
|
|
|
|
type_: ffi::ClientMessage,
|
2017-06-17 22:59:56 +10:00
|
|
|
window: self.wakeup_dummy_window,
|
2017-05-31 18:07:51 +10:00
|
|
|
format: 32,
|
|
|
|
message_type: 0,
|
|
|
|
serial: 0,
|
|
|
|
send_event: 0,
|
|
|
|
display: display.display,
|
|
|
|
data: unsafe { mem::zeroed() },
|
|
|
|
};
|
|
|
|
|
|
|
|
unsafe {
|
2017-06-17 22:59:56 +10:00
|
|
|
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);
|
2017-05-31 18:07:51 +10:00
|
|
|
(display.xlib.XFlush)(display.display);
|
|
|
|
display.check_errors().expect("Failed to call XSendEvent after wakeup");
|
2017-05-25 23:19:13 +10:00
|
|
|
}
|
|
|
|
|
2017-05-31 18:07:51 +10:00
|
|
|
Ok(())
|
2017-05-25 23:19:13 +10:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-23 06:52:35 +10:00
|
|
|
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) }
|
|
|
|
}
|
2017-03-04 07:41:51 +11:00
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
2017-04-23 06:52:35 +10:00
|
|
|
pub struct WindowId(ffi::Window);
|
|
|
|
|
|
|
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
|
|
pub struct DeviceId(c_int);
|
2017-03-04 07:41:51 +11:00
|
|
|
|
2017-09-07 01:32:24 +10:00
|
|
|
pub struct Window {
|
|
|
|
pub window: Arc<Window2>,
|
2017-06-02 21:19:45 +10:00
|
|
|
display: Weak<XConnection>,
|
|
|
|
windows: Weak<Mutex<HashMap<WindowId, WindowData>>>,
|
2017-03-04 07:41:51 +11:00
|
|
|
}
|
|
|
|
|
2017-09-07 01:32:24 +10:00
|
|
|
impl ::std::ops::Deref for Window {
|
|
|
|
type Target = Window2;
|
2017-03-04 07:41:51 +11:00
|
|
|
#[inline]
|
2017-09-07 01:32:24 +10:00
|
|
|
fn deref(&self) -> &Window2 {
|
2017-03-04 07:41:51 +11:00
|
|
|
&*self.window
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-04-23 06:52:35 +10:00
|
|
|
// 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(());
|
|
|
|
}
|
|
|
|
|
2017-09-07 01:32:24 +10:00
|
|
|
impl Window {
|
2017-09-01 19:04:57 +10:00
|
|
|
pub fn new(x_events_loop: &EventsLoop,
|
2017-06-02 21:19:45 +10:00
|
|
|
window: &::WindowAttributes,
|
|
|
|
pl_attribs: &PlatformSpecificWindowBuilderAttributes)
|
|
|
|
-> Result<Self, CreationError>
|
2017-03-04 07:41:51 +11:00
|
|
|
{
|
2017-09-07 01:32:24 +10:00
|
|
|
let win = ::std::sync::Arc::new(try!(Window2::new(&x_events_loop, window, pl_attribs)));
|
2017-04-23 06:52:35 +10:00
|
|
|
|
|
|
|
// 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
|
|
|
|
};
|
2017-11-13 07:56:57 +11:00
|
|
|
|
2017-04-23 06:52:35 +10:00
|
|
|
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},
|
2017-04-23 06:52:35 +10:00
|
|
|
config: None,
|
|
|
|
multitouch: window.multitouch,
|
|
|
|
cursor_pos: None,
|
|
|
|
});
|
|
|
|
|
2017-09-07 01:32:24 +10:00
|
|
|
Ok(Window {
|
2017-03-04 07:41:51 +11:00
|
|
|
window: win,
|
2017-06-02 21:19:45 +10:00
|
|
|
windows: Arc::downgrade(&x_events_loop.windows),
|
|
|
|
display: Arc::downgrade(&x_events_loop.display),
|
2017-03-04 07:41:51 +11:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
#[inline]
|
|
|
|
pub fn id(&self) -> WindowId {
|
2017-04-23 06:52:35 +10:00
|
|
|
self.window.id()
|
2017-03-04 07:41:51 +11:00
|
|
|
}
|
2017-07-12 14:00:51 +10:00
|
|
|
|
|
|
|
#[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();
|
2017-11-13 07:56:57 +11:00
|
|
|
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 {
|
2017-07-12 14:00:51 +10:00
|
|
|
return
|
|
|
|
}
|
2017-07-13 03:26:11 +10:00
|
|
|
w.ic_spot = nspot;
|
2017-07-12 14:00:51 +10:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-03-04 07:41:51 +11:00
|
|
|
}
|
|
|
|
|
2017-09-07 01:32:24 +10:00
|
|
|
impl Drop for Window {
|
2017-03-04 07:41:51 +11:00
|
|
|
fn drop(&mut self) {
|
2017-06-02 21:19:45 +10:00
|
|
|
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);
|
2017-03-04 07:41:51 +11:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-04-23 06:52:35 +10:00
|
|
|
|
|
|
|
/// 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,
|
2017-04-23 06:52:35 +10:00
|
|
|
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-04-23 06:52:35 +10:00
|
|
|
|
2017-07-09 17:47:52 +10:00
|
|
|
if Device::physical_device(info) {
|
2017-04-23 06:52:35 +10:00
|
|
|
// 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) {
|
2017-04-23 06:52:35 +10:00
|
|
|
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) {
|
2017-04-23 06:52:35 +10:00
|
|
|
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) {
|
2017-04-23 06:52:35 +10:00
|
|
|
axis.position = info.value;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
_ => {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-07-09 17:47:52 +10:00
|
|
|
}
|
2017-04-23 06:52:35 +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) }
|
2017-04-23 06:52:35 +10:00
|
|
|
}
|
|
|
|
}
|