mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-31 01:01:30 +11:00
0c151f9fb3
* Implement changes to `RedrawRequested` event Implements the changes described in #1041 for the X11 platform and for platform-independent public-facing code. * Fix `request_redraw` example * Fix examples in lib docs * Only issue `RedrawRequested` on final `Expose` event
1263 lines
58 KiB
Rust
1263 lines
58 KiB
Rust
use std::{cell::RefCell, collections::HashMap, rc::Rc, slice};
|
|
|
|
use libc::{c_char, c_int, c_long, c_uint, c_ulong};
|
|
|
|
use super::{
|
|
events, ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd,
|
|
DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId,
|
|
XExtension,
|
|
};
|
|
|
|
use util::modifiers::{ModifierKeyState, ModifierKeymap};
|
|
|
|
use crate::{
|
|
dpi::{LogicalPosition, LogicalSize},
|
|
event::{
|
|
DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, TouchPhase, WindowEvent,
|
|
},
|
|
event_loop::EventLoopWindowTarget as RootELW,
|
|
};
|
|
|
|
pub(super) struct EventProcessor<T: 'static> {
|
|
pub(super) dnd: Dnd,
|
|
pub(super) ime_receiver: ImeReceiver,
|
|
pub(super) randr_event_offset: c_int,
|
|
pub(super) devices: RefCell<HashMap<DeviceId, Device>>,
|
|
pub(super) xi2ext: XExtension,
|
|
pub(super) target: Rc<RootELW<T>>,
|
|
pub(super) mod_keymap: ModifierKeymap,
|
|
pub(super) device_mod_state: ModifierKeyState,
|
|
// Number of touch events currently in progress
|
|
pub(super) num_touch: u32,
|
|
pub(super) first_touch: Option<u64>,
|
|
}
|
|
|
|
impl<T: 'static> EventProcessor<T> {
|
|
pub(super) fn init_device(&self, device: c_int) {
|
|
let wt = get_xtarget(&self.target);
|
|
let mut devices = self.devices.borrow_mut();
|
|
if let Some(info) = DeviceInfo::get(&wt.xconn, device) {
|
|
for info in info.iter() {
|
|
devices.insert(DeviceId(info.deviceid), Device::new(&self, info));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn with_window<F, Ret>(&self, window_id: ffi::Window, callback: F) -> Option<Ret>
|
|
where
|
|
F: Fn(&UnownedWindow) -> Ret,
|
|
{
|
|
let mut deleted = false;
|
|
let window_id = WindowId(window_id);
|
|
let wt = get_xtarget(&self.target);
|
|
let result = wt
|
|
.windows
|
|
.borrow()
|
|
.get(&window_id)
|
|
.and_then(|window| {
|
|
let arc = window.upgrade();
|
|
deleted = arc.is_none();
|
|
arc
|
|
})
|
|
.map(|window| callback(&*window));
|
|
if deleted {
|
|
// Garbage collection
|
|
wt.windows.borrow_mut().remove(&window_id);
|
|
}
|
|
result
|
|
}
|
|
|
|
fn window_exists(&self, window_id: ffi::Window) -> bool {
|
|
self.with_window(window_id, |_| ()).is_some()
|
|
}
|
|
|
|
pub(super) fn poll(&self) -> bool {
|
|
let wt = get_xtarget(&self.target);
|
|
let result = unsafe { (wt.xconn.xlib.XPending)(wt.xconn.display) };
|
|
|
|
result != 0
|
|
}
|
|
|
|
pub(super) unsafe fn poll_one_event(&mut self, event_ptr: *mut ffi::XEvent) -> bool {
|
|
let wt = get_xtarget(&self.target);
|
|
// This function is used to poll and remove a single event
|
|
// from the Xlib event queue in a non-blocking, atomic way.
|
|
// XCheckIfEvent is non-blocking and removes events from queue.
|
|
// XNextEvent can't be used because it blocks while holding the
|
|
// global Xlib mutex.
|
|
// XPeekEvent does not remove events from the queue.
|
|
unsafe extern "C" fn predicate(
|
|
_display: *mut ffi::Display,
|
|
_event: *mut ffi::XEvent,
|
|
_arg: *mut c_char,
|
|
) -> c_int {
|
|
// This predicate always returns "true" (1) to accept all events
|
|
1
|
|
}
|
|
|
|
let result = (wt.xconn.xlib.XCheckIfEvent)(
|
|
wt.xconn.display,
|
|
event_ptr,
|
|
Some(predicate),
|
|
std::ptr::null_mut(),
|
|
);
|
|
|
|
result != 0
|
|
}
|
|
|
|
pub(super) fn process_event<F>(&mut self, xev: &mut ffi::XEvent, mut callback: F)
|
|
where
|
|
F: FnMut(Event<T>),
|
|
{
|
|
let wt = get_xtarget(&self.target);
|
|
// XFilterEvent tells us when an event has been discarded by the input method.
|
|
// Specifically, this involves all of the KeyPress events in compose/pre-edit sequences,
|
|
// along with an extra copy of the KeyRelease events. This also prevents backspace and
|
|
// arrow keys from being detected twice.
|
|
if ffi::True
|
|
== unsafe {
|
|
(wt.xconn.xlib.XFilterEvent)(xev, {
|
|
let xev: &ffi::XAnyEvent = xev.as_ref();
|
|
xev.window
|
|
})
|
|
}
|
|
{
|
|
return;
|
|
}
|
|
|
|
// We can't call a `&mut self` method because of the above borrow,
|
|
// so we use this macro for repeated modifier state updates.
|
|
macro_rules! update_modifiers {
|
|
( $state:expr , $modifier:expr ) => {{
|
|
match ($state, $modifier) {
|
|
(state, modifier) => {
|
|
if let Some(modifiers) =
|
|
self.device_mod_state.update_state(&state, modifier)
|
|
{
|
|
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
|
|
callback(Event::DeviceEvent {
|
|
device_id,
|
|
event: DeviceEvent::ModifiersChanged { modifiers },
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}};
|
|
}
|
|
|
|
let event_type = xev.get_type();
|
|
match event_type {
|
|
ffi::MappingNotify => {
|
|
let mapping: &ffi::XMappingEvent = xev.as_ref();
|
|
|
|
if mapping.request == ffi::MappingModifier
|
|
|| mapping.request == ffi::MappingKeyboard
|
|
{
|
|
unsafe {
|
|
(wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut());
|
|
}
|
|
wt.xconn
|
|
.check_errors()
|
|
.expect("Failed to call XRefreshKeyboardMapping");
|
|
|
|
self.mod_keymap.reset_from_x_connection(&wt.xconn);
|
|
self.device_mod_state.update_keymap(&self.mod_keymap);
|
|
}
|
|
}
|
|
|
|
ffi::ClientMessage => {
|
|
let client_msg: &ffi::XClientMessageEvent = xev.as_ref();
|
|
|
|
let window = client_msg.window;
|
|
let window_id = mkwid(window);
|
|
|
|
if client_msg.data.get_long(0) as ffi::Atom == wt.wm_delete_window {
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: WindowEvent::CloseRequested,
|
|
});
|
|
} else if client_msg.data.get_long(0) as ffi::Atom == wt.net_wm_ping {
|
|
let response_msg: &mut ffi::XClientMessageEvent = xev.as_mut();
|
|
response_msg.window = wt.root;
|
|
wt.xconn
|
|
.send_event(
|
|
wt.root,
|
|
Some(ffi::SubstructureNotifyMask | ffi::SubstructureRedirectMask),
|
|
*response_msg,
|
|
)
|
|
.queue();
|
|
} else if client_msg.message_type == self.dnd.atoms.enter {
|
|
let source_window = client_msg.data.get_long(0) as c_ulong;
|
|
let flags = client_msg.data.get_long(1);
|
|
let version = flags >> 24;
|
|
self.dnd.version = Some(version);
|
|
let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1;
|
|
if !has_more_types {
|
|
let type_list = vec![
|
|
client_msg.data.get_long(2) as c_ulong,
|
|
client_msg.data.get_long(3) as c_ulong,
|
|
client_msg.data.get_long(4) as c_ulong,
|
|
];
|
|
self.dnd.type_list = Some(type_list);
|
|
} else if let Ok(more_types) = unsafe { self.dnd.get_type_list(source_window) }
|
|
{
|
|
self.dnd.type_list = Some(more_types);
|
|
}
|
|
} else if client_msg.message_type == self.dnd.atoms.position {
|
|
// This event occurs every time the mouse moves while a file's being dragged
|
|
// over our window. We emit HoveredFile in response; while the macOS backend
|
|
// does that upon a drag entering, XDND doesn't have access to the actual drop
|
|
// data until this event. For parity with other platforms, we only emit
|
|
// `HoveredFile` the first time, though if winit's API is later extended to
|
|
// supply position updates with `HoveredFile` or another event, implementing
|
|
// that here would be trivial.
|
|
|
|
let source_window = client_msg.data.get_long(0) as c_ulong;
|
|
|
|
// Equivalent to `(x << shift) | y`
|
|
// where `shift = mem::size_of::<c_short>() * 8`
|
|
// Note that coordinates are in "desktop space", not "window space"
|
|
// (in X11 parlance, they're root window coordinates)
|
|
//let packed_coordinates = client_msg.data.get_long(2);
|
|
//let shift = mem::size_of::<libc::c_short>() * 8;
|
|
//let x = packed_coordinates >> shift;
|
|
//let y = packed_coordinates & !(x << shift);
|
|
|
|
// By our own state flow, `version` should never be `None` at this point.
|
|
let version = self.dnd.version.unwrap_or(5);
|
|
|
|
// Action is specified in versions 2 and up, though we don't need it anyway.
|
|
//let action = client_msg.data.get_long(4);
|
|
|
|
let accepted = if let Some(ref type_list) = self.dnd.type_list {
|
|
type_list.contains(&self.dnd.atoms.uri_list)
|
|
} else {
|
|
false
|
|
};
|
|
|
|
if accepted {
|
|
self.dnd.source_window = Some(source_window);
|
|
unsafe {
|
|
if self.dnd.result.is_none() {
|
|
let time = if version >= 1 {
|
|
client_msg.data.get_long(3) as c_ulong
|
|
} else {
|
|
// In version 0, time isn't specified
|
|
ffi::CurrentTime
|
|
};
|
|
// This results in the `SelectionNotify` event below
|
|
self.dnd.convert_selection(window, time);
|
|
}
|
|
self.dnd
|
|
.send_status(window, source_window, DndState::Accepted)
|
|
.expect("Failed to send `XdndStatus` message.");
|
|
}
|
|
} else {
|
|
unsafe {
|
|
self.dnd
|
|
.send_status(window, source_window, DndState::Rejected)
|
|
.expect("Failed to send `XdndStatus` message.");
|
|
}
|
|
self.dnd.reset();
|
|
}
|
|
} else if client_msg.message_type == self.dnd.atoms.drop {
|
|
let (source_window, state) = if let Some(source_window) = self.dnd.source_window
|
|
{
|
|
if let Some(Ok(ref path_list)) = self.dnd.result {
|
|
for path in path_list {
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: WindowEvent::DroppedFile(path.clone()),
|
|
});
|
|
}
|
|
}
|
|
(source_window, DndState::Accepted)
|
|
} else {
|
|
// `source_window` won't be part of our DND state if we already rejected the drop in our
|
|
// `XdndPosition` handler.
|
|
let source_window = client_msg.data.get_long(0) as c_ulong;
|
|
(source_window, DndState::Rejected)
|
|
};
|
|
unsafe {
|
|
self.dnd
|
|
.send_finished(window, source_window, state)
|
|
.expect("Failed to send `XdndFinished` message.");
|
|
}
|
|
self.dnd.reset();
|
|
} else if client_msg.message_type == self.dnd.atoms.leave {
|
|
self.dnd.reset();
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: WindowEvent::HoveredFileCancelled,
|
|
});
|
|
}
|
|
}
|
|
|
|
ffi::SelectionNotify => {
|
|
let xsel: &ffi::XSelectionEvent = xev.as_ref();
|
|
|
|
let window = xsel.requestor;
|
|
let window_id = mkwid(window);
|
|
|
|
if xsel.property == self.dnd.atoms.selection {
|
|
let mut result = None;
|
|
|
|
// This is where we receive data from drag and drop
|
|
if let Ok(mut data) = unsafe { self.dnd.read_data(window) } {
|
|
let parse_result = self.dnd.parse_data(&mut data);
|
|
if let Ok(ref path_list) = parse_result {
|
|
for path in path_list {
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: WindowEvent::HoveredFile(path.clone()),
|
|
});
|
|
}
|
|
}
|
|
result = Some(parse_result);
|
|
}
|
|
|
|
self.dnd.result = result;
|
|
}
|
|
}
|
|
|
|
ffi::ConfigureNotify => {
|
|
#[derive(Debug, Default)]
|
|
struct Events {
|
|
resized: Option<WindowEvent>,
|
|
moved: Option<WindowEvent>,
|
|
dpi_changed: Option<WindowEvent>,
|
|
}
|
|
|
|
let xev: &ffi::XConfigureEvent = xev.as_ref();
|
|
let xwindow = xev.window;
|
|
let events = self.with_window(xwindow, |window| {
|
|
// So apparently...
|
|
// `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root
|
|
// `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent
|
|
// https://tronche.com/gui/x/icccm/sec-4.html#s-4.1.5
|
|
// We don't want to send `Moved` when this is false, since then every `Resized`
|
|
// (whether the window moved or not) is accompanied by an extraneous `Moved` event
|
|
// that has a position relative to the parent window.
|
|
let is_synthetic = xev.send_event == ffi::True;
|
|
|
|
// These are both in physical space.
|
|
let new_inner_size = (xev.width as u32, xev.height as u32);
|
|
let new_inner_position = (xev.x as i32, xev.y as i32);
|
|
|
|
let mut monitor = window.current_monitor(); // This must be done *before* locking!
|
|
let mut shared_state_lock = window.shared_state.lock();
|
|
|
|
let (mut resized, moved) = {
|
|
let resized =
|
|
util::maybe_change(&mut shared_state_lock.size, new_inner_size);
|
|
let moved = if is_synthetic {
|
|
util::maybe_change(
|
|
&mut shared_state_lock.inner_position,
|
|
new_inner_position,
|
|
)
|
|
} else {
|
|
// Detect when frame extents change.
|
|
// Since this isn't synthetic, as per the notes above, this position is relative to the
|
|
// parent window.
|
|
let rel_parent = new_inner_position;
|
|
if util::maybe_change(
|
|
&mut shared_state_lock.inner_position_rel_parent,
|
|
rel_parent,
|
|
) {
|
|
// This ensures we process the next `Moved`.
|
|
shared_state_lock.inner_position = None;
|
|
// Extra insurance against stale frame extents.
|
|
shared_state_lock.frame_extents = None;
|
|
}
|
|
false
|
|
};
|
|
(resized, moved)
|
|
};
|
|
|
|
let mut events = Events::default();
|
|
|
|
let new_outer_position = if moved || shared_state_lock.position.is_none() {
|
|
// We need to convert client area position to window position.
|
|
let frame_extents = shared_state_lock
|
|
.frame_extents
|
|
.as_ref()
|
|
.cloned()
|
|
.unwrap_or_else(|| {
|
|
let frame_extents =
|
|
wt.xconn.get_frame_extents_heuristic(xwindow, wt.root);
|
|
shared_state_lock.frame_extents = Some(frame_extents.clone());
|
|
frame_extents
|
|
});
|
|
let outer = frame_extents
|
|
.inner_pos_to_outer(new_inner_position.0, new_inner_position.1);
|
|
shared_state_lock.position = Some(outer);
|
|
if moved {
|
|
let logical_position =
|
|
LogicalPosition::from_physical(outer, monitor.hidpi_factor);
|
|
events.moved = Some(WindowEvent::Moved(logical_position));
|
|
}
|
|
outer
|
|
} else {
|
|
shared_state_lock.position.unwrap()
|
|
};
|
|
|
|
if is_synthetic {
|
|
// If we don't use the existing adjusted value when available, then the user can screw up the
|
|
// resizing by dragging across monitors *without* dropping the window.
|
|
let (width, height) = shared_state_lock
|
|
.dpi_adjusted
|
|
.unwrap_or_else(|| (xev.width as f64, xev.height as f64));
|
|
|
|
let last_hidpi_factor = shared_state_lock.last_monitor.hidpi_factor;
|
|
let new_hidpi_factor = {
|
|
let window_rect = util::AaRect::new(new_outer_position, new_inner_size);
|
|
let new_monitor = wt.xconn.get_monitor_for_window(Some(window_rect));
|
|
|
|
if new_monitor.is_dummy() {
|
|
// Avoid updating monitor using a dummy monitor handle
|
|
last_hidpi_factor
|
|
} else {
|
|
monitor = new_monitor;
|
|
shared_state_lock.last_monitor = monitor.clone();
|
|
monitor.hidpi_factor
|
|
}
|
|
};
|
|
if last_hidpi_factor != new_hidpi_factor {
|
|
events.dpi_changed =
|
|
Some(WindowEvent::HiDpiFactorChanged(new_hidpi_factor));
|
|
let (new_width, new_height, flusher) = window.adjust_for_dpi(
|
|
last_hidpi_factor,
|
|
new_hidpi_factor,
|
|
width,
|
|
height,
|
|
);
|
|
flusher.queue();
|
|
shared_state_lock.dpi_adjusted = Some((new_width, new_height));
|
|
// if the DPI factor changed, force a resize event to ensure the logical
|
|
// size is computed with the right DPI factor
|
|
resized = true;
|
|
}
|
|
}
|
|
|
|
// This is a hack to ensure that the DPI adjusted resize is actually applied on all WMs. KWin
|
|
// doesn't need this, but Xfwm does. The hack should not be run on other WMs, since tiling
|
|
// WMs constrain the window size, making the resize fail. This would cause an endless stream of
|
|
// XResizeWindow requests, making Xorg, the winit client, and the WM consume 100% of CPU.
|
|
if let Some(adjusted_size) = shared_state_lock.dpi_adjusted {
|
|
let rounded_size = (
|
|
adjusted_size.0.round() as u32,
|
|
adjusted_size.1.round() as u32,
|
|
);
|
|
if new_inner_size == rounded_size || !util::wm_name_is_one_of(&["Xfwm4"]) {
|
|
// When this finally happens, the event will not be synthetic.
|
|
shared_state_lock.dpi_adjusted = None;
|
|
} else {
|
|
unsafe {
|
|
(wt.xconn.xlib.XResizeWindow)(
|
|
wt.xconn.display,
|
|
xwindow,
|
|
rounded_size.0 as c_uint,
|
|
rounded_size.1 as c_uint,
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
if resized {
|
|
let logical_size =
|
|
LogicalSize::from_physical(new_inner_size, monitor.hidpi_factor);
|
|
events.resized = Some(WindowEvent::Resized(logical_size));
|
|
}
|
|
|
|
events
|
|
});
|
|
|
|
if let Some(events) = events {
|
|
let window_id = mkwid(xwindow);
|
|
if let Some(event) = events.dpi_changed {
|
|
callback(Event::WindowEvent { window_id, event });
|
|
}
|
|
if let Some(event) = events.resized {
|
|
callback(Event::WindowEvent { window_id, event });
|
|
}
|
|
if let Some(event) = events.moved {
|
|
callback(Event::WindowEvent { window_id, event });
|
|
}
|
|
}
|
|
}
|
|
|
|
ffi::ReparentNotify => {
|
|
let xev: &ffi::XReparentEvent = xev.as_ref();
|
|
|
|
// This is generally a reliable way to detect when the window manager's been
|
|
// replaced, though this event is only fired by reparenting window managers
|
|
// (which is almost all of them). Failing to correctly update WM info doesn't
|
|
// really have much impact, since on the WMs affected (xmonad, dwm, etc.) the only
|
|
// effect is that we waste some time trying to query unsupported properties.
|
|
wt.xconn.update_cached_wm_info(wt.root);
|
|
|
|
self.with_window(xev.window, |window| {
|
|
window.invalidate_cached_frame_extents();
|
|
});
|
|
}
|
|
|
|
ffi::DestroyNotify => {
|
|
let xev: &ffi::XDestroyWindowEvent = xev.as_ref();
|
|
|
|
let window = xev.window;
|
|
let window_id = mkwid(window);
|
|
|
|
// In the event that the window's been destroyed without being dropped first, we
|
|
// cleanup again here.
|
|
wt.windows.borrow_mut().remove(&WindowId(window));
|
|
|
|
// Since all XIM stuff needs to happen from the same thread, we destroy the input
|
|
// context here instead of when dropping the window.
|
|
wt.ime
|
|
.borrow_mut()
|
|
.remove_context(window)
|
|
.expect("Failed to destroy input context");
|
|
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: WindowEvent::Destroyed,
|
|
});
|
|
}
|
|
|
|
ffi::VisibilityNotify => {
|
|
let xev: &ffi::XVisibilityEvent = xev.as_ref();
|
|
let xwindow = xev.window;
|
|
|
|
self.with_window(xwindow, |window| window.visibility_notify());
|
|
}
|
|
|
|
ffi::Expose => {
|
|
let xev: &ffi::XExposeEvent = xev.as_ref();
|
|
|
|
// Multiple Expose events may be received for subareas of a window.
|
|
// We issue `RedrawRequested` only for the last event of such a series.
|
|
if xev.count == 0 {
|
|
let window = xev.window;
|
|
let window_id = mkwid(window);
|
|
|
|
callback(Event::RedrawRequested(window_id));
|
|
}
|
|
}
|
|
|
|
ffi::KeyPress | ffi::KeyRelease => {
|
|
use crate::event::ElementState::{Pressed, Released};
|
|
|
|
// Note that in compose/pre-edit sequences, this will always be Released.
|
|
let state = if xev.get_type() == ffi::KeyPress {
|
|
Pressed
|
|
} else {
|
|
Released
|
|
};
|
|
|
|
let xkev: &mut ffi::XKeyEvent = xev.as_mut();
|
|
|
|
let window = xkev.window;
|
|
let window_id = mkwid(window);
|
|
|
|
// Standard virtual core keyboard ID. XInput2 needs to be used to get a reliable
|
|
// value, though this should only be an issue under multiseat configurations.
|
|
let device = util::VIRTUAL_CORE_KEYBOARD;
|
|
let device_id = mkdid(device);
|
|
let keycode = xkev.keycode;
|
|
|
|
// When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with
|
|
// a keycode of 0.
|
|
if keycode != 0 {
|
|
let scancode = keycode - 8;
|
|
let keysym = wt.xconn.lookup_keysym(xkev);
|
|
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
|
|
|
update_modifiers!(
|
|
ModifiersState::from_x11_mask(xkev.state),
|
|
self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode)
|
|
);
|
|
|
|
let modifiers = self.device_mod_state.modifiers();
|
|
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: WindowEvent::KeyboardInput {
|
|
device_id,
|
|
input: KeyboardInput {
|
|
state,
|
|
scancode,
|
|
virtual_keycode,
|
|
modifiers,
|
|
},
|
|
is_synthetic: false,
|
|
},
|
|
});
|
|
}
|
|
|
|
if state == Pressed {
|
|
let written = if let Some(ic) = wt.ime.borrow().get_context(window) {
|
|
wt.xconn.lookup_utf8(ic, xkev)
|
|
} else {
|
|
return;
|
|
};
|
|
|
|
for chr in written.chars() {
|
|
let event = Event::WindowEvent {
|
|
window_id,
|
|
event: WindowEvent::ReceivedCharacter(chr),
|
|
};
|
|
callback(event);
|
|
}
|
|
}
|
|
}
|
|
|
|
ffi::GenericEvent => {
|
|
let guard = if let Some(e) = GenericEventCookie::from_event(&wt.xconn, *xev) {
|
|
e
|
|
} else {
|
|
return;
|
|
};
|
|
let xev = &guard.cookie;
|
|
if self.xi2ext.opcode != xev.extension {
|
|
return;
|
|
}
|
|
|
|
use crate::event::{
|
|
ElementState::{Pressed, Released},
|
|
MouseButton::{Left, Middle, Other, Right},
|
|
MouseScrollDelta::LineDelta,
|
|
Touch,
|
|
WindowEvent::{
|
|
AxisMotion, CursorEntered, CursorLeft, CursorMoved, Focused, MouseInput,
|
|
MouseWheel,
|
|
},
|
|
};
|
|
|
|
match xev.evtype {
|
|
ffi::XI_ButtonPress | ffi::XI_ButtonRelease => {
|
|
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
|
let window_id = mkwid(xev.event);
|
|
let device_id = mkdid(xev.deviceid);
|
|
if (xev.flags & ffi::XIPointerEmulated) != 0 {
|
|
// Deliver multi-touch events instead of emulated mouse events.
|
|
return;
|
|
}
|
|
|
|
let modifiers = ModifiersState::from_x11(&xev.mods);
|
|
update_modifiers!(modifiers, None);
|
|
|
|
let state = if xev.evtype == ffi::XI_ButtonPress {
|
|
Pressed
|
|
} else {
|
|
Released
|
|
};
|
|
match xev.detail as u32 {
|
|
ffi::Button1 => callback(Event::WindowEvent {
|
|
window_id,
|
|
event: MouseInput {
|
|
device_id,
|
|
state,
|
|
button: Left,
|
|
modifiers,
|
|
},
|
|
}),
|
|
ffi::Button2 => callback(Event::WindowEvent {
|
|
window_id,
|
|
event: MouseInput {
|
|
device_id,
|
|
state,
|
|
button: Middle,
|
|
modifiers,
|
|
},
|
|
}),
|
|
ffi::Button3 => callback(Event::WindowEvent {
|
|
window_id,
|
|
event: MouseInput {
|
|
device_id,
|
|
state,
|
|
button: Right,
|
|
modifiers,
|
|
},
|
|
}),
|
|
|
|
// Suppress emulated scroll wheel clicks, since we handle the real motion events for those.
|
|
// In practice, even clicky scroll wheels appear to be reported by evdev (and XInput2 in
|
|
// turn) as axis motion, so we don't otherwise special-case these button presses.
|
|
4 | 5 | 6 | 7 => {
|
|
if xev.flags & ffi::XIPointerEmulated == 0 {
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: MouseWheel {
|
|
device_id,
|
|
delta: match xev.detail {
|
|
4 => LineDelta(0.0, 1.0),
|
|
5 => LineDelta(0.0, -1.0),
|
|
6 => LineDelta(-1.0, 0.0),
|
|
7 => LineDelta(1.0, 0.0),
|
|
_ => unreachable!(),
|
|
},
|
|
phase: TouchPhase::Moved,
|
|
modifiers,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
x => callback(Event::WindowEvent {
|
|
window_id,
|
|
event: MouseInput {
|
|
device_id,
|
|
state,
|
|
button: Other(x as u8),
|
|
modifiers,
|
|
},
|
|
}),
|
|
}
|
|
}
|
|
ffi::XI_Motion => {
|
|
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
|
let device_id = mkdid(xev.deviceid);
|
|
let window_id = mkwid(xev.event);
|
|
let new_cursor_pos = (xev.event_x, xev.event_y);
|
|
|
|
let modifiers = ModifiersState::from_x11(&xev.mods);
|
|
update_modifiers!(modifiers, None);
|
|
|
|
let cursor_moved = self.with_window(xev.event, |window| {
|
|
let mut shared_state_lock = window.shared_state.lock();
|
|
util::maybe_change(&mut shared_state_lock.cursor_pos, new_cursor_pos)
|
|
});
|
|
if cursor_moved == Some(true) {
|
|
let dpi_factor =
|
|
self.with_window(xev.event, |window| window.hidpi_factor());
|
|
if let Some(dpi_factor) = dpi_factor {
|
|
let position = LogicalPosition::from_physical(
|
|
(xev.event_x as f64, xev.event_y as f64),
|
|
dpi_factor,
|
|
);
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: CursorMoved {
|
|
device_id,
|
|
position,
|
|
modifiers,
|
|
},
|
|
});
|
|
} else {
|
|
return;
|
|
}
|
|
} else if cursor_moved.is_none() {
|
|
return;
|
|
}
|
|
|
|
// More gymnastics, for self.devices
|
|
let mut events = Vec::new();
|
|
{
|
|
let mask = unsafe {
|
|
slice::from_raw_parts(
|
|
xev.valuators.mask,
|
|
xev.valuators.mask_len as usize,
|
|
)
|
|
};
|
|
let mut devices = self.devices.borrow_mut();
|
|
let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) {
|
|
Some(device) => device,
|
|
None => return,
|
|
};
|
|
|
|
let mut value = xev.valuators.values;
|
|
for i in 0..xev.valuators.mask_len * 8 {
|
|
if ffi::XIMaskIsSet(mask, i) {
|
|
let x = unsafe { *value };
|
|
if let Some(&mut (_, ref mut info)) = physical_device
|
|
.scroll_axes
|
|
.iter_mut()
|
|
.find(|&&mut (axis, _)| axis == i)
|
|
{
|
|
let delta = (x - info.position) / info.increment;
|
|
info.position = x;
|
|
events.push(Event::WindowEvent {
|
|
window_id,
|
|
event: MouseWheel {
|
|
device_id,
|
|
delta: match info.orientation {
|
|
ScrollOrientation::Horizontal => {
|
|
LineDelta(delta as f32, 0.0)
|
|
}
|
|
// X11 vertical scroll coordinates are opposite to winit's
|
|
ScrollOrientation::Vertical => {
|
|
LineDelta(0.0, -delta as f32)
|
|
}
|
|
},
|
|
phase: TouchPhase::Moved,
|
|
modifiers,
|
|
},
|
|
});
|
|
} else {
|
|
events.push(Event::WindowEvent {
|
|
window_id,
|
|
event: AxisMotion {
|
|
device_id,
|
|
axis: i as u32,
|
|
value: unsafe { *value },
|
|
},
|
|
});
|
|
}
|
|
value = unsafe { value.offset(1) };
|
|
}
|
|
}
|
|
}
|
|
for event in events {
|
|
callback(event);
|
|
}
|
|
}
|
|
|
|
ffi::XI_Enter => {
|
|
let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) };
|
|
|
|
let window_id = mkwid(xev.event);
|
|
let device_id = mkdid(xev.deviceid);
|
|
|
|
if let Some(all_info) = DeviceInfo::get(&wt.xconn, ffi::XIAllDevices) {
|
|
let mut devices = self.devices.borrow_mut();
|
|
for device_info in all_info.iter() {
|
|
if device_info.deviceid == xev.sourceid
|
|
// This is needed for resetting to work correctly on i3, and
|
|
// presumably some other WMs. On those, `XI_Enter` doesn't include
|
|
// the physical device ID, so both `sourceid` and `deviceid` are
|
|
// the virtual device.
|
|
|| device_info.attachment == xev.sourceid
|
|
{
|
|
let device_id = DeviceId(device_info.deviceid);
|
|
if let Some(device) = devices.get_mut(&device_id) {
|
|
device.reset_scroll_position(device_info);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if let Some(dpi_factor) =
|
|
self.with_window(xev.event, |window| window.hidpi_factor())
|
|
{
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: CursorEntered { device_id },
|
|
});
|
|
|
|
let position = LogicalPosition::from_physical(
|
|
(xev.event_x as f64, xev.event_y as f64),
|
|
dpi_factor,
|
|
);
|
|
|
|
// 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.
|
|
//
|
|
// This needs to only be done after confirming the window still exists,
|
|
// since otherwise we risk getting a `BadWindow` error if the window was
|
|
// dropped with queued events.
|
|
let modifiers = wt
|
|
.xconn
|
|
.query_pointer(xev.event, xev.deviceid)
|
|
.expect("Failed to query pointer device")
|
|
.get_modifier_state();
|
|
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: CursorMoved {
|
|
device_id,
|
|
position,
|
|
modifiers,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
ffi::XI_Leave => {
|
|
let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) };
|
|
|
|
// Leave, FocusIn, and FocusOut can be received by a window that's already
|
|
// been destroyed, which the user presumably doesn't want to deal with.
|
|
let window_closed = !self.window_exists(xev.event);
|
|
if !window_closed {
|
|
callback(Event::WindowEvent {
|
|
window_id: mkwid(xev.event),
|
|
event: CursorLeft {
|
|
device_id: mkdid(xev.deviceid),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
ffi::XI_FocusIn => {
|
|
let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) };
|
|
|
|
let dpi_factor =
|
|
match self.with_window(xev.event, |window| window.hidpi_factor()) {
|
|
Some(dpi_factor) => dpi_factor,
|
|
None => return,
|
|
};
|
|
let window_id = mkwid(xev.event);
|
|
|
|
wt.ime
|
|
.borrow_mut()
|
|
.focus(xev.event)
|
|
.expect("Failed to focus input context");
|
|
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: Focused(true),
|
|
});
|
|
|
|
let modifiers = ModifiersState::from_x11(&xev.mods);
|
|
|
|
update_modifiers!(modifiers, None);
|
|
|
|
// The deviceid for this event is for a keyboard instead of a pointer,
|
|
// so we have to do a little extra work.
|
|
let pointer_id = self
|
|
.devices
|
|
.borrow()
|
|
.get(&DeviceId(xev.deviceid))
|
|
.map(|device| device.attachment)
|
|
.unwrap_or(2);
|
|
|
|
let position = LogicalPosition::from_physical(
|
|
(xev.event_x as f64, xev.event_y as f64),
|
|
dpi_factor,
|
|
);
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: CursorMoved {
|
|
device_id: mkdid(pointer_id),
|
|
position,
|
|
modifiers,
|
|
},
|
|
});
|
|
|
|
// Issue key press events for all pressed keys
|
|
self.handle_pressed_keys(window_id, ElementState::Pressed, &mut callback);
|
|
}
|
|
ffi::XI_FocusOut => {
|
|
let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) };
|
|
if !self.window_exists(xev.event) {
|
|
return;
|
|
}
|
|
wt.ime
|
|
.borrow_mut()
|
|
.unfocus(xev.event)
|
|
.expect("Failed to unfocus input context");
|
|
|
|
let window_id = mkwid(xev.event);
|
|
|
|
// Issue key release events for all pressed keys
|
|
self.handle_pressed_keys(window_id, ElementState::Released, &mut callback);
|
|
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: Focused(false),
|
|
})
|
|
}
|
|
|
|
ffi::XI_TouchBegin | ffi::XI_TouchUpdate | ffi::XI_TouchEnd => {
|
|
let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) };
|
|
let window_id = mkwid(xev.event);
|
|
let phase = match xev.evtype {
|
|
ffi::XI_TouchBegin => TouchPhase::Started,
|
|
ffi::XI_TouchUpdate => TouchPhase::Moved,
|
|
ffi::XI_TouchEnd => TouchPhase::Ended,
|
|
_ => unreachable!(),
|
|
};
|
|
let dpi_factor =
|
|
self.with_window(xev.event, |window| window.hidpi_factor());
|
|
if let Some(dpi_factor) = dpi_factor {
|
|
let id = xev.detail as u64;
|
|
let modifiers = self.device_mod_state.modifiers();
|
|
let location = LogicalPosition::from_physical(
|
|
(xev.event_x as f64, xev.event_y as f64),
|
|
dpi_factor,
|
|
);
|
|
|
|
// Mouse cursor position changes when touch events are received.
|
|
// Only the first concurrently active touch ID moves the mouse cursor.
|
|
if is_first_touch(&mut self.first_touch, &mut self.num_touch, id, phase)
|
|
{
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: WindowEvent::CursorMoved {
|
|
device_id: mkdid(util::VIRTUAL_CORE_POINTER),
|
|
position: location,
|
|
modifiers,
|
|
},
|
|
});
|
|
}
|
|
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: WindowEvent::Touch(Touch {
|
|
device_id: mkdid(xev.deviceid),
|
|
phase,
|
|
location,
|
|
force: None, // TODO
|
|
id,
|
|
}),
|
|
})
|
|
}
|
|
}
|
|
|
|
ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => {
|
|
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
|
|
if xev.flags & ffi::XIPointerEmulated == 0 {
|
|
callback(Event::DeviceEvent {
|
|
device_id: mkdid(xev.deviceid),
|
|
event: DeviceEvent::Button {
|
|
button: xev.detail as u32,
|
|
state: match xev.evtype {
|
|
ffi::XI_RawButtonPress => Pressed,
|
|
ffi::XI_RawButtonRelease => Released,
|
|
_ => unreachable!(),
|
|
},
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
ffi::XI_RawMotion => {
|
|
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
|
|
let did = mkdid(xev.deviceid);
|
|
|
|
let mask = unsafe {
|
|
slice::from_raw_parts(
|
|
xev.valuators.mask,
|
|
xev.valuators.mask_len as usize,
|
|
)
|
|
};
|
|
let mut value = xev.raw_values;
|
|
let mut mouse_delta = (0.0, 0.0);
|
|
let mut scroll_delta = (0.0, 0.0);
|
|
for i in 0..xev.valuators.mask_len * 8 {
|
|
if ffi::XIMaskIsSet(mask, i) {
|
|
let x = unsafe { *value };
|
|
// We assume that every XInput2 device with analog axes is a pointing device emitting
|
|
// relative coordinates.
|
|
match i {
|
|
0 => mouse_delta.0 = x,
|
|
1 => mouse_delta.1 = x,
|
|
2 => scroll_delta.0 = x as f32,
|
|
3 => scroll_delta.1 = x as f32,
|
|
_ => {}
|
|
}
|
|
callback(Event::DeviceEvent {
|
|
device_id: did,
|
|
event: DeviceEvent::Motion {
|
|
axis: i as u32,
|
|
value: x,
|
|
},
|
|
});
|
|
value = unsafe { value.offset(1) };
|
|
}
|
|
}
|
|
if mouse_delta != (0.0, 0.0) {
|
|
callback(Event::DeviceEvent {
|
|
device_id: did,
|
|
event: DeviceEvent::MouseMotion { delta: mouse_delta },
|
|
});
|
|
}
|
|
if scroll_delta != (0.0, 0.0) {
|
|
callback(Event::DeviceEvent {
|
|
device_id: did,
|
|
event: DeviceEvent::MouseWheel {
|
|
delta: LineDelta(scroll_delta.0, scroll_delta.1),
|
|
},
|
|
});
|
|
}
|
|
}
|
|
|
|
ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => {
|
|
let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) };
|
|
|
|
let state = match xev.evtype {
|
|
ffi::XI_RawKeyPress => Pressed,
|
|
ffi::XI_RawKeyRelease => Released,
|
|
_ => unreachable!(),
|
|
};
|
|
|
|
let device_id = mkdid(xev.sourceid);
|
|
let keycode = xev.detail;
|
|
if keycode < 8 {
|
|
return;
|
|
}
|
|
let scancode = (keycode - 8) as u32;
|
|
let keysym = wt.xconn.keycode_to_keysym(keycode as ffi::KeyCode);
|
|
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
|
let modifiers = self.device_mod_state.modifiers();
|
|
|
|
callback(Event::DeviceEvent {
|
|
device_id,
|
|
event: DeviceEvent::Key(KeyboardInput {
|
|
scancode,
|
|
virtual_keycode,
|
|
state,
|
|
modifiers,
|
|
}),
|
|
});
|
|
|
|
if let Some(modifier) =
|
|
self.mod_keymap.get_modifier(keycode as ffi::KeyCode)
|
|
{
|
|
self.device_mod_state.key_event(
|
|
state,
|
|
keycode as ffi::KeyCode,
|
|
modifier,
|
|
);
|
|
|
|
let new_modifiers = self.device_mod_state.modifiers();
|
|
|
|
if modifiers != new_modifiers {
|
|
callback(Event::DeviceEvent {
|
|
device_id,
|
|
event: DeviceEvent::ModifiersChanged {
|
|
modifiers: new_modifiers,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
ffi::XI_HierarchyChanged => {
|
|
let xev: &ffi::XIHierarchyEvent = unsafe { &*(xev.data as *const _) };
|
|
for info in
|
|
unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) }
|
|
{
|
|
if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) {
|
|
self.init_device(info.deviceid);
|
|
callback(Event::DeviceEvent {
|
|
device_id: mkdid(info.deviceid),
|
|
event: DeviceEvent::Added,
|
|
});
|
|
} else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved)
|
|
{
|
|
callback(Event::DeviceEvent {
|
|
device_id: mkdid(info.deviceid),
|
|
event: DeviceEvent::Removed,
|
|
});
|
|
let mut devices = self.devices.borrow_mut();
|
|
devices.remove(&DeviceId(info.deviceid));
|
|
}
|
|
}
|
|
}
|
|
|
|
_ => {}
|
|
}
|
|
}
|
|
_ => {
|
|
if event_type == self.randr_event_offset {
|
|
// In the future, it would be quite easy to emit monitor hotplug events.
|
|
let prev_list = monitor::invalidate_cached_monitor_list();
|
|
if let Some(prev_list) = prev_list {
|
|
let new_list = wt.xconn.available_monitors();
|
|
for new_monitor in new_list {
|
|
prev_list
|
|
.iter()
|
|
.find(|prev_monitor| prev_monitor.name == new_monitor.name)
|
|
.map(|prev_monitor| {
|
|
if new_monitor.hidpi_factor != prev_monitor.hidpi_factor {
|
|
for (window_id, window) in wt.windows.borrow().iter() {
|
|
if let Some(window) = window.upgrade() {
|
|
// Check if the window is on this monitor
|
|
let monitor = window.current_monitor();
|
|
if monitor.name == new_monitor.name {
|
|
callback(Event::WindowEvent {
|
|
window_id: mkwid(window_id.0),
|
|
event: WindowEvent::HiDpiFactorChanged(
|
|
new_monitor.hidpi_factor,
|
|
),
|
|
});
|
|
let (width, height) =
|
|
window.inner_size_physical();
|
|
let (_, _, flusher) = window.adjust_for_dpi(
|
|
prev_monitor.hidpi_factor,
|
|
new_monitor.hidpi_factor,
|
|
width as f64,
|
|
height as f64,
|
|
);
|
|
flusher.queue();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
match self.ime_receiver.try_recv() {
|
|
Ok((window_id, x, y)) => {
|
|
wt.ime.borrow_mut().send_xim_spot(window_id, x, y);
|
|
}
|
|
Err(_) => (),
|
|
}
|
|
}
|
|
|
|
fn handle_pressed_keys<F>(
|
|
&self,
|
|
window_id: crate::window::WindowId,
|
|
state: ElementState,
|
|
callback: &mut F,
|
|
) where
|
|
F: FnMut(Event<T>),
|
|
{
|
|
let wt = get_xtarget(&self.target);
|
|
|
|
let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD);
|
|
let modifiers = self.device_mod_state.modifiers();
|
|
|
|
// Get the set of keys currently pressed and apply Key events to each
|
|
let keys = wt.xconn.query_keymap();
|
|
|
|
for keycode in &keys {
|
|
if keycode < 8 {
|
|
continue;
|
|
}
|
|
|
|
let scancode = (keycode - 8) as u32;
|
|
let keysym = wt.xconn.keycode_to_keysym(keycode);
|
|
let virtual_keycode = events::keysym_to_element(keysym as c_uint);
|
|
|
|
callback(Event::WindowEvent {
|
|
window_id,
|
|
event: WindowEvent::KeyboardInput {
|
|
device_id,
|
|
input: KeyboardInput {
|
|
scancode,
|
|
state,
|
|
virtual_keycode,
|
|
modifiers,
|
|
},
|
|
is_synthetic: true,
|
|
},
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
fn is_first_touch(first: &mut Option<u64>, num: &mut u32, id: u64, phase: TouchPhase) -> bool {
|
|
match phase {
|
|
TouchPhase::Started => {
|
|
if *num == 0 {
|
|
*first = Some(id);
|
|
}
|
|
*num += 1;
|
|
}
|
|
TouchPhase::Cancelled | TouchPhase::Ended => {
|
|
if *first == Some(id) {
|
|
*first = None;
|
|
}
|
|
*num = num.saturating_sub(1);
|
|
}
|
|
_ => (),
|
|
}
|
|
|
|
*first == Some(id)
|
|
}
|