X11: General cleanup (#491)

* X11: General cleanup

This is almost entirely internal changes, and as usual, doesn't actually
fix any problems people have complained about.

- `XSetInputFocus` can't be called before the window is visible. This
was previously handled by looping (with a sleep) and querying for the
window's state until it was visible. Now we use `XIfEvent`, which blocks
until we receive `VisibilityNotify`. Note that this can't be replaced
with an `XSync` (I tried).
- We now call `XSync` at the end of window creation and check for
errors, assuring that broken windows are never returned. When creating
invisible windows, this is the only time the output buffer is flushed
during the entire window creation process (AFAIK). For visible windows,
`XIfEvent` will generally flush, but window creation has overall been
reduced to the minimum number of flushes.
- `check_errors().expect()` has been a common pattern throughout the
backend, but it seems that people (myself included) didn't make a
distinction between using it after synchronous requests and asynchronous
requests. Now we only use it after async requests if we flush first,
though this still isn't correct (since the request likely hasn't been
processed yet). The only real solution (besides forcing a sync *every
time*) is to handle asynchronous errors *asynchronously*. For future
work, I plan on adding logging, though I don't plan on actually
*handling* those errors; that's more of something to hope for in the
hypothetical async/await XCB paradise.
- We now flush whenever it makes sense to. `util::Flusher` was added to
force contributors to be aware of the output buffer.
- `Window::get_position`, `Window::get_inner_position`,
`Window::get_inner_size`, and `Window::get_outer_size` previously all
required *several* round-trips. On my machine, it took an average of
around 80µs. They've now been reduced to one round-trip each, which
reduces my measurement to 16µs. This was accomplished simply by caching
the frame extents, which are expensive to calculate (due to various
queries and heuristics), but change infrequently and predictably. I
still recommend that application developers use these methods sparingly
and generally prefer storing the values from `Resized`/`Moved`, as
that's zero overhead.
- The above change enabled me to change the `Moved` event to supply
window positions, rather than client area positions. Additionally, we no
longer generate `Moved` for real (as in, not synthetic)
`ConfigureNotify` events. Real `ConfigureNotify` events contain
positions relative to the parent window, which are typically constant
and useless. Since that position would be completely different from the
root-relative positions supplied by synthetic `ConfigureNotify` events
(which are the vast majority of them), that meant real `ConfigureNotify`
events would *always* be detected as the position having changed, so the
resultant `Moved` was multiple levels of misleading. In practice, this
meant a garbage `Moved` would be sent every time the window was resized;
now a resize has to actually change the window's position to be
accompanied by `Moved`.
- Every time we processed an `XI_Enter` event, we would leak 4 bytes via
`util::query_pointer` (`XIQueryPointer`). `XIButtonState` contains a
dynamically-allocated mask field which we weren't freeing. As this event
occurs with fairly high frequency, long-running applications could
easily accumulate substantial leaks. `util::PointerState::drop` now
takes care of this.
- The `util` module has been split up into several sub-modules, as it
was getting rather lengthy. This accounts for a significant part of this
diff, unfortunately.
- Atoms are now cached. Xlib caches them too, so `XInternAtom` wouldn't
typically be a round-trip anyway, but the added complexity is
negligible.
- Switched from `std::sync::Mutex` to `parking_lot::Mutex` (within this
backend). There appears to be no downside to this, but if anyone finds
one, this would be easy to revert.
- The WM name and supported hints are now global to the application, and
are updated upon `ReparentNotify`, which should detect when the WM was
replaced (assuming a reparenting WM was involved, that is). Previously,
these values were per-window and would never update, meaning replacing
the WM could potentially lead to (admittedly very minor) problems.
- The result of `Window2::create_empty_cursor` will now only be used if
it actually succeeds.
- `Window2::load_cursor` no longer re-allocates the cursor name.
- `util::lookup_utf8` previously allocated a 16-byte buffer on the heap.
Now it allocates a 1024-byte buffer on the stack, and falls back to
dynamic allocation if the buffer is too small. This base buffer size is
admittedly gratuitous, but less so if you're using IME.
- `with_c_str` was finally removed.
- Added `util::Format` enum to help prevent goofs when dealing with
format arguments.
- `util::get_property`, something I added way back in my first winit PR,
only calculated offsets correctly for `util::Format::Char`. This was
concealed by the accomodating buffer size, as it would be very rare for
the offset to be needed; however, testing with a buffer size of 1,
`util::Format::Long` would read from the same offset multiple times, and
`util::Format::Short` would miss data. This function now works correctly
for all formats, relying on the simple fact that the offset increases by
the buffer size on each iteration. We also account for the extra byte
that `XGetWindowProperty` allocates at the end of the buffer, and copy
data from the buffer instead of moving it and taking ownership of the
pointer.
- Drag and drop now reliably works in release mode. This is presumably
related to the `util::get_property` changes.
- `util::change_property` now exists, which should make it easier to add
features in the future.
- The `EventsLoop` device map is no longer in a mutex.
- `XConnection` now implements `Debug`.
- Valgrind no longer complains about anything related to winit (with
either the system allocator or jemalloc, though "not having valgrind
complain about jemalloc" isn't something to strive for).

* X11: Add better diagnostics when initialization fails

* X11: Handle XIQueryDevice failure

* X11: Use correct types in error handler
This commit is contained in:
Francesca Frangipane 2018-05-03 09:15:49 -04:00 committed by GitHub
parent fee874b5b7
commit c4b92ebd45
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1911 additions and 1340 deletions

View file

@ -12,15 +12,16 @@ use {CreationError, Event, EventsLoopClosed, WindowEvent, DeviceEvent,
use events::ModifiersState;
use std::{mem, ptr, slice};
use std::sync::{Arc, Mutex, Weak};
use std::sync::{Arc, Weak};
use std::sync::atomic::{self, AtomicBool};
use std::sync::mpsc;
use std::cell::RefCell;
use std::collections::HashMap;
use std::ffi::CStr;
use std::os::raw::{c_char, c_int, c_long, c_uchar, c_uint, c_ulong};
use std::os::raw::*;
use libc::{self, setlocale, LC_CTYPE};
use parking_lot::Mutex;
mod events;
mod monitor;
@ -33,13 +34,6 @@ mod util;
use self::dnd::{Dnd, DndState};
use self::ime::{ImeReceiver, ImeSender, ImeCreationError, Ime};
// 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,
@ -48,7 +42,9 @@ pub struct EventsLoop {
ime_sender: ImeSender,
ime: RefCell<Ime>,
windows: Arc<Mutex<HashMap<WindowId, WindowData>>>,
devices: Mutex<HashMap<DeviceId, Device>>,
// Please don't laugh at this type signature
shared_state: RefCell<HashMap<WindowId, Weak<Mutex<window::SharedState>>>>,
devices: RefCell<HashMap<DeviceId, Device>>,
xi2ext: XExtension,
pending_wakeup: Arc<AtomicBool>,
root: ffi::Window,
@ -66,8 +62,8 @@ pub struct EventsLoopProxy {
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 wm_delete_window = unsafe { util::get_atom(&display, b"WM_DELETE_WINDOW\0") }
.expect("Failed to call XInternAtom (WM_DELETE_WINDOW)");
let dnd = Dnd::new(Arc::clone(&display))
.expect("Failed to call XInternAtoms when initializing drag and drop");
@ -105,19 +101,36 @@ impl EventsLoop {
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);
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) };
util::update_cached_wm_info(&display, 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);
(display.xlib.XCreateSimpleWindow)(display.display, root, x, y, w, h,
border_w, border_px, background_px)
(display.xlib.XCreateSimpleWindow)(
display.display,
root,
x,
y,
w,
h,
border_w,
border_px,
background_px,
)
};
let result = EventsLoop {
@ -129,27 +142,24 @@ impl EventsLoop {
ime_sender,
ime,
windows: Arc::new(Mutex::new(HashMap::new())),
devices: Mutex::new(HashMap::new()),
shared_state: RefCell::new(HashMap::new()),
devices: RefCell::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);
}
// Register for device hotplug events
unsafe {
util::select_xinput_events(
&result.display,
root,
ffi::XIAllDevices,
ffi::XI_HierarchyChangedMask,
)
}.queue(); // The request buffer is flushed during init_device
result.init_device(ffi::XIAllDevices);
}
result.init_device(ffi::XIAllDevices);
result
}
@ -230,7 +240,8 @@ impl EventsLoop {
return;
}
match xev.get_type() {
let event_type = xev.get_type();
match event_type {
ffi::MappingNotify => {
unsafe { (xlib.XRefreshKeyboardMapping)(xev.as_mut()); }
self.display.check_errors().expect("Failed to call XRefreshKeyboardMapping");
@ -375,47 +386,129 @@ impl EventsLoop {
ffi::ConfigureNotify => {
let xev: &ffi::XConfigureEvent = xev.as_ref();
// 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 true, 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 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 mut windows = self.windows.lock();
if let Some(window_data) = windows.get_mut(&WindowId(window)) {
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 })
let (mut resized, mut moved) = (false, false);
if window_data.config.size.is_none() {
window_data.config.size = Some(new_size);
resized = true;
}
if window_data.config.size.is_none() && is_synthetic {
window_data.config.position = Some(new_position);
moved = true;
}
if !resized {
if window_data.config.size != Some(new_size) {
window_data.config.size = Some(new_size);
resized = true;
}
}
if !moved && is_synthetic {
if window_data.config.position != Some(new_position) {
window_data.config.position = Some(new_position);
moved = true;
}
}
if !is_synthetic
&& window_data.config.inner_position != Some(new_position) {
window_data.config.inner_position = Some(new_position);
// This way, we get sent Moved when the decorations are toggled.
window_data.config.position = None;
self.shared_state.borrow().get(&WindowId(window)).map(|window_state| {
if let Some(window_state) = window_state.upgrade() {
// Extra insurance against stale frame extents
(*window_state.lock()).frame_extents.take();
}
});
}
(resized, moved)
} else {
return;
}
};
if resized {
let (width, height) = (xev.width as u32, xev.height as u32);
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Resized(xev.width as u32, xev.height as u32),
event: WindowEvent::Resized(width, height),
});
}
if moved {
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Moved(xev.x as i32, xev.y as i32),
// We need to convert client area position to window position.
self.shared_state.borrow().get(&WindowId(window)).map(|window_state| {
if let Some(window_state) = window_state.upgrade() {
let (x, y) = {
let (inner_x, inner_y) = (xev.x as i32, xev.y as i32);
let mut window_state_lock = window_state.lock();
if (*window_state_lock).frame_extents.is_some() {
(*window_state_lock).frame_extents
.as_ref()
.unwrap()
.inner_pos_to_outer(inner_x, inner_y)
} else {
let extents = util::get_frame_extents_heuristic(
&self.display,
window,
self.root,
);
let outer_pos = extents.inner_pos_to_outer(inner_x, inner_y);
(*window_state_lock).frame_extents = Some(extents);
outer_pos
}
};
callback(Event::WindowEvent {
window_id,
event: WindowEvent::Moved(x, y),
});
}
});
}
}
ffi::ReparentNotify => {
let xev: &ffi::XReparentEvent = xev.as_ref();
let window = xev.window;
// 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.
util::update_cached_wm_info(&self.display, self.root);
self.shared_state
.borrow()
.get(&WindowId(window))
.map(|window_state| {
if let Some(window_state) = window_state.upgrade() {
(*window_state.lock()).frame_extents.take();
}
});
}
ffi::DestroyNotify => {
let xev: &ffi::XDestroyWindowEvent = xev.as_ref();
@ -424,7 +517,7 @@ impl EventsLoop {
// In the event that the window's been destroyed without being dropped first, we
// cleanup again here.
self.windows.lock().unwrap().remove(&WindowId(window));
self.windows.lock().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.
@ -460,42 +553,47 @@ impl EventsLoop {
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,
};
let keysym = unsafe {
let mut keysym = 0;
(self.display.xlib.XLookupString)(
xkev,
ptr::null_mut(),
0,
&mut keysym,
ptr::null_mut(),
);
keysym
};
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
// 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 {
callback(Event::WindowEvent { window_id, event: WindowEvent::KeyboardInput {
// 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.
device_id: mkdid(3),
input: KeyboardInput {
state,
scancode: xkev.keycode - 8,
virtual_keycode,
modifiers,
},
}});
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.display.xlib.XLookupString)(
xkev,
ptr::null_mut(),
0,
&mut keysym,
ptr::null_mut(),
);
self.display.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 {
@ -534,7 +632,7 @@ impl EventsLoop {
let window_id = mkwid(xev.event);
let device_id = mkdid(xev.deviceid);
if (xev.flags & ffi::XIPointerEmulated) != 0 {
let windows = self.windows.lock().unwrap();
let windows = self.windows.lock();
if let Some(window_data) = windows.get(&WindowId(xev.event)) {
if window_data.multitouch {
// Deliver multi-touch events instead of emulated mouse events.
@ -623,7 +721,7 @@ impl EventsLoop {
// Gymnastics to ensure self.windows isn't locked when we invoke callback
if {
let mut windows = self.windows.lock().unwrap();
let mut windows = self.windows.lock();
let window_data = {
if let Some(window_data) = windows.get_mut(&WindowId(xev.event)) {
window_data
@ -650,8 +748,11 @@ impl EventsLoop {
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 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 {
@ -698,11 +799,16 @@ impl EventsLoop {
let window_id = mkwid(xev.event);
let device_id = mkdid(xev.deviceid);
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);
let mut devices = self.devices.borrow_mut();
let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) {
Some(device) => device,
None => return,
};
if let Some(all_info) = DeviceInfo::get(&self.display, ffi::XIAllDevices) {
for device_info in all_info.iter() {
if device_info.deviceid == xev.sourceid {
physical_device.reset_scroll_position(device_info);
}
}
}
callback(Event::WindowEvent {
@ -711,15 +817,17 @@ impl EventsLoop {
});
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.
// 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 = unsafe {
util::query_pointer(
&self.display,
xev.event,
xev.deviceid,
).expect("Failed to query pointer device")
}.get_modifier_state();
)
}.expect("Failed to query pointer device").get_modifier_state();
callback(Event::WindowEvent { window_id, event: CursorMoved {
device_id,
@ -734,7 +842,6 @@ impl EventsLoop {
// been destroyed, which the user presumably doesn't want to deal with.
let window_closed = self.windows
.lock()
.unwrap()
.get(&WindowId(xev.event))
.is_none();
@ -750,7 +857,7 @@ impl EventsLoop {
let window_id = mkwid(xev.event);
if let None = self.windows.lock().unwrap().get(&WindowId(xev.event)) {
if let None = self.windows.lock().get(&WindowId(xev.event)) {
return;
}
self.ime
@ -762,11 +869,11 @@ impl EventsLoop {
// 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;
let pointer_id = self.devices
.borrow()
.get(&DeviceId(xev.deviceid))
.map(|device| device.attachment)
.unwrap_or(2);
callback(Event::WindowEvent {
window_id,
@ -780,7 +887,7 @@ impl EventsLoop {
ffi::XI_FocusOut => {
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
if let None = self.windows.lock().unwrap().get(&WindowId(xev.event)) {
if let None = self.windows.lock().get(&WindowId(xev.event)) {
return;
}
self.ime
@ -868,19 +975,44 @@ impl EventsLoop {
}
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(),
})});
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.display.xlib.XKeycodeToKeysym)(
self.display.display,
xev.detail as ffi::KeyCode,
0,
)
};
self.display.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 => {
@ -891,7 +1023,7 @@ impl EventsLoop {
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();
let mut devices = self.devices.borrow_mut();
devices.remove(&DeviceId(info.deviceid));
}
}
@ -899,23 +1031,24 @@ impl EventsLoop {
_ => {}
}
}
_ => {}
},
_ => (),
}
match self.ime_receiver.try_recv() {
Ok((window_id, x, y)) => {
self.ime.borrow_mut().send_xim_spot(window_id, x, y);
}
Err(_) => ()
},
Err(_) => (),
}
}
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));
let mut devices = self.devices.borrow_mut();
if let Some(info) = DeviceInfo::get(&self.display, device) {
for info in info.iter() {
devices.insert(DeviceId(info.deviceid), Device::new(&self, info));
}
}
}
}
@ -933,28 +1066,19 @@ impl EventsLoopProxy {
// 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() },
};
// 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)
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");
}
util::send_client_msg(
&display,
self.wakeup_dummy_window,
self.wakeup_dummy_window,
0,
None,
(0, 0, 0, 0, 0),
)
}.flush().expect("Failed to call XSendEvent after wakeup");
Ok(())
}
@ -967,21 +1091,30 @@ struct DeviceInfo<'a> {
}
impl<'a> DeviceInfo<'a> {
fn get(display: &'a XConnection, device: c_int) -> Self {
fn get(display: &'a XConnection, device: c_int) -> Option<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,
}
display.check_errors()
.ok()
.and_then(|_| {
if info.is_null() || count == 0 {
None
} else {
Some(DeviceInfo {
display,
info,
count: count as usize,
})
}
})
}
}
}
impl<'a> Drop for DeviceInfo<'a> {
fn drop(&mut self) {
assert!(!self.info.is_null());
unsafe { (self.display.xinput2.XIFreeDeviceInfo)(self.info as *mut _) };
}
}
@ -1020,15 +1153,19 @@ impl Window {
window: &::WindowAttributes,
pl_attribs: &PlatformSpecificWindowBuilderAttributes
) -> Result<Self, CreationError> {
let win = Arc::new(try!(Window2::new(&x_events_loop, window, pl_attribs)));
let win = Arc::new(Window2::new(&x_events_loop, window, pl_attribs)?);
x_events_loop.shared_state
.borrow_mut()
.insert(win.id(), Arc::downgrade(&win.shared_state));
x_events_loop.ime
.borrow_mut()
.create_context(win.id().0)
.expect("Failed to create input context");
x_events_loop.windows.lock().unwrap().insert(win.id(), WindowData {
config: None,
x_events_loop.windows.lock().insert(win.id(), WindowData {
config: Default::default(),
multitouch: window.multitouch,
cursor_pos: None,
});
@ -1050,7 +1187,6 @@ impl Window {
pub fn send_xim_spot(&self, x: i16, y: i16) {
let _ = self.ime_sender
.lock()
.unwrap()
.send((self.window.id().0, x, y));
}
}
@ -1058,10 +1194,9 @@ impl Window {
impl Drop for Window {
fn drop(&mut self) {
if let (Some(windows), Some(display)) = (self.windows.upgrade(), self.display.upgrade()) {
if let Some(_) = windows.lock().unwrap().remove(&self.window.id()) {
if let Some(_) = windows.lock().remove(&self.window.id()) {
unsafe {
(display.xlib.XDestroyWindow)(display.display, self.window.id().0);
display.check_errors().expect("Failed to destroy window");
}
}
}
@ -1069,8 +1204,9 @@ impl Drop for Window {
}
/// State maintained for translating window-related events
#[derive(Debug)]
struct WindowData {
config: Option<WindowConfig>,
config: WindowConfig,
multitouch: bool,
cursor_pos: Option<(f64, f64)>,
}
@ -1078,21 +1214,13 @@ struct WindowData {
// Required by ffi members
unsafe impl Send for WindowData {}
#[derive(Debug, Default)]
struct WindowConfig {
size: (c_int, c_int),
position: (c_int, c_int),
pub size: Option<(c_int, c_int)>,
pub position: Option<(c_int, c_int)>,
pub inner_position: Option<(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> {
@ -1136,6 +1264,9 @@ fn mkdid(w: c_int) -> ::DeviceId { ::DeviceId(::platform::DeviceId::X(DeviceId(w
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)]
@ -1152,24 +1283,25 @@ enum ScrollOrientation {
}
impl Device {
fn new(el: &EventsLoop, info: &ffi::XIDeviceInfo) -> Self
{
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;
| 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);
}
util::select_xinput_events(
&el.display,
el.root,
info.deviceid,
mask,
)
}.queue(); // The request buffer is flushed when we poll for events
// Identify scroll axes
for class_ptr in Device::classes(info) {
@ -1195,6 +1327,7 @@ impl Device {
let mut device = Device {
name: name.into_owned(),
scroll_axes: scroll_axes,
attachment: info.attachment,
};
device.reset_scroll_position(info);
device