1299 lines
54 KiB
Rust
1299 lines
54 KiB
Rust
#![cfg(any(target_os = "linux", target_os = "dragonfly", target_os = "freebsd", target_os = "openbsd"))]
|
|
|
|
pub mod ffi;
|
|
mod events;
|
|
mod monitor;
|
|
mod window;
|
|
mod xdisplay;
|
|
mod dnd;
|
|
mod ime;
|
|
pub mod util;
|
|
|
|
pub use self::monitor::{
|
|
MonitorId,
|
|
get_available_monitors,
|
|
get_monitor_for_window,
|
|
get_primary_monitor,
|
|
invalidate_cached_monitor_list,
|
|
};
|
|
pub use self::window::UnownedWindow;
|
|
pub use self::xdisplay::{XConnection, XNotSupported, XError};
|
|
|
|
use std::{mem, ptr, slice};
|
|
use std::cell::RefCell;
|
|
use std::collections::HashMap;
|
|
use std::ffi::CStr;
|
|
use std::ops::Deref;
|
|
use std::os::raw::*;
|
|
use std::sync::{Arc, mpsc, Weak};
|
|
use std::sync::atomic::{self, AtomicBool};
|
|
|
|
use libc::{self, setlocale, LC_CTYPE};
|
|
use parking_lot::Mutex;
|
|
|
|
use {
|
|
ControlFlow,
|
|
CreationError,
|
|
DeviceEvent,
|
|
Event,
|
|
EventsLoopClosed,
|
|
KeyboardInput,
|
|
WindowAttributes,
|
|
WindowEvent,
|
|
};
|
|
use events::ModifiersState;
|
|
use platform::PlatformSpecificWindowBuilderAttributes;
|
|
use self::dnd::{Dnd, DndState};
|
|
use self::ime::{ImeReceiver, ImeSender, ImeCreationError, Ime};
|
|
|
|
pub struct EventsLoop {
|
|
xconn: Arc<XConnection>,
|
|
wm_delete_window: ffi::Atom,
|
|
dnd: Dnd,
|
|
ime_receiver: ImeReceiver,
|
|
ime_sender: ImeSender,
|
|
ime: RefCell<Ime>,
|
|
randr_event_offset: c_int,
|
|
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
|
|
devices: RefCell<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,
|
|
}
|
|
|
|
#[derive(Clone)]
|
|
pub struct EventsLoopProxy {
|
|
pending_wakeup: Weak<AtomicBool>,
|
|
xconn: Weak<XConnection>,
|
|
wakeup_dummy_window: ffi::Window,
|
|
}
|
|
|
|
impl EventsLoop {
|
|
pub fn new(xconn: Arc<XConnection>) -> EventsLoop {
|
|
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
|
|
|
|
let wm_delete_window = unsafe { xconn.get_atom_unchecked(b"WM_DELETE_WINDOW\0") };
|
|
|
|
let dnd = Dnd::new(Arc::clone(&xconn))
|
|
.expect("Failed to call XInternAtoms when initializing drag and drop");
|
|
|
|
let (ime_sender, ime_receiver) = mpsc::channel();
|
|
// Input methods will open successfully without setting the locale, but it won't be
|
|
// possible to actually commit pre-edit sequences.
|
|
unsafe { setlocale(LC_CTYPE, b"\0".as_ptr() as *const _); }
|
|
let ime = RefCell::new({
|
|
let result = Ime::new(Arc::clone(&xconn));
|
|
if let Err(ImeCreationError::OpenFailure(ref state)) = result {
|
|
panic!(format!("Failed to open input method: {:#?}", state));
|
|
}
|
|
result.expect("Failed to set input method destruction callback")
|
|
});
|
|
|
|
let randr_event_offset = monitor::select_input(&xconn, root)
|
|
.expect("Failed to query XRandR extension");
|
|
|
|
let xi2ext = unsafe {
|
|
let mut result = XExtension {
|
|
opcode: mem::uninitialized(),
|
|
first_event_id: mem::uninitialized(),
|
|
first_error_id: mem::uninitialized(),
|
|
};
|
|
let res = (xconn.xlib.XQueryExtension)(
|
|
xconn.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 (xconn.xinput2.XIQueryVersion)(
|
|
xconn.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,
|
|
);
|
|
}
|
|
}
|
|
|
|
xconn.update_cached_wm_info(root);
|
|
|
|
let wakeup_dummy_window = unsafe {
|
|
let (x, y, w, h) = (10, 10, 10, 10);
|
|
let (border_w, border_px, background_px) = (0, 0, 0);
|
|
(xconn.xlib.XCreateSimpleWindow)(
|
|
xconn.display,
|
|
root,
|
|
x,
|
|
y,
|
|
w,
|
|
h,
|
|
border_w,
|
|
border_px,
|
|
background_px,
|
|
)
|
|
};
|
|
|
|
let result = EventsLoop {
|
|
xconn,
|
|
wm_delete_window,
|
|
dnd,
|
|
ime_receiver,
|
|
ime_sender,
|
|
ime,
|
|
randr_event_offset,
|
|
windows: Default::default(),
|
|
devices: Default::default(),
|
|
xi2ext,
|
|
pending_wakeup: Default::default(),
|
|
root,
|
|
wakeup_dummy_window,
|
|
};
|
|
|
|
// Register for device hotplug events
|
|
// (The request buffer is flushed during `init_device`)
|
|
result.xconn.select_xinput_events(
|
|
root,
|
|
ffi::XIAllDevices,
|
|
ffi::XI_HierarchyChangedMask,
|
|
).queue();
|
|
|
|
result.init_device(ffi::XIAllDevices);
|
|
|
|
result
|
|
}
|
|
|
|
/// Returns the `XConnection` of this events loop.
|
|
#[inline]
|
|
pub fn x_connection(&self) -> &Arc<XConnection> {
|
|
&self.xconn
|
|
}
|
|
|
|
pub fn create_proxy(&self) -> EventsLoopProxy {
|
|
EventsLoopProxy {
|
|
pending_wakeup: Arc::downgrade(&self.pending_wakeup),
|
|
xconn: Arc::downgrade(&self.xconn),
|
|
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.xconn.xlib.XPending)(self.xconn.display);
|
|
if count == 0 {
|
|
break;
|
|
}
|
|
|
|
(self.xconn.xlib.XNextEvent)(self.xconn.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
|
|
{
|
|
let mut xev = unsafe { mem::uninitialized() };
|
|
|
|
loop {
|
|
unsafe { (self.xconn.xlib.XNextEvent)(self.xconn.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)
|
|
{
|
|
// XFilterEvent tells us when an event has been discarded by the input method.
|
|
// Specifically, this involves all of the KeyPress events in compose/pre-edit sequences,
|
|
// along with an extra copy of the KeyRelease events. This also prevents backspace and
|
|
// arrow keys from being detected twice.
|
|
if ffi::True == unsafe { (self.xconn.xlib.XFilterEvent)(
|
|
xev,
|
|
{ let xev: &ffi::XAnyEvent = xev.as_ref(); xev.window }
|
|
) } {
|
|
return;
|
|
}
|
|
|
|
let event_type = xev.get_type();
|
|
match event_type {
|
|
ffi::MappingNotify => {
|
|
unsafe { (self.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); }
|
|
self.xconn.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::CloseRequested });
|
|
} 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 xwindow = xev.window;
|
|
let events = self.with_window(xwindow, |window| {
|
|
// So apparently...
|
|
// `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root
|
|
// `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent
|
|
// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.5
|
|
// We don't want to send `Moved` when this is false, since then every `Resized`
|
|
// (whether the window moved or not) is accompanied by an extraneous `Moved` event
|
|
// that has a position relative to the parent window.
|
|
let is_synthetic = xev.send_event == ffi::True;
|
|
|
|
let new_inner_size = (xev.width as u32, xev.height as u32);
|
|
let new_inner_position = (xev.x as i32, xev.y as i32);
|
|
|
|
let mut shared_state_lock = window.shared_state.lock();
|
|
|
|
let (resized, moved) = {
|
|
let resized = util::maybe_change(&mut shared_state_lock.size, new_inner_size);
|
|
let moved = if is_synthetic {
|
|
util::maybe_change(&mut shared_state_lock.inner_position, new_inner_position)
|
|
} else {
|
|
// Detect when frame extents change.
|
|
// Since this isn't synthetic, as per the notes above, this position is relative to the
|
|
// parent window.
|
|
let rel_parent = new_inner_position;
|
|
if util::maybe_change(&mut shared_state_lock.inner_position_rel_parent, rel_parent) {
|
|
// This ensures we process the next `Moved`.
|
|
shared_state_lock.inner_position = None;
|
|
// Extra insurance against stale frame extents.
|
|
shared_state_lock.frame_extents = None;
|
|
}
|
|
false
|
|
};
|
|
(resized, moved)
|
|
};
|
|
|
|
let capacity = resized as usize + moved as usize;
|
|
let mut events = Vec::with_capacity(capacity);
|
|
|
|
if resized {
|
|
events.push(WindowEvent::Resized(new_inner_size.0, new_inner_size.1));
|
|
}
|
|
|
|
if moved || shared_state_lock.position.is_none() {
|
|
// We need to convert client area position to window position.
|
|
let frame_extents = shared_state_lock.frame_extents
|
|
.as_ref()
|
|
.cloned()
|
|
.unwrap_or_else(|| {
|
|
let frame_extents = self.xconn.get_frame_extents_heuristic(xwindow, self.root);
|
|
shared_state_lock.frame_extents = Some(frame_extents.clone());
|
|
frame_extents
|
|
});
|
|
let outer = frame_extents.inner_pos_to_outer(new_inner_position.0, new_inner_position.1);
|
|
shared_state_lock.position = Some(outer);
|
|
if moved {
|
|
events.push(WindowEvent::Moved(outer.0, outer.1));
|
|
}
|
|
}
|
|
|
|
events
|
|
});
|
|
|
|
if let Some(events) = events {
|
|
for event in events {
|
|
callback(Event::WindowEvent {
|
|
window_id: mkwid(xwindow),
|
|
event,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
ffi::ReparentNotify => {
|
|
let xev: &ffi::XReparentEvent = xev.as_ref();
|
|
|
|
// This is generally a reliable way to detect when the window manager's been
|
|
// replaced, though this event is only fired by reparenting window managers
|
|
// (which is almost all of them). Failing to correctly update WM info doesn't
|
|
// really have much impact, since on the WMs affected (xmonad, dwm, etc.) the only
|
|
// effect is that we waste some time trying to query unsupported properties.
|
|
self.xconn.update_cached_wm_info(self.root);
|
|
|
|
self.with_window(xev.window, |window| {
|
|
window.invalidate_cached_frame_extents();
|
|
});
|
|
}
|
|
|
|
ffi::DestroyNotify => {
|
|
let xev: &ffi::XDestroyWindowEvent = xev.as_ref();
|
|
|
|
let window = xev.window;
|
|
let window_id = mkwid(window);
|
|
|
|
// In the event that the window's been destroyed without being dropped first, we
|
|
// cleanup again here.
|
|
self.windows.borrow_mut().remove(&WindowId(window));
|
|
|
|
// Since all XIM stuff needs to happen from the same thread, we destroy the input
|
|
// context here instead of when dropping the window.
|
|
self.ime
|
|
.borrow_mut()
|
|
.remove_context(window)
|
|
.expect("Failed to destroy input context");
|
|
|
|
callback(Event::WindowEvent { window_id, event: WindowEvent::Destroyed });
|
|
}
|
|
|
|
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 });
|
|
}
|
|
|
|
ffi::KeyPress | ffi::KeyRelease => {
|
|
use events::ElementState::{Pressed, Released};
|
|
|
|
// Note that in compose/pre-edit sequences, this will always be Released.
|
|
let state = if xev.get_type() == ffi::KeyPress {
|
|
Pressed
|
|
} else {
|
|
Released
|
|
};
|
|
|
|
let xkev: &mut ffi::XKeyEvent = xev.as_mut();
|
|
|
|
let window = xkev.window;
|
|
let window_id = mkwid(window);
|
|
|
|
// Standard virtual core keyboard ID. XInput2 needs to be used to get a reliable
|
|
// value, though this should only be an issue under multiseat configurations.
|
|
let device = 3;
|
|
let device_id = mkdid(device);
|
|
|
|
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
|
|
// a keycode of 0.
|
|
if xkev.keycode != 0 {
|
|
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,
|
|
};
|
|
|
|
let keysym = unsafe {
|
|
let mut keysym = 0;
|
|
(self.xconn.xlib.XLookupString)(
|
|
xkev,
|
|
ptr::null_mut(),
|
|
0,
|
|
&mut keysym,
|
|
ptr::null_mut(),
|
|
);
|
|
self.xconn.check_errors().expect("Failed to lookup keysym");
|
|
keysym
|
|
};
|
|
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
|
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: WindowEvent::KeyboardInput {
|
|
device_id,
|
|
input: KeyboardInput {
|
|
state,
|
|
scancode: xkev.keycode - 8,
|
|
virtual_keycode,
|
|
modifiers,
|
|
},
|
|
}
|
|
});
|
|
}
|
|
|
|
if state == Pressed {
|
|
let written = if let Some(ic) = self.ime.borrow().get_context(window) {
|
|
self.xconn.lookup_utf8(ic, xkev)
|
|
} else {
|
|
return;
|
|
};
|
|
|
|
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.xconn, *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 {
|
|
// Deliver multi-touch events instead of emulated mouse events.
|
|
let return_now = self
|
|
.with_window(xev.event, |window| window.multitouch)
|
|
.unwrap_or(true);
|
|
if return_now { 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);
|
|
|
|
let cursor_moved = self.with_window(xev.event, |window| {
|
|
let mut shared_state_lock = window.shared_state.lock();
|
|
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
|
|
});
|
|
if cursor_moved == Some(true) {
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: CursorMoved {
|
|
device_id,
|
|
position: new_cursor_pos,
|
|
modifiers,
|
|
},
|
|
});
|
|
} else if cursor_moved.is_none() {
|
|
return;
|
|
}
|
|
|
|
// 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.borrow_mut();
|
|
let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) {
|
|
Some(device) => device,
|
|
None => return,
|
|
};
|
|
|
|
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 _) };
|
|
|
|
let window_id = mkwid(xev.event);
|
|
let device_id = mkdid(xev.deviceid);
|
|
|
|
if let Some(all_info) = DeviceInfo::get(&self.xconn, ffi::XIAllDevices) {
|
|
let mut devices = self.devices.borrow_mut();
|
|
for device_info in all_info.iter() {
|
|
if device_info.deviceid == xev.sourceid
|
|
// This is needed for resetting to work correctly on i3, and
|
|
// presumably some other WMs. On those, `XI_Enter` doesn't include
|
|
// the physical device ID, so both `sourceid` and `deviceid` are
|
|
// the virtual device.
|
|
|| device_info.attachment == xev.sourceid {
|
|
let device_id = DeviceId(device_info.deviceid);
|
|
if let Some(device) = devices.get_mut(&device_id) {
|
|
device.reset_scroll_position(device_info);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: CursorEntered { device_id },
|
|
});
|
|
|
|
let new_cursor_pos = (xev.event_x, xev.event_y);
|
|
|
|
// The mods field on this event isn't actually populated, so query the
|
|
// pointer device. In the future, we can likely remove this round-trip by
|
|
// relying on Xkb for modifier values.
|
|
let modifiers = self.xconn.query_pointer(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 _) };
|
|
|
|
// Leave, FocusIn, and FocusOut can be received by a window that's already
|
|
// been destroyed, which the user presumably doesn't want to deal with.
|
|
let window_closed = !self.window_exists(xev.event);
|
|
if !window_closed {
|
|
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 _) };
|
|
|
|
if !self.window_exists(xev.event) { return; }
|
|
let window_id = mkwid(xev.event);
|
|
|
|
self.ime
|
|
.borrow_mut()
|
|
.focus(xev.event)
|
|
.expect("Failed to focus input context");
|
|
|
|
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 pointer_id = self.devices
|
|
.borrow()
|
|
.get(&DeviceId(xev.deviceid))
|
|
.map(|device| device.attachment)
|
|
.unwrap_or(2);
|
|
|
|
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 _) };
|
|
if !self.window_exists(xev.event) { return; }
|
|
self.ime
|
|
.borrow_mut()
|
|
.unfocus(xev.event)
|
|
.expect("Failed to unfocus input context");
|
|
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 {
|
|
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 {
|
|
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 => {
|
|
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
|
|
|
|
let state = match xev.evtype {
|
|
ffi::XI_RawKeyPress => Pressed,
|
|
ffi::XI_RawKeyRelease => Released,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let device_id = xev.sourceid;
|
|
let keycode = xev.detail;
|
|
if keycode < 8 { return; }
|
|
let scancode = (keycode - 8) as u32;
|
|
|
|
let keysym = unsafe {
|
|
(self.xconn.xlib.XKeycodeToKeysym)(
|
|
self.xconn.display,
|
|
xev.detail as ffi::KeyCode,
|
|
0,
|
|
)
|
|
};
|
|
self.xconn.check_errors().expect("Failed to lookup raw keysym");
|
|
|
|
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
|
|
|
callback(Event::DeviceEvent {
|
|
device_id: mkdid(device_id),
|
|
event: DeviceEvent::Key(KeyboardInput {
|
|
scancode,
|
|
virtual_keycode,
|
|
state,
|
|
// So, in an ideal world we can use libxkbcommon to get modifiers.
|
|
// However, libxkbcommon-x11 isn't as commonly installed as one
|
|
// would hope. We can still use the Xkb extension to get
|
|
// comprehensive keyboard state updates, but interpreting that
|
|
// info manually is going to be involved.
|
|
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.borrow_mut();
|
|
devices.remove(&DeviceId(info.deviceid));
|
|
}
|
|
}
|
|
}
|
|
|
|
_ => {}
|
|
}
|
|
},
|
|
_ => {
|
|
if event_type == self.randr_event_offset {
|
|
// In the future, it would be quite easy to emit monitor hotplug events.
|
|
monitor::invalidate_cached_monitor_list();
|
|
}
|
|
},
|
|
}
|
|
|
|
match self.ime_receiver.try_recv() {
|
|
Ok((window_id, x, y)) => {
|
|
self.ime.borrow_mut().send_xim_spot(window_id, x, y);
|
|
},
|
|
Err(_) => (),
|
|
}
|
|
}
|
|
|
|
fn init_device(&self, device: c_int) {
|
|
let mut devices = self.devices.borrow_mut();
|
|
if let Some(info) = DeviceInfo::get(&self.xconn, device) {
|
|
for info in info.iter() {
|
|
devices.insert(DeviceId(info.deviceid), Device::new(&self, info));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn with_window<F, T>(&self, window_id: ffi::Window, callback: F) -> Option<T>
|
|
where F: Fn(&UnownedWindow) -> T
|
|
{
|
|
let mut deleted = false;
|
|
let window_id = WindowId(window_id);
|
|
let result = self.windows
|
|
.borrow()
|
|
.get(&window_id)
|
|
.and_then(|window| {
|
|
let arc = window.upgrade();
|
|
deleted = arc.is_none();
|
|
arc
|
|
})
|
|
.map(|window| callback(&*window));
|
|
if deleted {
|
|
// Garbage collection
|
|
self.windows.borrow_mut().remove(&window_id);
|
|
}
|
|
result
|
|
}
|
|
|
|
fn window_exists(&self, window_id: ffi::Window) -> bool {
|
|
self.with_window(window_id, |_| ()).is_some()
|
|
}
|
|
}
|
|
|
|
impl EventsLoopProxy {
|
|
pub fn wakeup(&self) -> Result<(), EventsLoopClosed> {
|
|
// Update the `EventsLoop`'s `pending_wakeup` flag.
|
|
let display = match (self.pending_wakeup.upgrade(), self.xconn.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 design is taken from the old `WindowProxy::wakeup` implementation. It
|
|
// assumes that X11 is thread safe. Is this true?
|
|
// (WARNING: it's probably not true)
|
|
display.send_client_msg(
|
|
self.wakeup_dummy_window,
|
|
self.wakeup_dummy_window,
|
|
0,
|
|
None,
|
|
[0, 0, 0, 0, 0],
|
|
).flush().expect("Failed to call XSendEvent after wakeup");
|
|
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
struct DeviceInfo<'a> {
|
|
xconn: &'a XConnection,
|
|
info: *const ffi::XIDeviceInfo,
|
|
count: usize,
|
|
}
|
|
|
|
impl<'a> DeviceInfo<'a> {
|
|
fn get(xconn: &'a XConnection, device: c_int) -> Option<Self> {
|
|
unsafe {
|
|
let mut count = mem::uninitialized();
|
|
let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count);
|
|
xconn.check_errors()
|
|
.ok()
|
|
.and_then(|_| {
|
|
if info.is_null() || count == 0 {
|
|
None
|
|
} else {
|
|
Some(DeviceInfo {
|
|
xconn,
|
|
info,
|
|
count: count as usize,
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Drop for DeviceInfo<'a> {
|
|
fn drop(&mut self) {
|
|
assert!(!self.info.is_null());
|
|
unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
|
|
}
|
|
}
|
|
|
|
impl<'a> 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<UnownedWindow>,
|
|
ime_sender: Mutex<ImeSender>,
|
|
}
|
|
|
|
impl Deref for Window {
|
|
type Target = UnownedWindow;
|
|
#[inline]
|
|
fn deref(&self) -> &UnownedWindow {
|
|
&*self.window
|
|
}
|
|
}
|
|
|
|
impl Window {
|
|
pub fn new(
|
|
event_loop: &EventsLoop,
|
|
attribs: WindowAttributes,
|
|
pl_attribs: PlatformSpecificWindowBuilderAttributes
|
|
) -> Result<Self, CreationError> {
|
|
let window = Arc::new(UnownedWindow::new(&event_loop, attribs, pl_attribs)?);
|
|
|
|
event_loop.windows
|
|
.borrow_mut()
|
|
.insert(window.id(), Arc::downgrade(&window));
|
|
|
|
event_loop.ime
|
|
.borrow_mut()
|
|
.create_context(window.id().0)
|
|
.expect("Failed to create input context");
|
|
|
|
Ok(Window {
|
|
window,
|
|
ime_sender: Mutex::new(event_loop.ime_sender.clone()),
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
pub fn send_xim_spot(&self, x: i16, y: i16) {
|
|
let _ = self.ime_sender
|
|
.lock()
|
|
.send((self.window.id().0, x, y));
|
|
}
|
|
}
|
|
|
|
impl Drop for Window {
|
|
fn drop(&mut self) {
|
|
let xconn = &self.window.xconn;
|
|
unsafe {
|
|
(xconn.xlib.XDestroyWindow)(xconn.display, self.window.id().0);
|
|
// If the window was somehow already destroyed, we'll get a `BadWindow` error, which we don't care about.
|
|
let _ = xconn.check_errors();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 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> {
|
|
xconn: &'a XConnection,
|
|
cookie: ffi::XGenericEventCookie
|
|
}
|
|
|
|
impl<'a> GenericEventCookie<'a> {
|
|
fn from_event<'b>(xconn: &'b XConnection, event: ffi::XEvent) -> Option<GenericEventCookie<'b>> {
|
|
unsafe {
|
|
let mut cookie: ffi::XGenericEventCookie = From::from(event);
|
|
if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True {
|
|
Some(GenericEventCookie { xconn, cookie })
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a> Drop for GenericEventCookie<'a> {
|
|
fn drop(&mut self) {
|
|
unsafe {
|
|
(self.xconn.xlib.XFreeEventData)(self.xconn.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)>,
|
|
// For master devices, this is the paired device (pointer <-> keyboard).
|
|
// For slave devices, this is the master.
|
|
attachment: c_int,
|
|
}
|
|
|
|
#[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() };
|
|
let mut scroll_axes = Vec::new();
|
|
|
|
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;
|
|
// The request buffer is flushed when we poll for events
|
|
el.xconn.select_xinput_events(el.root, info.deviceid, mask).queue();
|
|
|
|
// Identify scroll axes
|
|
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,
|
|
}));
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut device = Device {
|
|
name: name.into_owned(),
|
|
scroll_axes: scroll_axes,
|
|
attachment: info.attachment,
|
|
};
|
|
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) };
|
|
if let Some(&mut (_, ref mut axis)) = self.scroll_axes.iter_mut().find(|&&mut (axis, _)| axis == info.number) {
|
|
axis.position = info.value;
|
|
}
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[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) }
|
|
}
|
|
}
|