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:
parent
fee874b5b7
commit
c4b92ebd45
16 changed files with 1911 additions and 1340 deletions
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue