Richer input events
This expands input events to represent sub-pixel mouse positions, devices responsible for generating events, and raw device-oriented events. The X11 back end is refactored to make full use of the new expressiveness. Other backends have had new functionality minimally stubbed out, save for the macos backend which already supports sub-pixel mouse positions.
This commit is contained in:
parent
544ee13bf3
commit
22bc119cd7
19 changed files with 994 additions and 786 deletions
|
|
@ -1,14 +1,11 @@
|
|||
use {WindowEvent as Event, MouseCursor};
|
||||
use MouseCursor;
|
||||
use CreationError;
|
||||
use CreationError::OsError;
|
||||
use libc;
|
||||
use std::borrow::Borrow;
|
||||
use std::{mem, ptr, cmp};
|
||||
use std::cell::Cell;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::collections::VecDeque;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::os::raw::c_long;
|
||||
use std::os::raw::{c_int, c_long, c_uchar};
|
||||
use std::thread;
|
||||
use std::time::Duration;
|
||||
|
||||
|
|
@ -18,14 +15,8 @@ use platform::PlatformSpecificWindowBuilderAttributes;
|
|||
|
||||
use platform::MonitorId as PlatformMonitorId;
|
||||
|
||||
use super::input::XInputEventHandler;
|
||||
use super::{ffi};
|
||||
use super::{MonitorId, XConnection};
|
||||
|
||||
// XOpenIM doesn't seem to be thread-safe
|
||||
lazy_static! { // TODO: use a static mutex when that's possible, and put me back in my function
|
||||
static ref GLOBAL_XOPENIM_LOCK: Mutex<()> = Mutex::new(());
|
||||
}
|
||||
use super::{MonitorId, XConnection, WindowId, EventsLoop};
|
||||
|
||||
// TODO: remove me
|
||||
fn with_c_str<F, T>(s: &str, f: F) -> T where F: FnOnce(*const libc::c_char) -> T {
|
||||
|
|
@ -47,8 +38,6 @@ pub struct XWindow {
|
|||
is_fullscreen: bool,
|
||||
screen_id: libc::c_int,
|
||||
xf86_desk_mode: Option<ffi::XF86VidModeModeInfo>,
|
||||
ic: ffi::XIC,
|
||||
im: ffi::XIM,
|
||||
window_proxy_data: Arc<Mutex<Option<WindowProxyData>>>,
|
||||
}
|
||||
|
||||
|
|
@ -65,8 +54,6 @@ impl Drop for XWindow {
|
|||
// are no longer able to send messages to this window.
|
||||
*self.window_proxy_data.lock().unwrap() = None;
|
||||
|
||||
let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap();
|
||||
|
||||
if self.is_fullscreen {
|
||||
if let Some(mut xf86_desk_mode) = self.xf86_desk_mode {
|
||||
(self.display.xf86vmode.XF86VidModeSwitchToMode)(self.display.display, self.screen_id, &mut xf86_desk_mode);
|
||||
|
|
@ -74,8 +61,6 @@ impl Drop for XWindow {
|
|||
(self.display.xf86vmode.XF86VidModeSetViewPort)(self.display.display, self.screen_id, 0, 0);
|
||||
}
|
||||
|
||||
(self.display.xlib.XDestroyIC)(self.ic);
|
||||
(self.display.xlib.XCloseIM)(self.im);
|
||||
(self.display.xlib.XDestroyWindow)(self.display.display, self.window);
|
||||
}
|
||||
}
|
||||
|
|
@ -111,190 +96,17 @@ impl WindowProxy {
|
|||
}
|
||||
}
|
||||
|
||||
// XEvents of type GenericEvent store their actual data
|
||||
// in an XGenericEventCookie data structure. This is a wrapper
|
||||
// to extract the cookie from a GenericEvent XEvent and release
|
||||
// the cookie data once it has been processed
|
||||
struct GenericEventCookie<'a> {
|
||||
display: &'a XConnection,
|
||||
cookie: ffi::XGenericEventCookie
|
||||
}
|
||||
|
||||
impl<'a> GenericEventCookie<'a> {
|
||||
fn from_event<'b>(display: &'b XConnection, event: ffi::XEvent) -> Option<GenericEventCookie<'b>> {
|
||||
unsafe {
|
||||
let mut cookie: ffi::XGenericEventCookie = From::from(event);
|
||||
if (display.xlib.XGetEventData)(display.display, &mut cookie) == ffi::True {
|
||||
Some(GenericEventCookie{display: display, cookie: cookie})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Drop for GenericEventCookie<'a> {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let xlib = &self.display.xlib;
|
||||
(xlib.XFreeEventData)(self.display.display, &mut self.cookie);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct PollEventsIterator<'a> {
|
||||
window: &'a Window
|
||||
}
|
||||
|
||||
impl<'a> Iterator for PollEventsIterator<'a> {
|
||||
type Item = Event;
|
||||
|
||||
fn next(&mut self) -> Option<Event> {
|
||||
let xlib = &self.window.x.display.xlib;
|
||||
|
||||
loop {
|
||||
if let Some(ev) = self.window.pending_events.lock().unwrap().pop_front() {
|
||||
return Some(ev);
|
||||
}
|
||||
|
||||
let mut xev = unsafe { mem::uninitialized() };
|
||||
|
||||
// Get the next X11 event. XNextEvent will block if there's no
|
||||
// events available; checking the count first ensures an event will
|
||||
// be returned without blocking.
|
||||
//
|
||||
// Functions like XCheckTypedEvent can prevent events from being
|
||||
// popped if they are of the wrong type in which case winit would
|
||||
// enter a busy loop. To avoid that, XNextEvent is used to pop
|
||||
// events off the queue since it will accept any event type.
|
||||
unsafe {
|
||||
let count = (xlib.XPending)(self.window.x.display.display);
|
||||
if count == 0 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let res = (xlib.XNextEvent)(self.window.x.display.display, &mut xev);
|
||||
|
||||
// Can res ever be none zero if count is > 0?
|
||||
assert!(res == 0);
|
||||
};
|
||||
|
||||
match xev.get_type() {
|
||||
ffi::MappingNotify => {
|
||||
unsafe { (xlib.XRefreshKeyboardMapping)(mem::transmute(&xev)); }
|
||||
self.window.x.display.check_errors().expect("Failed to call XRefreshKeyboardMapping");
|
||||
},
|
||||
|
||||
ffi::ClientMessage => {
|
||||
use events::WindowEvent::{Closed, Awakened};
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
|
||||
let client_msg: &ffi::XClientMessageEvent = unsafe { mem::transmute(&xev) };
|
||||
|
||||
if client_msg.data.get_long(0) == self.window.wm_delete_window as libc::c_long {
|
||||
self.window.is_closed.store(true, Relaxed);
|
||||
return Some(Closed);
|
||||
} else {
|
||||
return Some(Awakened);
|
||||
}
|
||||
},
|
||||
|
||||
ffi::ConfigureNotify => {
|
||||
use events::WindowEvent::Resized;
|
||||
let cfg_event: &ffi::XConfigureEvent = unsafe { mem::transmute(&xev) };
|
||||
let (current_width, current_height) = self.window.current_size.get();
|
||||
if current_width != cfg_event.width || current_height != cfg_event.height {
|
||||
self.window.current_size.set((cfg_event.width, cfg_event.height));
|
||||
return Some(Resized(cfg_event.width as u32, cfg_event.height as u32));
|
||||
}
|
||||
},
|
||||
|
||||
ffi::Expose => {
|
||||
use events::WindowEvent::Refresh;
|
||||
return Some(Refresh);
|
||||
},
|
||||
|
||||
ffi::KeyPress | ffi::KeyRelease => {
|
||||
let mut event: &mut ffi::XKeyEvent = unsafe { mem::transmute(&mut xev) };
|
||||
let events = self.window.input_handler.lock().unwrap().translate_key_event(&mut event);
|
||||
for event in events {
|
||||
self.window.pending_events.lock().unwrap().push_back(event);
|
||||
}
|
||||
},
|
||||
|
||||
ffi::GenericEvent => {
|
||||
if let Some(cookie) = GenericEventCookie::from_event(self.window.x.display.borrow(), xev) {
|
||||
match cookie.cookie.evtype {
|
||||
ffi::XI_DeviceChanged...ffi::XI_LASTEVENT => {
|
||||
match self.window.input_handler.lock() {
|
||||
Ok(mut handler) => {
|
||||
match handler.translate_event(&cookie.cookie) {
|
||||
Some(event) => self.window.pending_events.lock().unwrap().push_back(event),
|
||||
None => {}
|
||||
}
|
||||
},
|
||||
Err(_) => {}
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct WaitEventsIterator<'a> {
|
||||
window: &'a Window,
|
||||
}
|
||||
|
||||
impl<'a> Iterator for WaitEventsIterator<'a> {
|
||||
type Item = Event;
|
||||
|
||||
fn next(&mut self) -> Option<Event> {
|
||||
use std::sync::atomic::Ordering::Relaxed;
|
||||
use std::mem;
|
||||
|
||||
while !self.window.is_closed.load(Relaxed) {
|
||||
if let Some(ev) = self.window.pending_events.lock().unwrap().pop_front() {
|
||||
return Some(ev);
|
||||
}
|
||||
|
||||
// this will block until an event arrives, but doesn't remove
|
||||
// it from the queue
|
||||
let mut xev = unsafe { mem::uninitialized() };
|
||||
unsafe { (self.window.x.display.xlib.XPeekEvent)(self.window.x.display.display, &mut xev) };
|
||||
self.window.x.display.check_errors().expect("Failed to call XPeekEvent");
|
||||
|
||||
// calling poll_events()
|
||||
if let Some(ev) = self.window.poll_events().next() {
|
||||
return Some(ev);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Window {
|
||||
pub x: Arc<XWindow>,
|
||||
is_closed: AtomicBool,
|
||||
wm_delete_window: ffi::Atom,
|
||||
current_size: Cell<(libc::c_int, libc::c_int)>,
|
||||
/// Events that have been retreived with XLib but not dispatched with iterators yet
|
||||
pending_events: Mutex<VecDeque<Event>>,
|
||||
cursor_state: Mutex<CursorState>,
|
||||
input_handler: Mutex<XInputEventHandler>
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub fn new(display: &Arc<XConnection>, window_attrs: &WindowAttributes,
|
||||
pub fn new(ctx: &EventsLoop, window_attrs: &WindowAttributes,
|
||||
pl_attribs: &PlatformSpecificWindowBuilderAttributes)
|
||||
-> Result<Window, CreationError>
|
||||
{
|
||||
let display = &ctx.display;
|
||||
let dimensions = {
|
||||
|
||||
// x11 only applies constraints when the window is actively resized
|
||||
|
|
@ -413,49 +225,13 @@ impl Window {
|
|||
display.check_errors().expect("Failed to set window visibility");
|
||||
}
|
||||
|
||||
// creating window, step 2
|
||||
let wm_delete_window = unsafe {
|
||||
let mut wm_delete_window = with_c_str("WM_DELETE_WINDOW", |delete_window|
|
||||
(display.xlib.XInternAtom)(display.display, delete_window, 0)
|
||||
);
|
||||
display.check_errors().expect("Failed to call XInternAtom");
|
||||
(display.xlib.XSetWMProtocols)(display.display, window, &mut wm_delete_window, 1);
|
||||
// Opt into handling window close
|
||||
unsafe {
|
||||
(display.xlib.XSetWMProtocols)(display.display, window, &ctx.wm_delete_window as *const _ as *mut _, 1);
|
||||
display.check_errors().expect("Failed to call XSetWMProtocols");
|
||||
(display.xlib.XFlush)(display.display);
|
||||
display.check_errors().expect("Failed to call XFlush");
|
||||
|
||||
wm_delete_window
|
||||
};
|
||||
|
||||
// creating IM
|
||||
let im = unsafe {
|
||||
let _lock = GLOBAL_XOPENIM_LOCK.lock().unwrap();
|
||||
|
||||
let im = (display.xlib.XOpenIM)(display.display, ptr::null_mut(), ptr::null_mut(), ptr::null_mut());
|
||||
if im.is_null() {
|
||||
return Err(OsError(format!("XOpenIM failed")));
|
||||
}
|
||||
im
|
||||
};
|
||||
|
||||
// creating input context
|
||||
let ic = unsafe {
|
||||
let ic = with_c_str("inputStyle", |input_style|
|
||||
with_c_str("clientWindow", |client_window|
|
||||
(display.xlib.XCreateIC)(
|
||||
im, input_style,
|
||||
ffi::XIMPreeditNothing | ffi::XIMStatusNothing, client_window,
|
||||
window, ptr::null::<()>()
|
||||
)
|
||||
)
|
||||
);
|
||||
if ic.is_null() {
|
||||
return Err(OsError(format!("XCreateIC failed")));
|
||||
}
|
||||
(display.xlib.XSetICFocus)(ic);
|
||||
display.check_errors().expect("Failed to call XSetICFocus");
|
||||
ic
|
||||
};
|
||||
}
|
||||
|
||||
// Attempt to make keyboard input repeat detectable
|
||||
unsafe {
|
||||
|
|
@ -569,6 +345,25 @@ impl Window {
|
|||
|
||||
}
|
||||
|
||||
// Select XInput2 events
|
||||
{
|
||||
let mask = ffi::XI_MotionMask
|
||||
| ffi::XI_ButtonPressMask | ffi::XI_ButtonReleaseMask
|
||||
// | ffi::XI_KeyPressMask | ffi::XI_KeyReleaseMask
|
||||
| ffi::XI_EnterMask | ffi::XI_LeaveMask
|
||||
| ffi::XI_FocusInMask | ffi::XI_FocusOutMask
|
||||
| if window_attrs.multitouch { ffi::XI_TouchBeginMask | ffi::XI_TouchUpdateMask | ffi::XI_TouchEndMask } else { 0 };
|
||||
unsafe {
|
||||
let mut event_mask = ffi::XIEventMask{
|
||||
deviceid: ffi::XIAllMasterDevices,
|
||||
mask: mem::transmute::<*const i32, *mut c_uchar>(&mask as *const i32),
|
||||
mask_len: mem::size_of_val(&mask) as c_int,
|
||||
};
|
||||
(display.xinput2.XISelectEvents)(display.display, window,
|
||||
&mut event_mask as *mut ffi::XIEventMask, 1);
|
||||
};
|
||||
}
|
||||
|
||||
// creating the window object
|
||||
let window_proxy_data = WindowProxyData {
|
||||
display: display.clone(),
|
||||
|
|
@ -580,19 +375,12 @@ impl Window {
|
|||
x: Arc::new(XWindow {
|
||||
display: display.clone(),
|
||||
window: window,
|
||||
im: im,
|
||||
ic: ic,
|
||||
screen_id: screen_id,
|
||||
is_fullscreen: is_fullscreen,
|
||||
xf86_desk_mode: xf86_desk_mode,
|
||||
window_proxy_data: window_proxy_data,
|
||||
}),
|
||||
is_closed: AtomicBool::new(false),
|
||||
wm_delete_window: wm_delete_window,
|
||||
current_size: Cell::new((0, 0)),
|
||||
pending_events: Mutex::new(VecDeque::new()),
|
||||
cursor_state: Mutex::new(CursorState::Normal),
|
||||
input_handler: Mutex::new(XInputEventHandler::new(display, window, ic, window_attrs))
|
||||
};
|
||||
|
||||
window.set_title(&window_attrs.title);
|
||||
|
|
@ -768,20 +556,6 @@ impl Window {
|
|||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn poll_events(&self) -> PollEventsIterator {
|
||||
PollEventsIterator {
|
||||
window: self
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn wait_events(&self) -> WaitEventsIterator {
|
||||
WaitEventsIterator {
|
||||
window: self
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_xlib_display(&self) -> *mut libc::c_void {
|
||||
self.x.display.display as *mut libc::c_void
|
||||
|
|
@ -1016,4 +790,7 @@ impl Window {
|
|||
self.x.display.check_errors().map_err(|_| ())
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn id(&self) -> WindowId { WindowId(self.x.window) }
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue