mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-28 16:11:29 +11:00
e707052f66
* Move `ModifiersChanged` variant to `WindowEvent` * macos: Fix flags_changed for ModifiersChanged variant move I haven't look too deep at what this does internally, but at least cargo-check is fully happy now. :) Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * macos: Fire a ModifiersChanged event on window_did_resign_key From debugging, I determined that macOS' emission of a flagsChanged around window switching is inconsistent. It is fair to assume, I think, that when the user switches windows, they do not expect their former modifiers state to remain effective; so I think it's best to clear that state by sending a ModifiersChanged(ModifiersState::empty()). Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * windows: Fix build I don't know enough about the code to implement the fix as it is done on this branch, but this commit at least fixes the build. Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * windows: Send ModifiersChanged(ModifiersState::empty) on KILLFOCUS Very similar to the changes made in [1], as focus is lost, send an event to the window indicating that the modifiers have been released. It's unclear to me (without a Windows device to test this on) whether this is necessary, but it certainly ensures that unfocused windows will have at least received this event, which is an improvement. [1]: f79f21641a31da3e4039d41be89047cdcc6028f7 Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * macos: Add a hook to update stale modifiers Sometimes, `ViewState` and `event` might have different values for their stored `modifiers` flags. These are internally stored as a bitmask in the latter and an enum in the former. We can check to see if they differ, and if they do, automatically dispatch an event to update consumers of modifier state as well as the stored `state.modifiers`. That's what the hook does. This hook is then called in the key_down, mouse_entered, mouse_exited, mouse_click, scroll_wheel, and pressure_change_with_event callbacks, which each will contain updated modifiers. Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * Only call event_mods once when determining whether to update state Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * flags_changed: Memoize window_id collection Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * window_did_resign_key: Remove synthetic ModifiersChanged event We no longer need to emit this event, since we are checking the state of our modifiers before emitting most other events. Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * mouse_motion: Add a call to update_potentially_stale_modifiers Now, cover all events (that I can think of, at least) where stale modifiers might affect how user programs behave. Effectively, every human-interface event (keypress, mouse click, keydown, etc.) will cause a ModifiersChanged event to be fired if something has changed. Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * key_up: Add a call to update_potentially_stale_modifiers We also want to make sure modifiers state is synchronized here, too. Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * mouse_motion: Remove update_potentially_stale_modifiers invocation Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * Retry CI * ViewState: Promote visibility of modifiers to the macos impl This is so that we can interact with the ViewState directly from the WindowDelegate. Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * window_delegate: Synthetically set modifiers state to empty on resignKey This logic is implemented similarly on other platforms, so we wish to regain parity here. Originally this behavior was implemented to always fire an event with ModifiersState::empty(), but that was not the best as it was not necessarily correct and could be a duplicate event. This solution is perhaps the most elegant possible to implement the desired behavior of sending a synthetic empty modifiers event when a window loses focus, trading some safety for interoperation between the NSWindowDelegate and the NSView (as the objc runtime must now be consulted in order to acquire access to the ViewState which is "owned" by the NSView). Signed-off-by: Kristofer Rye <kristofer.rye@gmail.com> * Check for modifiers change in window events * Fix modifier changed on macOS Since the `mouse_entered` function was generating a mouse motion, which updates the modifier state, a modifiers changed event was incorrectly generated. The updating of the modifier state has also been changed to make sure it consistently happens before events that have a modifier state attached to it, without happening on any other event. This of course means that no `CursorMoved` event is generated anymore when the user enters the window without it being focused, however I'd say that is consistent with how winit should behave. * Fix unused variable warning * Move changelog entry into `Unreleased` section Co-authored-by: Freya Gentz <zegentzy@protonmail.com> Co-authored-by: Kristofer Rye <kristofer.rye@gmail.com> Co-authored-by: Christian Duerr <contact@christianduerr.com>
713 lines
22 KiB
Rust
713 lines
22 KiB
Rust
#![cfg(any(
|
|
target_os = "linux",
|
|
target_os = "dragonfly",
|
|
target_os = "freebsd",
|
|
target_os = "netbsd",
|
|
target_os = "openbsd"
|
|
))]
|
|
|
|
mod dnd;
|
|
mod event_processor;
|
|
mod events;
|
|
pub mod ffi;
|
|
mod ime;
|
|
mod monitor;
|
|
pub mod util;
|
|
mod window;
|
|
mod xdisplay;
|
|
|
|
pub use self::{
|
|
monitor::{MonitorHandle, VideoMode},
|
|
window::UnownedWindow,
|
|
xdisplay::{XConnection, XError, XNotSupported},
|
|
};
|
|
|
|
use std::{
|
|
cell::RefCell,
|
|
collections::{HashMap, HashSet},
|
|
ffi::CStr,
|
|
mem::{self, MaybeUninit},
|
|
ops::Deref,
|
|
os::raw::*,
|
|
ptr,
|
|
rc::Rc,
|
|
slice,
|
|
sync::{mpsc, Arc, Mutex, Weak},
|
|
time::{Duration, Instant},
|
|
};
|
|
|
|
use libc::{self, setlocale, LC_CTYPE};
|
|
|
|
use mio::{unix::EventedFd, Events, Poll, PollOpt, Ready, Token};
|
|
|
|
use mio_extras::channel::{channel, Receiver, SendError, Sender};
|
|
|
|
use self::{
|
|
dnd::{Dnd, DndState},
|
|
event_processor::EventProcessor,
|
|
ime::{Ime, ImeCreationError, ImeReceiver, ImeSender},
|
|
util::modifiers::ModifierKeymap,
|
|
};
|
|
use crate::{
|
|
error::OsError as RootOsError,
|
|
event::{Event, StartCause},
|
|
event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW},
|
|
platform_impl::{platform::sticky_exit_callback, PlatformSpecificWindowBuilderAttributes},
|
|
window::WindowAttributes,
|
|
};
|
|
|
|
const X_TOKEN: Token = Token(0);
|
|
const USER_TOKEN: Token = Token(1);
|
|
|
|
pub struct EventLoopWindowTarget<T> {
|
|
xconn: Arc<XConnection>,
|
|
wm_delete_window: ffi::Atom,
|
|
net_wm_ping: ffi::Atom,
|
|
ime_sender: ImeSender,
|
|
root: ffi::Window,
|
|
ime: RefCell<Ime>,
|
|
windows: RefCell<HashMap<WindowId, Weak<UnownedWindow>>>,
|
|
pending_redraws: Arc<Mutex<HashSet<WindowId>>>,
|
|
_marker: ::std::marker::PhantomData<T>,
|
|
}
|
|
|
|
pub struct EventLoop<T: 'static> {
|
|
poll: Poll,
|
|
event_processor: EventProcessor<T>,
|
|
user_channel: Receiver<T>,
|
|
user_sender: Sender<T>,
|
|
target: Rc<RootELW<T>>,
|
|
}
|
|
|
|
pub struct EventLoopProxy<T: 'static> {
|
|
user_sender: Sender<T>,
|
|
}
|
|
|
|
impl<T: 'static> Clone for EventLoopProxy<T> {
|
|
fn clone(&self) -> Self {
|
|
EventLoopProxy {
|
|
user_sender: self.user_sender.clone(),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<T: 'static> EventLoop<T> {
|
|
pub fn new(xconn: Arc<XConnection>) -> EventLoop<T> {
|
|
let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) };
|
|
|
|
let wm_delete_window = unsafe { xconn.get_atom_unchecked(b"WM_DELETE_WINDOW\0") };
|
|
|
|
let net_wm_ping = unsafe { xconn.get_atom_unchecked(b"_NET_WM_PING\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 {
|
|
// Remember default locale to restore it if target locale is unsupported
|
|
// by Xlib
|
|
let default_locale = setlocale(LC_CTYPE, ptr::null());
|
|
setlocale(LC_CTYPE, b"\0".as_ptr() as *const _);
|
|
|
|
// Check if set locale is supported by Xlib.
|
|
// If not, calls to some Xlib functions like `XSetLocaleModifiers`
|
|
// will fail.
|
|
let locale_supported = (xconn.xlib.XSupportsLocale)() == 1;
|
|
if !locale_supported {
|
|
let unsupported_locale = setlocale(LC_CTYPE, ptr::null());
|
|
warn!(
|
|
"Unsupported locale \"{}\". Restoring default locale \"{}\".",
|
|
CStr::from_ptr(unsupported_locale).to_string_lossy(),
|
|
CStr::from_ptr(default_locale).to_string_lossy()
|
|
);
|
|
// Restore default locale
|
|
setlocale(LC_CTYPE, default_locale);
|
|
}
|
|
}
|
|
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 = xconn
|
|
.select_xrandr_input(root)
|
|
.expect("Failed to query XRandR extension");
|
|
|
|
let xi2ext = unsafe {
|
|
let mut ext = XExtension::default();
|
|
|
|
let res = (xconn.xlib.XQueryExtension)(
|
|
xconn.display,
|
|
b"XInputExtension\0".as_ptr() as *const c_char,
|
|
&mut ext.opcode,
|
|
&mut ext.first_event_id,
|
|
&mut ext.first_error_id,
|
|
);
|
|
|
|
if res == ffi::False {
|
|
panic!("X server missing XInput extension");
|
|
}
|
|
|
|
ext
|
|
};
|
|
|
|
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 pending_redraws: Arc<Mutex<HashSet<WindowId>>> = Default::default();
|
|
|
|
let mut mod_keymap = ModifierKeymap::new();
|
|
mod_keymap.reset_from_x_connection(&xconn);
|
|
|
|
let target = Rc::new(RootELW {
|
|
p: super::EventLoopWindowTarget::X(EventLoopWindowTarget {
|
|
ime,
|
|
root,
|
|
windows: Default::default(),
|
|
_marker: ::std::marker::PhantomData,
|
|
ime_sender,
|
|
xconn,
|
|
wm_delete_window,
|
|
net_wm_ping,
|
|
pending_redraws: pending_redraws.clone(),
|
|
}),
|
|
_marker: ::std::marker::PhantomData,
|
|
});
|
|
|
|
let poll = Poll::new().unwrap();
|
|
|
|
let (user_sender, user_channel) = channel();
|
|
|
|
poll.register(
|
|
&EventedFd(&get_xtarget(&target).xconn.x11_fd),
|
|
X_TOKEN,
|
|
Ready::readable(),
|
|
PollOpt::level(),
|
|
)
|
|
.unwrap();
|
|
|
|
poll.register(
|
|
&user_channel,
|
|
USER_TOKEN,
|
|
Ready::readable(),
|
|
PollOpt::level(),
|
|
)
|
|
.unwrap();
|
|
|
|
let event_processor = EventProcessor {
|
|
target: target.clone(),
|
|
dnd,
|
|
devices: Default::default(),
|
|
randr_event_offset,
|
|
ime_receiver,
|
|
xi2ext,
|
|
mod_keymap,
|
|
device_mod_state: Default::default(),
|
|
num_touch: 0,
|
|
first_touch: None,
|
|
active_window: None,
|
|
};
|
|
|
|
// Register for device hotplug events
|
|
// (The request buffer is flushed during `init_device`)
|
|
get_xtarget(&target)
|
|
.xconn
|
|
.select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask)
|
|
.queue();
|
|
|
|
event_processor.init_device(ffi::XIAllDevices);
|
|
|
|
let result = EventLoop {
|
|
poll,
|
|
user_channel,
|
|
user_sender,
|
|
event_processor,
|
|
target,
|
|
};
|
|
|
|
result
|
|
}
|
|
|
|
pub fn create_proxy(&self) -> EventLoopProxy<T> {
|
|
EventLoopProxy {
|
|
user_sender: self.user_sender.clone(),
|
|
}
|
|
}
|
|
|
|
pub(crate) fn window_target(&self) -> &RootELW<T> {
|
|
&self.target
|
|
}
|
|
|
|
pub(crate) fn x_connection(&self) -> &Arc<XConnection> {
|
|
get_xtarget(&self.target).x_connection()
|
|
}
|
|
|
|
pub fn run_return<F>(&mut self, mut callback: F)
|
|
where
|
|
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
{
|
|
let mut control_flow = ControlFlow::default();
|
|
let mut events = Events::with_capacity(8);
|
|
|
|
callback(
|
|
crate::event::Event::NewEvents(crate::event::StartCause::Init),
|
|
&self.target,
|
|
&mut control_flow,
|
|
);
|
|
|
|
loop {
|
|
// Process all pending events
|
|
self.drain_events(&mut callback, &mut control_flow);
|
|
|
|
let wt = get_xtarget(&self.target);
|
|
|
|
// Empty the user event buffer
|
|
{
|
|
while let Ok(event) = self.user_channel.try_recv() {
|
|
sticky_exit_callback(
|
|
crate::event::Event::UserEvent(event),
|
|
&self.target,
|
|
&mut control_flow,
|
|
&mut callback,
|
|
);
|
|
}
|
|
}
|
|
// send MainEventsCleared
|
|
{
|
|
sticky_exit_callback(
|
|
crate::event::Event::MainEventsCleared,
|
|
&self.target,
|
|
&mut control_flow,
|
|
&mut callback,
|
|
);
|
|
}
|
|
// Empty the redraw requests
|
|
{
|
|
// Release the lock to prevent deadlock
|
|
let windows: Vec<_> = wt.pending_redraws.lock().unwrap().drain().collect();
|
|
|
|
for wid in windows {
|
|
sticky_exit_callback(
|
|
Event::RedrawRequested(crate::window::WindowId(super::WindowId::X(wid))),
|
|
&self.target,
|
|
&mut control_flow,
|
|
&mut callback,
|
|
);
|
|
}
|
|
}
|
|
// send RedrawEventsCleared
|
|
{
|
|
sticky_exit_callback(
|
|
crate::event::Event::RedrawEventsCleared,
|
|
&self.target,
|
|
&mut control_flow,
|
|
&mut callback,
|
|
);
|
|
}
|
|
|
|
let start = Instant::now();
|
|
let (mut cause, deadline, timeout);
|
|
|
|
match control_flow {
|
|
ControlFlow::Exit => break,
|
|
ControlFlow::Poll => {
|
|
cause = StartCause::Poll;
|
|
deadline = None;
|
|
timeout = Some(Duration::from_millis(0));
|
|
}
|
|
ControlFlow::Wait => {
|
|
cause = StartCause::WaitCancelled {
|
|
start,
|
|
requested_resume: None,
|
|
};
|
|
deadline = None;
|
|
timeout = None;
|
|
}
|
|
ControlFlow::WaitUntil(wait_deadline) => {
|
|
cause = StartCause::ResumeTimeReached {
|
|
start,
|
|
requested_resume: wait_deadline,
|
|
};
|
|
timeout = if wait_deadline > start {
|
|
Some(wait_deadline - start)
|
|
} else {
|
|
Some(Duration::from_millis(0))
|
|
};
|
|
deadline = Some(wait_deadline);
|
|
}
|
|
}
|
|
|
|
if self.event_processor.poll() {
|
|
// If the XConnection already contains buffered events, we don't
|
|
// need to wait for data on the socket.
|
|
// However, we still need to check for user events.
|
|
self.poll
|
|
.poll(&mut events, Some(Duration::from_millis(0)))
|
|
.unwrap();
|
|
events.clear();
|
|
|
|
callback(
|
|
crate::event::Event::NewEvents(cause),
|
|
&self.target,
|
|
&mut control_flow,
|
|
);
|
|
} else {
|
|
self.poll.poll(&mut events, timeout).unwrap();
|
|
events.clear();
|
|
|
|
let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline);
|
|
|
|
if wait_cancelled {
|
|
cause = StartCause::WaitCancelled {
|
|
start,
|
|
requested_resume: deadline,
|
|
};
|
|
}
|
|
|
|
callback(
|
|
crate::event::Event::NewEvents(cause),
|
|
&self.target,
|
|
&mut control_flow,
|
|
);
|
|
}
|
|
}
|
|
|
|
callback(
|
|
crate::event::Event::LoopDestroyed,
|
|
&self.target,
|
|
&mut control_flow,
|
|
);
|
|
}
|
|
|
|
pub fn run<F>(mut self, callback: F) -> !
|
|
where
|
|
F: 'static + FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
{
|
|
self.run_return(callback);
|
|
::std::process::exit(0);
|
|
}
|
|
|
|
fn drain_events<F>(&mut self, callback: &mut F, control_flow: &mut ControlFlow)
|
|
where
|
|
F: FnMut(Event<'_, T>, &RootELW<T>, &mut ControlFlow),
|
|
{
|
|
let target = &self.target;
|
|
let mut xev = MaybeUninit::uninit();
|
|
|
|
let wt = get_xtarget(&self.target);
|
|
|
|
while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } {
|
|
let mut xev = unsafe { xev.assume_init() };
|
|
self.event_processor.process_event(&mut xev, |event| {
|
|
sticky_exit_callback(
|
|
event,
|
|
target,
|
|
control_flow,
|
|
&mut |event, window_target, control_flow| {
|
|
if let Event::RedrawRequested(crate::window::WindowId(
|
|
super::WindowId::X(wid),
|
|
)) = event
|
|
{
|
|
wt.pending_redraws.lock().unwrap().insert(wid);
|
|
} else {
|
|
callback(event, window_target, control_flow);
|
|
}
|
|
},
|
|
);
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn get_xtarget<T>(target: &RootELW<T>) -> &EventLoopWindowTarget<T> {
|
|
match target.p {
|
|
super::EventLoopWindowTarget::X(ref target) => target,
|
|
_ => unreachable!(),
|
|
}
|
|
}
|
|
|
|
impl<T> EventLoopWindowTarget<T> {
|
|
/// Returns the `XConnection` of this events loop.
|
|
#[inline]
|
|
pub fn x_connection(&self) -> &Arc<XConnection> {
|
|
&self.xconn
|
|
}
|
|
}
|
|
|
|
impl<T: 'static> EventLoopProxy<T> {
|
|
pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed<T>> {
|
|
self.user_sender.send(event).map_err(|e| {
|
|
EventLoopClosed(if let SendError::Disconnected(x) = e {
|
|
x
|
|
} else {
|
|
unreachable!()
|
|
})
|
|
})
|
|
}
|
|
}
|
|
|
|
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 = 0;
|
|
let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count);
|
|
xconn.check_errors().ok()?;
|
|
|
|
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(Arc<UnownedWindow>);
|
|
|
|
impl Deref for Window {
|
|
type Target = UnownedWindow;
|
|
#[inline]
|
|
fn deref(&self) -> &UnownedWindow {
|
|
&*self.0
|
|
}
|
|
}
|
|
|
|
impl Window {
|
|
pub fn new<T>(
|
|
event_loop: &EventLoopWindowTarget<T>,
|
|
attribs: WindowAttributes,
|
|
pl_attribs: PlatformSpecificWindowBuilderAttributes,
|
|
) -> Result<Self, RootOsError> {
|
|
let window = Arc::new(UnownedWindow::new(&event_loop, attribs, pl_attribs)?);
|
|
event_loop
|
|
.windows
|
|
.borrow_mut()
|
|
.insert(window.id(), Arc::downgrade(&window));
|
|
Ok(Window(window))
|
|
}
|
|
}
|
|
|
|
impl Drop for Window {
|
|
fn drop(&mut self) {
|
|
let window = self.deref();
|
|
let xconn = &window.xconn;
|
|
unsafe {
|
|
(xconn.xlib.XDestroyWindow)(xconn.display, 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, Default, Copy, Clone)]
|
|
struct XExtension {
|
|
opcode: c_int,
|
|
first_event_id: c_int,
|
|
first_error_id: c_int,
|
|
}
|
|
|
|
fn mkwid(w: ffi::Window) -> crate::window::WindowId {
|
|
crate::window::WindowId(crate::platform_impl::WindowId::X(WindowId(w)))
|
|
}
|
|
fn mkdid(w: c_int) -> crate::event::DeviceId {
|
|
crate::event::DeviceId(crate::platform_impl::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<T: 'static>(el: &EventProcessor<T>, info: &ffi::XIDeviceInfo) -> Self {
|
|
let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() };
|
|
let mut scroll_axes = Vec::new();
|
|
|
|
let wt = get_xtarget(&el.target);
|
|
|
|
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
|
|
wt.xconn
|
|
.select_xinput_events(wt.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,
|
|
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,
|
|
)
|
|
}
|
|
}
|
|
}
|