diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs new file mode 100644 index 000000000..9b27fff0a --- /dev/null +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -0,0 +1,1010 @@ +use std::cell::RefCell; +use std::collections::HashMap; +use std::ptr; +use std::rc::Rc; +use std::slice; + +use libc::{c_int, c_uint, c_ulong, c_char, c_long}; + +use super::{ + mkdid, mkwid, get_xtarget, DeviceId, WindowId, Device, ImeReceiver, XExtension, + monitor, ffi, UnownedWindow, ScrollOrientation, GenericEventCookie, + events, util, DndState, Dnd, DeviceInfo +}; + +use event_loop::EventLoopWindowTarget as RootELW; +use event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent}; +use dpi::{LogicalPosition,LogicalSize}; + +pub(super) struct EventProcessor { + pub(super) dnd: Dnd, + pub(super) ime_receiver: ImeReceiver, + pub(super) randr_event_offset: c_int, + pub(super) devices: RefCell>, + pub(super) xi2ext: XExtension, + pub(super) target: Rc> +} + +impl EventProcessor { + 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(&self, window_id: ffi::Window, callback: F) -> Option + 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) 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(&mut self, xev: &mut ffi::XEvent, mut callback: F) + where F: FnMut(Event) + { + 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; + } + + let event_type = xev.get_type(); + match event_type { + ffi::MappingNotify => { + unsafe { (wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); } + wt.xconn.check_errors().expect("Failed to call XRefreshKeyboardMapping"); + } + + 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.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::() * 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::() * 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, + moved: Option, + dpi_changed: Option, + } + + 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.get_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.guessed_dpi + .take() + .unwrap_or_else(|| { + shared_state_lock.last_monitor + .as_ref() + .map(|last_monitor| last_monitor.hidpi_factor) + .unwrap_or(1.0) + }); + let new_hidpi_factor = { + let window_rect = util::AaRect::new(new_outer_position, new_inner_size); + monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); + let new_hidpi_factor = monitor.hidpi_factor; + shared_state_lock.last_monitor = Some(monitor.clone()); + new_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::Expose => { + let xev: &ffi::XExposeEvent = xev.as_ref(); + + let window = xev.window; + let window_id = mkwid(window); + + callback(Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested }); + } + + ffi::KeyPress | ffi::KeyRelease => { + use 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); + + // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with + // a keycode of 0. + if xkev.keycode != 0 { + 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; + (wt.xconn.xlib.XLookupString)( + xkev, + ptr::null_mut(), + 0, + &mut keysym, + ptr::null_mut(), + ); + wt.xconn.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 { + 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 event::WindowEvent::{Focused, CursorEntered, MouseInput, CursorLeft, CursorMoved, MouseWheel, AxisMotion}; + use event::ElementState::{Pressed, Released}; + use event::MouseButton::{Left, Right, Middle, Other}; + use event::MouseScrollDelta::LineDelta; + use event::{Touch, TouchPhase}; + + 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. + let return_now = self + .with_window(xev.event, |window| window.multitouch) + .unwrap_or(true); + if return_now { return; } + } + + let modifiers = ModifiersState::from(xev.mods); + + 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(xev.mods); + + 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.get_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); + } + } + } + } + callback(Event::WindowEvent { + window_id, + event: CursorEntered { device_id }, + }); + + if let Some(dpi_factor) = self.with_window(xev.event, |window| { + window.get_hidpi_factor() + }) { + 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.get_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) }); + + // 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: ModifiersState::from(xev.mods), + } + }); + } + 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"); + callback(Event::WindowEvent { + window_id: mkwid(xev.event), + 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.get_hidpi_factor() + }); + if let Some(dpi_factor) = dpi_factor { + let location = LogicalPosition::from_physical( + (xev.event_x as f64, xev.event_y as f64), + dpi_factor, + ); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Touch(Touch { + device_id: mkdid(xev.deviceid), + phase, + location, + id: xev.detail as u64, + }), + }) + } + } + + 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 = xev.sourceid; + let keycode = xev.detail; + if keycode < 8 { return; } + let scancode = (keycode - 8) as u32; + + let keysym = unsafe { + (wt.xconn.xlib.XKeycodeToKeysym)( + wt.xconn.display, + xev.detail as ffi::KeyCode, + 0, + ) + }; + wt.xconn.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 => { + 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.get_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.get_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) = match window.get_inner_size_physical() { + Some(result) => result, + None => continue, + }; + 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(_) => (), + } + } +} \ No newline at end of file diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index e3a9352f0..96f39fa15 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -8,36 +8,30 @@ mod xdisplay; mod dnd; mod ime; pub mod util; +mod event_processor; pub use self::monitor::MonitorHandle; pub use self::window::UnownedWindow; pub use self::xdisplay::{XConnection, XNotSupported, XError}; -use std::{mem, ptr, slice}; +use std::{mem, slice}; use std::cell::RefCell; -use std::collections::HashMap; +use std::collections::{VecDeque, HashMap}; use std::ffi::CStr; use std::ops::Deref; use std::os::raw::*; -use libc::{select, fd_set, FD_SET, FD_ZERO, FD_ISSET, EINTR, EINVAL, ENOMEM, EBADF}; -#[cfg(target_os = "linux")] -use libc::__errno_location; -#[cfg(target_os = "freebsd")] -use libc::__error as __errno_location; -#[cfg(any(target_os = "netbsd", target_os = "openbsd"))] -use libc::__errno as __errno_location; -use std::sync::{Arc, mpsc, Weak}; -use std::sync::atomic::{self, AtomicBool}; +use std::rc::Rc; +use std::sync::{Arc, mpsc, Weak, Mutex}; use libc::{self, setlocale, LC_CTYPE}; use event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}; -use event::{DeviceEvent, Event, KeyboardInput, ModifiersState, WindowEvent}; +use event::{WindowEvent, Event}; use platform_impl::PlatformSpecificWindowBuilderAttributes; -use dpi::{LogicalPosition,LogicalSize}; use window::{CreationError, WindowAttributes}; use self::dnd::{Dnd, DndState}; use self::ime::{ImeReceiver, ImeSender, ImeCreationError, Ime}; +use self::event_processor::EventProcessor; pub struct EventLoopWindowTarget { xconn: Arc, @@ -46,28 +40,23 @@ pub struct EventLoopWindowTarget { root: ffi::Window, ime: RefCell, windows: RefCell>>, + pending_redraws: Arc>>, _marker: ::std::marker::PhantomData } pub struct EventLoop { - dnd: Dnd, - ime_receiver: ImeReceiver, - randr_event_offset: c_int, - devices: RefCell>, - xi2ext: XExtension, - pending_wakeup: Arc, - // A dummy, `InputOnly` window that we can use to receive wakeup events and interrupt blocking - // `XNextEvent` calls. - wakeup_dummy_window: ffi::Window, - target: RootELW + inner_loop: ::calloop::EventLoop<()>, + _x11_source: ::calloop::Source<::calloop::generic::Generic<::calloop::generic::EventedRawFd>>, + _user_source: ::calloop::Source<::calloop::channel::Channel>, + pending_user_events: Rc>>, + user_sender: ::calloop::channel::Sender, + pending_events: Rc>>>, + target: Rc> } #[derive(Clone)] pub struct EventLoopProxy { - pending_wakeup: Weak, - xconn: Weak, - wakeup_dummy_window: ffi::Window, - _marker: ::std::marker::PhantomData + user_sender: ::calloop::channel::Sender, } impl EventLoop { @@ -130,53 +119,88 @@ impl EventLoop { xconn.update_cached_wm_info(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); - (xconn.xlib.XCreateSimpleWindow)( - xconn.display, + let target = Rc::new(RootELW{ + p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { + ime, root, - x, - y, - w, - h, - border_w, - border_px, - background_px, - ) - }; + windows: Default::default(), + _marker: ::std::marker::PhantomData, + ime_sender, + xconn, + wm_delete_window, + pending_redraws: Default::default(), + }), + _marker: ::std::marker::PhantomData + }); - let result = EventLoop { - dnd, - ime_receiver, - randr_event_offset, - devices: Default::default(), - xi2ext, - pending_wakeup: Default::default(), - wakeup_dummy_window, - target: RootELW{ - p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { - ime, - root, - windows: Default::default(), - _marker: ::std::marker::PhantomData, - ime_sender, - xconn, - wm_delete_window, - }), - _marker: ::std::marker::PhantomData + // A calloop event loop to drive us + let inner_loop = ::calloop::EventLoop::new().unwrap(); + + // Handle user events + let pending_user_events = Rc::new(RefCell::new(VecDeque::new())); + let pending_user_events2 = pending_user_events.clone(); + + let (user_sender, user_channel) = ::calloop::channel::channel(); + + let _user_source = inner_loop.handle().insert_source(user_channel, move |evt, &mut()| { + if let ::calloop::channel::Event::Msg(msg) = evt { + pending_user_events2.borrow_mut().push_back(msg); } + }).unwrap(); + + // Handle X11 events + let pending_events: Rc>> = Default::default(); + + let mut processor = EventProcessor { + target: target.clone(), + dnd, + devices: Default::default(), + randr_event_offset, + ime_receiver, + xi2ext, }; // Register for device hotplug events // (The request buffer is flushed during `init_device`) - get_xtarget(&result.target).xconn.select_xinput_events( + get_xtarget(&target).xconn.select_xinput_events( root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask, ).queue(); - result.init_device(ffi::XIAllDevices); + processor.init_device(ffi::XIAllDevices); + + // Setup the X11 event source + let mut x11_events = ::calloop::generic::Generic::from_raw_fd(get_xtarget(&target).xconn.x11_fd); + x11_events.set_interest(::calloop::mio::Ready::readable()); + let _x11_source = inner_loop.handle().insert_source( + x11_events, + { + let pending_events = pending_events.clone(); + let mut callback = move |event| { + pending_events.borrow_mut().push_back(event); + }; + move |evt, &mut ()| { + if evt.readiness.is_readable() { + // process all pending events + let mut xev = unsafe { mem::uninitialized() }; + while unsafe { processor.poll_one_event(&mut xev) } { + processor.process_event(&mut xev, &mut callback); + } + } + } + } + ).unwrap(); + + let result = EventLoop { + inner_loop, + pending_events, + _x11_source, + _user_source, + user_sender, + pending_user_events, + target + }; result } @@ -188,1042 +212,11 @@ impl EventLoop { } pub fn create_proxy(&self) -> EventLoopProxy { - let wt = get_xtarget(&self.target); EventLoopProxy { - pending_wakeup: Arc::downgrade(&self.pending_wakeup), - xconn: Arc::downgrade(&wt.xconn), - wakeup_dummy_window: self.wakeup_dummy_window, - _marker: ::std::marker::PhantomData + user_sender: self.user_sender.clone(), } } - 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 - } - - unsafe fn wait_for_input(&mut self) { - let wt = get_xtarget(&self.target); - // XNextEvent can not be used in multi-threaded applications - // because it is blocking for input while holding the global - // Xlib mutex. - // To work around this issue, first flush the X11 display, then - // use select(2) to wait for input to arrive - loop { - // First use XFlush to flush any buffered x11 requests - (wt.xconn.xlib.XFlush)(wt.xconn.display); - - // Then use select(2) to wait for input data - let mut fds : fd_set = mem::uninitialized(); - FD_ZERO(&mut fds); - FD_SET(wt.xconn.x11_fd, &mut fds); - let err = select( - wt.xconn.x11_fd + 1, - &mut fds, // read fds - std::ptr::null_mut(), // write fds - std::ptr::null_mut(), // except fds (could be used to detect errors) - std::ptr::null_mut()); // timeout - - if err < 0 { - let errno_ptr = __errno_location(); - let errno = *errno_ptr; - - if errno == EINTR { - // try again if errno is EINTR - continue; - } - - assert!(errno == EBADF || errno == EINVAL || errno == ENOMEM); - panic!("select(2) returned fatal error condition"); - } - - if FD_ISSET(wt.xconn.x11_fd, &mut fds) { - break; - } - } - } - - fn process_event(&mut self, xev: &mut ffi::XEvent, mut callback: F) - where F: FnMut(Event) - { - 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; - } - - let event_type = xev.get_type(); - match event_type { - ffi::MappingNotify => { - unsafe { (wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); } - wt.xconn.check_errors().expect("Failed to call XRefreshKeyboardMapping"); - } - - 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.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::() * 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::() * 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, - }); - } else if self.pending_wakeup.load(atomic::Ordering::Relaxed) { - self.pending_wakeup.store(false, atomic::Ordering::Relaxed); - // TODO: User event - // callback(Event::Awakened); - } - } - - 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, - moved: Option, - dpi_changed: Option, - } - - 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.get_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.guessed_dpi - .take() - .unwrap_or_else(|| { - shared_state_lock.last_monitor - .as_ref() - .map(|last_monitor| last_monitor.hidpi_factor) - .unwrap_or(1.0) - }); - let new_hidpi_factor = { - let window_rect = util::AaRect::new(new_outer_position, new_inner_size); - monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); - let new_hidpi_factor = monitor.hidpi_factor; - shared_state_lock.last_monitor = Some(monitor.clone()); - new_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::Expose => { - let xev: &ffi::XExposeEvent = xev.as_ref(); - - let window = xev.window; - let window_id = mkwid(window); - - callback(Event::WindowEvent { window_id, event: WindowEvent::RedrawRequested }); - } - - ffi::KeyPress | ffi::KeyRelease => { - use 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); - - // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with - // a keycode of 0. - if xkev.keycode != 0 { - 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; - (wt.xconn.xlib.XLookupString)( - xkev, - ptr::null_mut(), - 0, - &mut keysym, - ptr::null_mut(), - ); - wt.xconn.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 { - 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 event::WindowEvent::{Focused, CursorEntered, MouseInput, CursorLeft, CursorMoved, MouseWheel, AxisMotion}; - use event::ElementState::{Pressed, Released}; - use event::MouseButton::{Left, Right, Middle, Other}; - use event::MouseScrollDelta::LineDelta; - use event::{Touch, TouchPhase}; - - 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. - let return_now = self - .with_window(xev.event, |window| window.multitouch) - .unwrap_or(true); - if return_now { return; } - } - - let modifiers = ModifiersState::from(xev.mods); - - 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(xev.mods); - - 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.get_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); - } - } - } - } - callback(Event::WindowEvent { - window_id, - event: CursorEntered { device_id }, - }); - - if let Some(dpi_factor) = self.with_window(xev.event, |window| { - window.get_hidpi_factor() - }) { - 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.get_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) }); - - // 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: ModifiersState::from(xev.mods), - } - }); - } - 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"); - callback(Event::WindowEvent { - window_id: mkwid(xev.event), - 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.get_hidpi_factor() - }); - if let Some(dpi_factor) = dpi_factor { - let location = LogicalPosition::from_physical( - (xev.event_x as f64, xev.event_y as f64), - dpi_factor, - ); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Touch(Touch { - device_id: mkdid(xev.deviceid), - phase, - location, - id: xev.detail as u64, - }), - }) - } - } - - 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 = xev.sourceid; - let keycode = xev.detail; - if keycode < 8 { return; } - let scancode = (keycode - 8) as u32; - - let keysym = unsafe { - (wt.xconn.xlib.XKeycodeToKeysym)( - wt.xconn.display, - xev.detail as ffi::KeyCode, - 0, - ) - }; - wt.xconn.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 => { - 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.get_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.get_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) = match window.get_inner_size_physical() { - Some(result) => result, - None => continue, - }; - 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 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(&self, window_id: ffi::Window, callback: F) -> Option - 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(crate) fn window_target(&self) -> &RootELW { &self.target } @@ -1232,18 +225,89 @@ impl EventLoop { where F: FnMut(Event, &RootELW, &mut ControlFlow) { let mut control_flow = ControlFlow::default(); + let wt = get_xtarget(&self.target); loop { + // Empty the event buffer + { + let mut guard = self.pending_events.borrow_mut(); + for evt in guard.drain(..) { + callback(evt, &self.target, &mut control_flow); + } + } + // Empty the user event buffer + { + let mut guard = self.pending_user_events.borrow_mut(); + for evt in guard.drain(..) { + callback(::event::Event::UserEvent(evt), &self.target, &mut control_flow); + } + } + + callback(::event::Event::EventsCleared, &self.target, &mut control_flow); + // Empty th redraw requests + { + let mut guard = wt.pending_redraws.lock().unwrap(); + for wid in guard.drain(..) { + callback( + Event::WindowEvent { + window_id: ::window::WindowId(super::WindowId::X(wid)), + event: WindowEvent::RedrawRequested + }, + &self.target, + &mut control_flow + ); + } + } + + // flush the X11 connection + unsafe { (wt.xconn.xlib.XFlush)(wt.xconn.display); } + match control_flow { ControlFlow::Exit => return, ControlFlow::Poll => { - + // non-blocking dispatch + self.inner_loop.dispatch(Some(::std::time::Duration::from_millis(0)), &mut ()).unwrap(); + control_flow = ControlFlow::default(); + callback(::event::Event::NewEvents(::event::StartCause::Poll), &self.target, &mut control_flow); }, ControlFlow::Wait => { - + self.inner_loop.dispatch(None, &mut ()).unwrap(); + control_flow = ControlFlow::default(); + callback( + ::event::Event::NewEvents(::event::StartCause::WaitCancelled { + start: ::std::time::Instant::now(), + requested_resume: None + }), + &self.target, + &mut control_flow + ); }, ControlFlow::WaitUntil(deadline) => { - + let start = ::std::time::Instant::now(); + // compute the blocking duration + let duration = deadline.duration_since(::std::cmp::max(deadline, start)); + self.inner_loop.dispatch(Some(duration), &mut ()).unwrap(); + control_flow = ControlFlow::default(); + let now = std::time::Instant::now(); + if now < deadline { + callback( + ::event::Event::NewEvents(::event::StartCause::WaitCancelled { + start, + requested_resume: Some(deadline) + }), + &self.target, + &mut control_flow + ); + } else { + callback( + ::event::Event::NewEvents(::event::StartCause::ResumeTimeReached { + start, + requested_resume: deadline + }), + &self.target, + &mut control_flow + ); + } } } } @@ -1255,55 +319,6 @@ impl EventLoop { self.run_return(callback); ::std::process::exit(0); } - -/* - pub fn poll_events(&mut self, mut callback: F) - where F: FnMut(Event) - { - let mut xev = unsafe { mem::uninitialized() }; - loop { - // Get next event - unsafe { - if !self.poll_one_event(&mut xev) { - break; - } - } - self.process_event(&mut xev, &mut callback); - } - } - - pub fn run_forever(&mut self, mut callback: F) - where F: FnMut(Event) -> ControlFlow - { - let mut xev = unsafe { mem::uninitialized() }; - - loop { - unsafe { - while !self.poll_one_event(&mut xev) { - // block until input is available - self.wait_for_input(); - } - }; - - let mut control_flow = ControlFlow::Poll; - - // Track whether or not `Break` was returned when processing the event. - { - let mut cb = |event| { - if let ControlFlow::Exit = callback(event) { - control_flow = ControlFlow::Exit; - } - }; - - self.process_event(&mut xev, &mut cb); - } - - if let ControlFlow::Exit = control_flow { - break; - } - } - } -*/ } fn get_xtarget(rt: &RootELW) -> &EventLoopWindowTarget { @@ -1315,34 +330,8 @@ fn get_xtarget(rt: &RootELW) -> &EventLoopWindowTarget { } impl EventLoopProxy { - pub fn send_event(&self, evt: T) -> Result<(), EventLoopClosed> { - unimplemented!(); - } - - pub fn wakeup(&self) -> Result<(), EventLoopClosed> { - // Update the `EventLoop`'s `pending_wakeup` flag. - let display = match (self.pending_wakeup.upgrade(), self.xconn.upgrade()) { - (Some(wakeup), Some(display)) => { - wakeup.store(true, atomic::Ordering::Relaxed); - display - }, - _ => return Err(EventLoopClosed), - }; - - // Push an event on the X event queue so that methods run_forever will advance. - // - // 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) - display.send_client_msg( - self.wakeup_dummy_window, - self.wakeup_dummy_window, - 0, - None, - [0, 0, 0, 0, 0], - ).flush().expect("Failed to call XSendEvent after wakeup"); - - Ok(()) + pub fn send_event(&self, event: T) -> Result<(), EventLoopClosed> { + self.user_sender.send(event).map_err(|_| EventLoopClosed) } } @@ -1503,7 +492,7 @@ enum ScrollOrientation { } impl Device { - fn new(el: &EventLoop, info: &ffi::XIDeviceInfo) -> Self { + fn new(el: &EventProcessor, info: &ffi::XIDeviceInfo) -> Self { let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; let mut scroll_axes = Vec::new(); diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index af6556a60..435bfe412 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1,4 +1,5 @@ use std::{cmp, env, mem}; +use std::collections::VecDeque; use std::ffi::CString; use std::os::raw::*; use std::path::Path; @@ -66,6 +67,7 @@ pub struct UnownedWindow { ime_sender: Mutex, pub multitouch: bool, // never changes pub shared_state: Mutex, + pending_redraws: Arc<::std::sync::Mutex>>, } impl UnownedWindow { @@ -203,6 +205,7 @@ impl UnownedWindow { ime_sender: Mutex::new(event_loop.ime_sender.clone()), multitouch: window_attrs.multitouch, shared_state: SharedState::new(dpi_factor), + pending_redraws: event_loop.pending_redraws.clone(), }; // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window @@ -1213,6 +1216,6 @@ impl UnownedWindow { #[inline] pub fn request_redraw(&self) { - unimplemented!(); + self.pending_redraws.lock().unwrap().push_back(WindowId(self.xwindow)); } }