From 06b511a7bd910b289cc3577469e157493429acd3 Mon Sep 17 00:00:00 2001 From: jtnunley Date: Fri, 27 Jan 2023 14:03:28 -0800 Subject: [PATCH] Implement partial IME support --- Cargo.toml | 3 +- .../linux/x11/event_processor.rs | 1927 +++++++++-------- src/platform_impl/linux/x11/ime.rs | 495 +++++ src/platform_impl/linux/x11/mod.rs | 4 +- src/platform_impl/linux/x11/xdisplay.rs | 42 +- 5 files changed, 1506 insertions(+), 965 deletions(-) create mode 100644 src/platform_impl/linux/x11/ime.rs diff --git a/Cargo.toml b/Cargo.toml index b10ef411bc1..06250788452 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,7 +36,7 @@ rustdoc-args = ["--cfg", "docsrs"] [features] default = ["x11", "wayland", "wayland-dlopen", "wayland-csd-adwaita"] -x11 = ["bytemuck", "x11-dl", "x11rb", "mio", "percent-encoding"] +x11 = ["bytemuck", "x11-dl", "x11rb", "mio", "percent-encoding", "xim"] wayland = ["wayland-client", "wayland-protocols", "sctk", "wayland-commons"] wayland-dlopen = ["sctk/dlopen", "wayland-client/dlopen"] wayland-csd-adwaita = ["sctk-adwaita", "sctk-adwaita/ab_glyph"] @@ -115,6 +115,7 @@ wayland-protocols = { version = "0.29.5", features = [ "staging_protocols"], opt wayland-commons = { version = "0.29.5", optional = true } x11-dl = { version = "2.18.5", optional = true } x11rb = { version = "0.11.0", features = ["allow-unsafe-code", "dl-libxcb", "randr", "resource_manager", "xinput", "xkb"], optional = true } +xim = { version = "0.2.1", features = ["client", "x11rb-client", "x11rb-xcb"], optional = true } [target.'cfg(target_os = "redox")'.dependencies] orbclient = { version = "0.3.42", default-features = false } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 2705143efd9..5e288b48978 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1,9 +1,4 @@ -use std::{ - cell::RefCell, - collections::{HashMap, VecDeque}, - rc::Rc, - sync::Arc, -}; +use std::{cell::RefCell, collections::HashMap, rc::Rc, sync::Arc}; use x11rb::{ connection::Connection, errors::ConnectionError, @@ -60,9 +55,6 @@ pub(super) struct EventProcessor { // Currently focused window belonging to this process pub(super) active_window: Option, pub(super) is_composing: bool, - - /// A queue containing events that we have read from the X server but have not yet processed. - pub(super) event_queue: VecDeque, } impl EventProcessor { @@ -104,9 +96,12 @@ impl EventProcessor { /// /// Returns `true` if there are events in the queue, `false` otherwise. pub(super) fn poll(&mut self) -> bool { + let wt = get_xtarget(&self.target); + let mut event_queue = wt.xconn.event_queue.lock().unwrap(); + loop { // If we have events in the queue, we need to process them. - if !self.event_queue.is_empty() { + if !event_queue.is_empty() { return true; } @@ -114,7 +109,7 @@ impl EventProcessor { let wt = get_xtarget(&self.target); match wt.xconn.connection.poll_for_event() { Ok(Some(event)) => { - self.event_queue.push_back(event); + event_queue.push_back(event); } Ok(None) => { // No events in the queue, and no events on the connection. @@ -134,12 +129,13 @@ impl EventProcessor { } pub(super) fn poll_one_event(&mut self) -> Result, ConnectionError> { + let wt = get_xtarget(&self.target); + // If we previously polled and found an event, return it. - if let Some(event) = self.event_queue.pop_front() { + if let Some(event) = wt.xconn.event_queue.lock().unwrap().pop_front() { return Ok(Some(event)); } - let wt = get_xtarget(&self.target); wt.xconn.connection.poll_for_event() } @@ -149,7 +145,13 @@ impl EventProcessor { { let wt = get_xtarget(&self.target); - // TODO: Filter IME events. + // Filter out any IME events. + let mut skip_event = false; + if let Some(ime_data) = wt.xconn.ime_data.as_ref() { + if ime_data.filter_event(xev, &wt.xconn) { + skip_event = true; + } + } // We can't call a `&mut self` method because of the above borrow, // so we use this macro for repeated modifier state updates. @@ -174,1105 +176,1120 @@ impl EventProcessor { let is_synthetic = xev.sent_event(); - match xev { - X11Event::MappingNotify(mapping) => { - if matches!(mapping.request, Mapping::MODIFIER | Mapping::KEYBOARD) { - // TODO: IME - - // Update modifier keymap. - self.mod_keymap - .reset_from_x_connection(&wt.xconn) - .expect("Failed to update modifier keymap"); - self.device_mod_state.update_keymap(&self.mod_keymap); + if !skip_event { + match xev { + X11Event::MappingNotify(mapping) => { + if matches!(mapping.request, Mapping::MODIFIER | Mapping::KEYBOARD) { + // TODO: IME + + // Update modifier keymap. + self.mod_keymap + .reset_from_x_connection(&wt.xconn) + .expect("Failed to update modifier keymap"); + self.device_mod_state.update_keymap(&self.mod_keymap); + } } - } - X11Event::ClientMessage(mut client_msg) => { - let window = client_msg.window; - let window_id = mkwid(window); - - let atom = client_msg.data.as_data32()[0] as xproto::Atom; - if atom == wt.xconn.atoms[WM_DELETE_WINDOW] { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::CloseRequested, - }); - } else if atom == wt.xconn.atoms[_NET_WM_PING] { - client_msg.window = wt.root; - wt.xconn - .connection - .send_event( - false, - wt.root, - xproto::EventMask::SUBSTRUCTURE_NOTIFY - | xproto::EventMask::SUBSTRUCTURE_REDIRECT, - client_msg, - ) - .expect("Failed to send ping event") - .ignore_error(); - } else if client_msg.type_ == wt.xconn.atoms[XdndEnter] { - let longs = client_msg.data.as_data32(); - let source_window = longs[0] as xproto::Window; - let flags = longs[1]; - let version = flags >> 24; - self.dnd.version = Some(version); - let has_more_types = flags - (flags & (u32::max_value() - 1)) == 1; - if !has_more_types { - let type_list = vec![longs[2], longs[3], longs[4]]; - self.dnd.type_list = Some(type_list); - } else if let Ok(more_types) = self.dnd.get_type_list(source_window) { - self.dnd.type_list = Some(more_types); - } - } else if client_msg.type_ == wt.xconn.atoms[XdndPosition] { - // 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 longs = client_msg.data.as_data32(); - let source_window = longs[0]; - - // 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(&wt.xconn.atoms[TextUriList]) - } else { - false - }; + X11Event::ClientMessage(mut client_msg) => { + let window = client_msg.window; + let window_id = mkwid(window); - if accepted { - self.dnd.source_window = Some(source_window); - { - if self.dnd.result.is_none() { - let time = if version >= 1 { - longs[3] - } else { - // In version 0, time isn't specified - 0 - }; + let atom = client_msg.data.as_data32()[0] as xproto::Atom; + if atom == wt.xconn.atoms[WM_DELETE_WINDOW] { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::CloseRequested, + }); + } else if atom == wt.xconn.atoms[_NET_WM_PING] { + client_msg.window = wt.root; + wt.xconn + .connection + .send_event( + false, + wt.root, + xproto::EventMask::SUBSTRUCTURE_NOTIFY + | xproto::EventMask::SUBSTRUCTURE_REDIRECT, + client_msg, + ) + .expect("Failed to send ping event") + .ignore_error(); + } else if client_msg.type_ == wt.xconn.atoms[XdndEnter] { + let longs = client_msg.data.as_data32(); + let source_window = longs[0] as xproto::Window; + let flags = longs[1]; + let version = flags >> 24; + self.dnd.version = Some(version); + let has_more_types = flags - (flags & (u32::max_value() - 1)) == 1; + if !has_more_types { + let type_list = vec![longs[2], longs[3], longs[4]]; + self.dnd.type_list = Some(type_list); + } else if let Ok(more_types) = self.dnd.get_type_list(source_window) { + self.dnd.type_list = Some(more_types); + } + } else if client_msg.type_ == wt.xconn.atoms[XdndPosition] { + // 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 longs = client_msg.data.as_data32(); + let source_window = longs[0]; + + // 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(&wt.xconn.atoms[TextUriList]) + } else { + false + }; - // This results in the `SelectionNotify` event below + if accepted { + self.dnd.source_window = Some(source_window); + { + if self.dnd.result.is_none() { + let time = if version >= 1 { + longs[3] + } else { + // In version 0, time isn't specified + 0 + }; + + // This results in the `SelectionNotify` event below + self.dnd + .convert_selection(window, time) + .expect("Failed to convert selection"); + } self.dnd - .convert_selection(window, time) - .expect("Failed to convert selection"); + .send_status(window, source_window, DndState::Accepted) + .expect("Failed to send `XdndStatus` message."); } - self.dnd - .send_status(window, source_window, DndState::Accepted) - .expect("Failed to send `XdndStatus` message."); + } else { + { + self.dnd + .send_status(window, source_window, DndState::Rejected) + .expect("Failed to send `XdndStatus` message."); + } + self.dnd.reset(); } - } else { + } else if client_msg.type_ == wt.xconn.atoms[XdndDrop] { + 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.as_data32()[0]; + (source_window, DndState::Rejected) + }; { self.dnd - .send_status(window, source_window, DndState::Rejected) - .expect("Failed to send `XdndStatus` message."); + .send_finished(window, source_window, state) + .expect("Failed to send `XdndFinished` message."); } self.dnd.reset(); + } else if client_msg.type_ == wt.xconn.atoms[XdndLeave] { + self.dnd.reset(); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::HoveredFileCancelled, + }); } - } else if client_msg.type_ == wt.xconn.atoms[XdndDrop] { - 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.as_data32()[0]; - (source_window, DndState::Rejected) - }; - { - self.dnd - .send_finished(window, source_window, state) - .expect("Failed to send `XdndFinished` message."); - } - self.dnd.reset(); - } else if client_msg.type_ == wt.xconn.atoms[XdndLeave] { - self.dnd.reset(); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::HoveredFileCancelled, - }); } - } - X11Event::SelectionNotify(xsel) => { - let window = xsel.requestor; - let window_id = mkwid(window); - - if xsel.property == wt.xconn.atoms[XdndSelection] { - let mut result = None; + X11Event::SelectionNotify(xsel) => { + let window = xsel.requestor; + let window_id = mkwid(window); - // This is where we receive data from drag and drop - if let Ok(mut data) = 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()), - }); + if xsel.property == wt.xconn.atoms[XdndSelection] { + let mut result = None; + + // This is where we receive data from drag and drop + if let Ok(mut data) = 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); } - result = Some(parse_result); - } - self.dnd.result = result; + self.dnd.result = result; + } } - } - X11Event::ConfigureNotify(xev) => { - let xwindow = xev.window; - let window_id = mkwid(xwindow); + X11Event::ConfigureNotify(xev) => { + let xwindow = xev.window; + let window_id = mkwid(xwindow); + + if let Some(window) = self.with_window(xwindow, Arc::clone) { + // 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. + + // These are both in physical space. + let new_inner_size = (xev.width as u32, xev.height as u32); + let new_inner_position = (xev.x.into(), xev.y.into()); + + let (mut resized, moved) = { + let mut shared_state_lock = window.shared_state_lock(); + + 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) + }; - if let Some(window) = self.with_window(xwindow, Arc::clone) { - // 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 position = window.shared_state_lock().position; - // These are both in physical space. - let new_inner_size = (xev.width as u32, xev.height as u32); - let new_inner_position = (xev.x.into(), xev.y.into()); + let new_outer_position = if let (Some(position), false) = (position, moved) + { + position + } else { + let mut shared_state_lock = window.shared_state_lock(); + + // 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); - let (mut resized, moved) = { - let mut shared_state_lock = window.shared_state_lock(); + // Unlock shared state to prevent deadlock in callback below + drop(shared_state_lock); - 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; + if moved { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Moved(outer.into()), + }); } - false + outer }; - (resized, moved) - }; - let position = window.shared_state_lock().position; + if is_synthetic { + let mut shared_state_lock = window.shared_state_lock(); + // 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((xev.width as u32, xev.height as u32)); + + let last_scale_factor = shared_state_lock.last_monitor.scale_factor; + let new_scale_factor = { + let window_rect = + util::AaRect::new(new_outer_position, new_inner_size); + let monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); + + if monitor.is_dummy() { + // Avoid updating monitor using a dummy monitor handle + last_scale_factor + } else { + shared_state_lock.last_monitor = monitor.clone(); + monitor.scale_factor + } + }; + if last_scale_factor != new_scale_factor { + let (new_width, new_height) = window.adjust_for_dpi( + last_scale_factor, + new_scale_factor, + width, + height, + &shared_state_lock, + ); - let new_outer_position = if let (Some(position), false) = (position, moved) { - position - } else { - let mut shared_state_lock = window.shared_state_lock(); + let old_inner_size = PhysicalSize::new(width, height); + let mut new_inner_size = PhysicalSize::new(new_width, new_height); - // 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); + // Unlock shared state to prevent deadlock in callback below + drop(shared_state_lock); - // Unlock shared state to prevent deadlock in callback below - drop(shared_state_lock); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor: new_scale_factor, + new_inner_size: &mut new_inner_size, + }, + }); - if moved { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Moved(outer.into()), - }); + if new_inner_size != old_inner_size { + window.set_inner_size_physical( + new_inner_size.width, + new_inner_size.height, + ); + window.shared_state_lock().dpi_adjusted = + Some(new_inner_size.into()); + // if the DPI factor changed, force a resize event to ensure the logical + // size is computed with the right DPI factor + resized = true; + } + } } - outer - }; - if is_synthetic { let mut shared_state_lock = window.shared_state_lock(); - // 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((xev.width as u32, xev.height as u32)); - - let last_scale_factor = shared_state_lock.last_monitor.scale_factor; - let new_scale_factor = { - let window_rect = util::AaRect::new(new_outer_position, new_inner_size); - let monitor = wt.xconn.get_monitor_for_window(Some(window_rect)); - - if monitor.is_dummy() { - // Avoid updating monitor using a dummy monitor handle - last_scale_factor + + // 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 { + if new_inner_size == adjusted_size + || !wt.xconn.wm_name_is_one_of(&["Xfwm4"]) + { + // When this finally happens, the event will not be synthetic. + shared_state_lock.dpi_adjusted = None; } else { - shared_state_lock.last_monitor = monitor.clone(); - monitor.scale_factor + window.set_inner_size_physical(adjusted_size.0, adjusted_size.1); } - }; - if last_scale_factor != new_scale_factor { - let (new_width, new_height) = window.adjust_for_dpi( - last_scale_factor, - new_scale_factor, - width, - height, - &shared_state_lock, - ); - - let old_inner_size = PhysicalSize::new(width, height); - let mut new_inner_size = PhysicalSize::new(new_width, new_height); + } - // Unlock shared state to prevent deadlock in callback below - drop(shared_state_lock); + // Unlock shared state to prevent deadlock in callback below + drop(shared_state_lock); + if resized { callback(Event::WindowEvent { window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor: new_scale_factor, - new_inner_size: &mut new_inner_size, - }, + event: WindowEvent::Resized(new_inner_size.into()), }); - - if new_inner_size != old_inner_size { - window.set_inner_size_physical( - new_inner_size.width, - new_inner_size.height, - ); - window.shared_state_lock().dpi_adjusted = - Some(new_inner_size.into()); - // if the DPI factor changed, force a resize event to ensure the logical - // size is computed with the right DPI factor - resized = true; - } } } - - let mut shared_state_lock = window.shared_state_lock(); - - // 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 { - if new_inner_size == adjusted_size - || !wt.xconn.wm_name_is_one_of(&["Xfwm4"]) - { - // When this finally happens, the event will not be synthetic. - shared_state_lock.dpi_adjusted = None; - } else { - window.set_inner_size_physical(adjusted_size.0, adjusted_size.1); - } - } - - // Unlock shared state to prevent deadlock in callback below - drop(shared_state_lock); - - if resized { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Resized(new_inner_size.into()), - }); - } } - } - - X11Event::ReparentNotify(xev) => { - // 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) - .expect("Failed to update WM info"); - - self.with_window(xev.window, |window| { - window.invalidate_cached_frame_extents(); - }); - } - X11Event::MapNotify(xev) => { - let window = xev.window; - let window_id = mkwid(window); - - // XXX re-issue the focus state when mapping the window. - // - // The purpose of it is to deliver initial focused state of the newly created - // window, given that we can't rely on `CreateNotify`, due to it being not - // sent. - let focus = self - .with_window(window, |window| window.has_focus()) - .unwrap_or_default(); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Focused(focus), - }); - } - X11Event::DestroyNotify(xev) => { - 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 as _)); - - // 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 as _) - .expect("Failed to destroy input context"); - */ - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Destroyed, - }); - } - X11Event::VisibilityNotify(xev) => { - let xwindow = xev.window; - callback(Event::WindowEvent { - window_id: mkwid(xwindow), - event: WindowEvent::Occluded(xev.state == xproto::Visibility::FULLY_OBSCURED), - }); - self.with_window(xwindow, |window| { - window.visibility_notify(); - }); - } + X11Event::ReparentNotify(xev) => { + // 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) + .expect("Failed to update WM info"); - X11Event::Expose(xev) => { - // 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 { + self.with_window(xev.window, |window| { + window.invalidate_cached_frame_extents(); + }); + } + X11Event::MapNotify(xev) => { let window = xev.window; let window_id = mkwid(window); - callback(Event::RedrawRequested(window_id)); + // XXX re-issue the focus state when mapping the window. + // + // The purpose of it is to deliver initial focused state of the newly created + // window, given that we can't rely on `CreateNotify`, due to it being not + // sent. + let focus = self + .with_window(window, |window| window.has_focus()) + .unwrap_or_default(); + callback(Event::WindowEvent { + window_id, + event: WindowEvent::Focused(focus), + }); } - } - - X11Event::KeyPress(ref xkev) | X11Event::KeyRelease(ref xkev) => { - // Note that in compose/pre-edit sequences, this will always be Released. - let state = if matches!(&xev, X11Event::KeyPress(_)) { - Pressed - } else { - Released - }; - - let window = xkev.event; - 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.detail; - - // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with - // a keycode of 0. - if keycode != 0 && !self.is_composing { - let scancode = (keycode - KEYCODE_OFFSET) as u32; - let keysym = wt.xconn.lookup_keysym(xkev); - let virtual_keycode = events::keysym_to_element(keysym as c_uint); + X11Event::DestroyNotify(xev) => { + let window = xev.window; + let window_id = mkwid(window); - update_modifiers!( - ModifiersState::from_x11_mask(xkev.state.into()), - self.mod_keymap.get_modifier(xkev.detail as _) - ); + // In the event that the window's been destroyed without being dropped first, we + // cleanup again here. + wt.windows.borrow_mut().remove(&WindowId(window as _)); - let modifiers = self.device_mod_state.modifiers(); + // 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 as _) + .expect("Failed to destroy input context"); + */ - #[allow(deprecated)] callback(Event::WindowEvent { window_id, - event: WindowEvent::KeyboardInput { - device_id, - input: KeyboardInput { - state, - scancode, - virtual_keycode, - modifiers, - }, - is_synthetic: false, - }, + event: WindowEvent::Destroyed, + }); + } + + X11Event::VisibilityNotify(xev) => { + let xwindow = xev.window; + callback(Event::WindowEvent { + window_id: mkwid(xwindow), + event: WindowEvent::Occluded( + xev.state == xproto::Visibility::FULLY_OBSCURED, + ), + }); + self.with_window(xwindow, |window| { + window.visibility_notify(); }); } - /* - if state == Pressed { - let written = if let Some(ic) = wt.ime.borrow().get_context(window as _) { - wt.xconn.lookup_utf8(ic, xkev) + X11Event::Expose(xev) => { + // 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)); + } + } + + X11Event::KeyPress(ref xkev) | X11Event::KeyRelease(ref xkev) => { + // Note that in compose/pre-edit sequences, this will always be Released. + let state = if matches!(&xev, X11Event::KeyPress(_)) { + Pressed } else { - return; + Released }; - // If we're composing right now, send the string we've got from X11 via - // Ime::Commit. - if self.is_composing && keycode == 0 && !written.is_empty() { - let event = Event::WindowEvent { - window_id, - event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), - }; - callback(event); + let window = xkev.event; + 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.detail; + + // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with + // a keycode of 0. + if keycode != 0 && !self.is_composing { + let scancode = (keycode - KEYCODE_OFFSET) as u32; + 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.into()), + self.mod_keymap.get_modifier(xkev.detail as _) + ); + + let modifiers = self.device_mod_state.modifiers(); - let event = Event::WindowEvent { + #[allow(deprecated)] + callback(Event::WindowEvent { window_id, - event: WindowEvent::Ime(Ime::Commit(written)), + 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 as _) { + wt.xconn.lookup_utf8(ic, xkev) + } else { + return; }; - self.is_composing = false; - callback(event); - } else { - for chr in written.chars() { + // If we're composing right now, send the string we've got from X11 via + // Ime::Commit. + if self.is_composing && keycode == 0 && !written.is_empty() { + let event = Event::WindowEvent { + window_id, + event: WindowEvent::Ime(Ime::Preedit(String::new(), None)), + }; + callback(event); + let event = Event::WindowEvent { window_id, - event: WindowEvent::ReceivedCharacter(chr), + event: WindowEvent::Ime(Ime::Commit(written)), }; + self.is_composing = false; callback(event); + } else { + for chr in written.chars() { + let event = Event::WindowEvent { + window_id, + event: WindowEvent::ReceivedCharacter(chr), + }; + + callback(event); + } } } + */ } - */ - } - X11Event::XinputButtonPress(ref xbev) | X11Event::XinputButtonRelease(ref xbev) => { - let window_id = mkwid(xbev.event); - let device_id = mkdid(xbev.deviceid); + X11Event::XinputButtonPress(ref xbev) | X11Event::XinputButtonRelease(ref xbev) => { + let window_id = mkwid(xbev.event); + let device_id = mkdid(xbev.deviceid); - // Once psychon/x11rb#768 reaches a release, use BitAnd directly on the flags. - if (u32::from(xbev.flags) & u32::from(xinput::PointerEventFlags::POINTER_EMULATED)) - != 0 - { - // Deliver multi-touch events instead of emulated mouse events. - return; - } + // Once psychon/x11rb#768 reaches a release, use BitAnd directly on the flags. + if (u32::from(xbev.flags) + & u32::from(xinput::PointerEventFlags::POINTER_EMULATED)) + != 0 + { + // Deliver multi-touch events instead of emulated mouse events. + return; + } - let modifiers = ModifiersState::from_x11(&xbev.mods); - update_modifiers!(modifiers, None); + let modifiers = ModifiersState::from_x11(&xbev.mods); + update_modifiers!(modifiers, None); - let state = if matches!(&xev, X11Event::XinputButtonPress(_)) { - Pressed - } else { - Released - }; - match xbev.detail { - 1 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Left, - modifiers, - }, - }), - 2 => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Middle, - modifiers, - }, - }), - 3 => 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 u32::from(xbev.flags) - & u32::from(xinput::PointerEventFlags::POINTER_EMULATED) - == 0 - { - callback(Event::WindowEvent { - window_id, - event: MouseWheel { - device_id, - delta: match xbev.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!(), + let state = if matches!(&xev, X11Event::XinputButtonPress(_)) { + Pressed + } else { + Released + }; + match xbev.detail { + 1 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Left, + modifiers, + }, + }), + 2 => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Middle, + modifiers, + }, + }), + 3 => 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 u32::from(xbev.flags) + & u32::from(xinput::PointerEventFlags::POINTER_EMULATED) + == 0 + { + callback(Event::WindowEvent { + window_id, + event: MouseWheel { + device_id, + delta: match xbev.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, }, - phase: TouchPhase::Moved, - modifiers, - }, - }); + }); + } } - } - x => callback(Event::WindowEvent { - window_id, - event: MouseInput { - device_id, - state, - button: Other(x as u16), - modifiers, - }, - }), + x => callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button: Other(x as u16), + modifiers, + }, + }), + } } - } - - X11Event::XinputMotion(xev) => { - let device_id = mkdid(xev.deviceid); - let window_id = mkwid(xev.event); - let new_cursor_pos = (fp1616(xev.event_x), fp1616(xev.event_y)); - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); + X11Event::XinputMotion(xev) => { + let device_id = mkdid(xev.deviceid); + let window_id = mkwid(xev.event); + let new_cursor_pos = (fp1616(xev.event_x), fp1616(xev.event_y)); - 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 position = PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); + let modifiers = ModifiersState::from_x11(&xev.mods); + update_modifiers!(modifiers, None); - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id, - position, - modifiers, - }, + 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) }); - } else if cursor_moved.is_none() { - return; - } + if cursor_moved == Some(true) { + let position = + PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); - // More gymnastics, for self.devices - let mut events = Vec::new(); - { - let mut devices = self.devices.borrow_mut(); - let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) { - Some(device) => device, - None => return, - }; + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id, + position, + modifiers, + }, + }); + } else if cursor_moved.is_none() { + return; + } - // Iterator over the set bits in the mask. - let bits_iter = xev.valuator_mask.iter().enumerate().flat_map(|(i, &mask)| { - let quantum = std::mem::size_of::(); - (0..quantum) - .filter(move |j| mask & (1 << j) != 0) - .map(move |j| i * quantum + j) - }); + // More gymnastics, for self.devices + let mut events = Vec::new(); + { + let mut devices = self.devices.borrow_mut(); + let physical_device = match devices.get_mut(&DeviceId(xev.sourceid)) { + Some(device) => device, + None => return, + }; - // Get the iterator over the axises that we want. - let axis_iter = xev - .axisvalues - .iter() - .map(|&frac| fp3232(frac)) - .zip(bits_iter); - - // Iterate and set the axises. - axis_iter.for_each(|(x, i)| { - if let Some(&mut (_, ref mut info)) = physical_device - .scroll_axes - .iter_mut() - .find(|&&mut (axis, _)| axis as usize == 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 { - // X11 vertical scroll coordinates are opposite to winit's - ScrollOrientation::Horizontal => { - LineDelta(-delta as f32, 0.0) - } - 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: x, - }, + // Iterator over the set bits in the mask. + let bits_iter = + xev.valuator_mask.iter().enumerate().flat_map(|(i, &mask)| { + let quantum = std::mem::size_of::(); + (0..quantum) + .filter(move |j| mask & (1 << j) != 0) + .map(move |j| i * quantum + j) }); - } - }); - } - for event in events { - callback(event); + + // Get the iterator over the axises that we want. + let axis_iter = xev + .axisvalues + .iter() + .map(|&frac| fp3232(frac)) + .zip(bits_iter); + + // Iterate and set the axises. + axis_iter.for_each(|(x, i)| { + if let Some(&mut (_, ref mut info)) = physical_device + .scroll_axes + .iter_mut() + .find(|&&mut (axis, _)| axis as usize == 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 { + // X11 vertical scroll coordinates are opposite to winit's + ScrollOrientation::Horizontal => { + LineDelta(-delta as f32, 0.0) + } + 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: x, + }, + }); + } + }); + } + for event in events { + callback(event); + } } - } - X11Event::XinputEnter(xev) => { - let window_id = mkwid(xev.event); - let device_id = mkdid(xev.deviceid); + X11Event::XinputEnter(xev) => { + let window_id = mkwid(xev.event); + let device_id = mkdid(xev.deviceid); - if let Some(all_info) = DeviceInfo::get(&wt.xconn, 0) { - let mut devices = self.devices.borrow_mut(); - for device_info in all_info.iter() { - if device_info.deviceid == xev.sourceid + if let Some(all_info) = DeviceInfo::get(&wt.xconn, 0) { + 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); + { + let device_id = DeviceId(device_info.deviceid); + if let Some(device) = devices.get_mut(&device_id) { + device.reset_scroll_position(device_info); + } } } } - } - if self.window_exists(xev.event) { - callback(Event::WindowEvent { - window_id, - event: CursorEntered { device_id }, - }); - - let position = PhysicalPosition::new(fp1616(xev.event_x), fp1616(xev.event_y)); + if self.window_exists(xev.event) { + callback(Event::WindowEvent { + window_id, + event: CursorEntered { device_id }, + }); - // 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 - .connection - .xinput_xi_query_pointer(xev.event, xev.deviceid) - .platform() - .and_then(|r| r.reply().platform()) - .map(|r| ModifiersState::from_x11(&r.mods)) - .expect("Failed to query pointer device"); + let position = + PhysicalPosition::new(fp1616(xev.event_x), fp1616(xev.event_y)); + + // 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 + .connection + .xinput_xi_query_pointer(xev.event, xev.deviceid) + .platform() + .and_then(|r| r.reply().platform()) + .map(|r| ModifiersState::from_x11(&r.mods)) + .expect("Failed to query pointer device"); - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id, - position, - modifiers, - }, - }); + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id, + position, + modifiers, + }, + }); + } } - } - X11Event::XinputLeave(xev) => { - // 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), - }, - }); + X11Event::XinputLeave(xev) => { + // 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), + }, + }); + } } - } - X11Event::XinputFocusIn(xev) => { - /* - wt.ime - .borrow_mut() - .focus(xev.event as _) - .expect("Failed to focus input context"); - */ + X11Event::XinputFocusIn(xev) => { + /* + wt.ime + .borrow_mut() + .focus(xev.event as _) + .expect("Failed to focus input context"); + */ - let modifiers = ModifiersState::from_x11(&xev.mods); + let modifiers = ModifiersState::from_x11(&xev.mods); - self.device_mod_state.update_state(&modifiers, None); + self.device_mod_state.update_state(&modifiers, None); - if self.active_window != Some(xev.event) { - self.active_window = Some(xev.event); + if self.active_window != Some(xev.event) { + self.active_window = Some(xev.event); - wt.update_device_event_filter(true) - .expect("Failed to update device event filter"); + wt.update_device_event_filter(true) + .expect("Failed to update device event filter"); - let window_id = mkwid(xev.event); - let position = PhysicalPosition::new(fp1616(xev.event_x), fp1616(xev.event_y)); + let window_id = mkwid(xev.event); + let position = + PhysicalPosition::new(fp1616(xev.event_x), fp1616(xev.event_y)); - if let Some(window) = self.with_window(xev.event, Arc::clone) { - window.shared_state_lock().has_focus = true; - } - - callback(Event::WindowEvent { - window_id, - event: Focused(true), - }); + if let Some(window) = self.with_window(xev.event, Arc::clone) { + window.shared_state_lock().has_focus = true; + } - if !modifiers.is_empty() { callback(Event::WindowEvent { window_id, - event: WindowEvent::ModifiersChanged(modifiers), + 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); + if !modifiers.is_empty() { + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(modifiers), + }); + } - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id: mkdid(pointer_id), - position, - modifiers, - }, - }); + // 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); - // Issue key press events for all pressed keys - Self::handle_pressed_keys( - wt, - window_id, - ElementState::Pressed, - &self.mod_keymap, - &mut self.device_mod_state, - &mut callback, - ); - } - } + callback(Event::WindowEvent { + window_id, + event: CursorMoved { + device_id: mkdid(pointer_id), + position, + modifiers, + }, + }); - X11Event::XinputFocusOut(xev) => { - if !self.window_exists(xev.event) { - return; + // Issue key press events for all pressed keys + Self::handle_pressed_keys( + wt, + window_id, + ElementState::Pressed, + &self.mod_keymap, + &mut self.device_mod_state, + &mut callback, + ); + } } - /* - wt.ime - .borrow_mut() - .unfocus(xev.event as _) - .expect("Failed to unfocus input context"); - */ + X11Event::XinputFocusOut(xev) => { + if !self.window_exists(xev.event) { + return; + } - if self.active_window.take() == Some(xev.event) { - let window_id = mkwid(xev.event); + /* + wt.ime + .borrow_mut() + .unfocus(xev.event as _) + .expect("Failed to unfocus input context"); + */ - wt.update_device_event_filter(false) - .expect("Failed to update device event filter"); + if self.active_window.take() == Some(xev.event) { + let window_id = mkwid(xev.event); - // Issue key release events for all pressed keys - Self::handle_pressed_keys( - wt, - window_id, - ElementState::Released, - &self.mod_keymap, - &mut self.device_mod_state, - &mut callback, - ); + wt.update_device_event_filter(false) + .expect("Failed to update device event filter"); - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ModifiersChanged(ModifiersState::empty()), - }); + // Issue key release events for all pressed keys + Self::handle_pressed_keys( + wt, + window_id, + ElementState::Released, + &self.mod_keymap, + &mut self.device_mod_state, + &mut callback, + ); - if let Some(window) = self.with_window(xev.event, Arc::clone) { - window.shared_state_lock().has_focus = false; - } + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ModifiersChanged(ModifiersState::empty()), + }); - callback(Event::WindowEvent { - window_id, - event: Focused(false), - }) + if let Some(window) = self.with_window(xev.event, Arc::clone) { + window.shared_state_lock().has_focus = false; + } + + callback(Event::WindowEvent { + window_id, + event: Focused(false), + }) + } } - } - X11Event::XinputTouchBegin(ref xtev) - | X11Event::XinputTouchEnd(ref xtev) - | X11Event::XinputTouchUpdate(ref xtev) => { - let window_id = mkwid(xtev.event); - let phase = match xev { - X11Event::XinputTouchBegin(_) => TouchPhase::Started, - X11Event::XinputTouchUpdate(_) => TouchPhase::Moved, - X11Event::XinputTouchEnd(_) => TouchPhase::Ended, - _ => unreachable!(), - }; - if self.window_exists(xtev.event) { - let id = xtev.detail as u64; - let modifiers = self.device_mod_state.modifiers(); - let location = PhysicalPosition::new(xtev.event_x as f64, xtev.event_y as f64); + X11Event::XinputTouchBegin(ref xtev) + | X11Event::XinputTouchEnd(ref xtev) + | X11Event::XinputTouchUpdate(ref xtev) => { + let window_id = mkwid(xtev.event); + let phase = match xev { + X11Event::XinputTouchBegin(_) => TouchPhase::Started, + X11Event::XinputTouchUpdate(_) => TouchPhase::Moved, + X11Event::XinputTouchEnd(_) => TouchPhase::Ended, + _ => unreachable!(), + }; + if self.window_exists(xtev.event) { + let id = xtev.detail as u64; + let modifiers = self.device_mod_state.modifiers(); + let location = + PhysicalPosition::new(xtev.event_x as f64, xtev.event_y as f64); + + // 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.cast(), + modifiers, + }, + }); + } - // 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.cast(), - modifiers, - }, - }); + event: WindowEvent::Touch(Touch { + device_id: mkdid(xtev.deviceid), + phase, + location, + force: None, // TODO + id, + }), + }) } - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::Touch(Touch { - device_id: mkdid(xtev.deviceid), - phase, - location, - force: None, // TODO - id, - }), - }) } - } - X11Event::XinputRawButtonPress(ref xbev) - | X11Event::XinputRawButtonRelease(ref xbev) => { - if u32::from(xbev.flags) & u32::from(xinput::PointerEventFlags::POINTER_EMULATED) - == 0 - { - callback(Event::DeviceEvent { - device_id: mkdid(xbev.deviceid), - event: DeviceEvent::Button { - button: xbev.detail, - state: match xev { - X11Event::XinputRawButtonPress(_) => Pressed, - X11Event::XinputRawButtonRelease(_) => Released, - _ => unreachable!(), + X11Event::XinputRawButtonPress(ref xbev) + | X11Event::XinputRawButtonRelease(ref xbev) => { + if u32::from(xbev.flags) + & u32::from(xinput::PointerEventFlags::POINTER_EMULATED) + == 0 + { + callback(Event::DeviceEvent { + device_id: mkdid(xbev.deviceid), + event: DeviceEvent::Button { + button: xbev.detail, + state: match xev { + X11Event::XinputRawButtonPress(_) => Pressed, + X11Event::XinputRawButtonRelease(_) => Released, + _ => unreachable!(), + }, }, - }, - }); + }); + } } - } - - X11Event::XinputRawMotion(xev) => { - let did = mkdid(xev.deviceid); - let mut mouse_delta = (0.0, 0.0); - let mut scroll_delta = (0.0, 0.0); + X11Event::XinputRawMotion(xev) => { + let did = mkdid(xev.deviceid); - // Iterate over all bits in the mask. - let bits_iter = xev.valuator_mask.iter().enumerate().flat_map(|(i, &mask)| { - let quantum = std::mem::size_of::(); + let mut mouse_delta = (0.0, 0.0); + let mut scroll_delta = (0.0, 0.0); - (0..quantum * 8).filter_map(move |j| { - let bit = 1 << j; - if mask & bit != 0 { - Some(i * quantum * 8 + j) - } else { - None - } - }) - }); + // Iterate over all bits in the mask. + let bits_iter = xev.valuator_mask.iter().enumerate().flat_map(|(i, &mask)| { + let quantum = std::mem::size_of::(); - // Match those bits to the raw values. - let values_iter = xev.axisvalues_raw.iter().map(|&v| fp3232(v)).zip(bits_iter); - - values_iter.for_each(|(x, i)| { - // 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, - }, + (0..quantum * 8).filter_map(move |j| { + let bit = 1 << j; + if mask & bit != 0 { + Some(i * quantum * 8 + j) + } else { + None + } + }) }); - }); - if mouse_delta != (0.0, 0.0) { - callback(Event::DeviceEvent { - device_id: did, - event: DeviceEvent::MouseMotion { delta: mouse_delta }, + + // Match those bits to the raw values. + let values_iter = xev.axisvalues_raw.iter().map(|&v| fp3232(v)).zip(bits_iter); + + values_iter.for_each(|(x, i)| { + // 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, + }, + }); }); + 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), + }, + }); + } } - if scroll_delta != (0.0, 0.0) { + + X11Event::XinputRawKeyPress(ref xkev) | X11Event::XinputRawKeyRelease(ref xkev) => { + let state = match xev { + X11Event::XinputRawKeyPress(_) => Pressed, + X11Event::XinputRawKeyRelease(_) => Released, + _ => unreachable!(), + }; + + let device_id = mkdid(xkev.sourceid); + let keycode = xkev.detail; + let scancode = match keycode.checked_sub(KEYCODE_OFFSET as u32) { + Some(scancode) => scancode, + None => return, + }; + 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(); + + #[allow(deprecated)] callback(Event::DeviceEvent { - device_id: did, - event: DeviceEvent::MouseWheel { - delta: LineDelta(scroll_delta.0, scroll_delta.1), - }, + device_id, + event: DeviceEvent::Key(KeyboardInput { + scancode, + virtual_keycode, + state, + modifiers, + }), }); - } - } - - X11Event::XinputRawKeyPress(ref xkev) | X11Event::XinputRawKeyRelease(ref xkev) => { - let state = match xev { - X11Event::XinputRawKeyPress(_) => Pressed, - X11Event::XinputRawKeyRelease(_) => Released, - _ => unreachable!(), - }; - - let device_id = mkdid(xkev.sourceid); - let keycode = xkev.detail; - let scancode = match keycode.checked_sub(KEYCODE_OFFSET as u32) { - Some(scancode) => scancode, - None => return, - }; - 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(); - - #[allow(deprecated)] - 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); + 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(); + let new_modifiers = self.device_mod_state.modifiers(); - if modifiers != new_modifiers { - if let Some(window_id) = self.active_window { - callback(Event::WindowEvent { - window_id: mkwid(window_id), - event: WindowEvent::ModifiersChanged(new_modifiers), - }); + if modifiers != new_modifiers { + if let Some(window_id) = self.active_window { + callback(Event::WindowEvent { + window_id: mkwid(window_id), + event: WindowEvent::ModifiersChanged(new_modifiers), + }); + } } } } - } - X11Event::XinputHierarchy(xev) => { - for info in xev.infos.iter() { - if 0 != (u32::from(info.flags) - & u32::from( - xinput::HierarchyMask::SLAVE_ADDED - | xinput::HierarchyMask::MASTER_ADDED, - )) - { - self.init_device(info.deviceid); - callback(Event::DeviceEvent { - device_id: mkdid(info.deviceid), - event: DeviceEvent::Added, - }); - } else if 0 - != (u32::from(info.flags) + X11Event::XinputHierarchy(xev) => { + for info in xev.infos.iter() { + if 0 != (u32::from(info.flags) & u32::from( - xinput::HierarchyMask::SLAVE_REMOVED - | xinput::HierarchyMask::MASTER_REMOVED, + xinput::HierarchyMask::SLAVE_ADDED + | xinput::HierarchyMask::MASTER_ADDED, )) - { - callback(Event::DeviceEvent { - device_id: mkdid(info.deviceid), - event: DeviceEvent::Removed, - }); - let mut devices = self.devices.borrow_mut(); - devices.remove(&DeviceId(info.deviceid)); + { + self.init_device(info.deviceid); + callback(Event::DeviceEvent { + device_id: mkdid(info.deviceid), + event: DeviceEvent::Added, + }); + } else if 0 + != (u32::from(info.flags) + & u32::from( + xinput::HierarchyMask::SLAVE_REMOVED + | xinput::HierarchyMask::MASTER_REMOVED, + )) + { + callback(Event::DeviceEvent { + device_id: mkdid(info.deviceid), + event: DeviceEvent::Removed, + }); + let mut devices = self.devices.borrow_mut(); + devices.remove(&DeviceId(info.deviceid)); + } } } - } - X11Event::RandrNotify(_) => { - // 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 { - // Previous list may be empty, in case of disconnecting and - // reconnecting the only one monitor. We still need to emit events in - // this case. - let maybe_prev_scale_factor = prev_list - .iter() - .find(|prev_monitor| prev_monitor.name == new_monitor.name) - .map(|prev_monitor| prev_monitor.scale_factor); - if Some(new_monitor.scale_factor) != maybe_prev_scale_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 { - let (width, height) = window.inner_size_physical(); - let (new_width, new_height) = window.adjust_for_dpi( - // If we couldn't determine the previous scale - // factor (e.g., because all monitors were closed - // before), just pick whatever the current monitor - // has set as a baseline. - maybe_prev_scale_factor.unwrap_or(monitor.scale_factor), - new_monitor.scale_factor, - width, - height, - &window.shared_state_lock(), - ); - - let window_id = crate::window::WindowId(*window_id); - let old_inner_size = PhysicalSize::new(width, height); - let mut new_inner_size = - PhysicalSize::new(new_width, new_height); - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ScaleFactorChanged { - scale_factor: new_monitor.scale_factor, - new_inner_size: &mut new_inner_size, - }, - }); - - if new_inner_size != old_inner_size { - let (new_width, new_height) = new_inner_size.into(); - window.set_inner_size_physical(new_width, new_height); + X11Event::RandrNotify(_) => { + // 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 { + // Previous list may be empty, in case of disconnecting and + // reconnecting the only one monitor. We still need to emit events in + // this case. + let maybe_prev_scale_factor = prev_list + .iter() + .find(|prev_monitor| prev_monitor.name == new_monitor.name) + .map(|prev_monitor| prev_monitor.scale_factor); + if Some(new_monitor.scale_factor) != maybe_prev_scale_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 { + let (width, height) = window.inner_size_physical(); + let (new_width, new_height) = window.adjust_for_dpi( + // If we couldn't determine the previous scale + // factor (e.g., because all monitors were closed + // before), just pick whatever the current monitor + // has set as a baseline. + maybe_prev_scale_factor + .unwrap_or(monitor.scale_factor), + new_monitor.scale_factor, + width, + height, + &window.shared_state_lock(), + ); + + let window_id = crate::window::WindowId(*window_id); + let old_inner_size = PhysicalSize::new(width, height); + let mut new_inner_size = + PhysicalSize::new(new_width, new_height); + + callback(Event::WindowEvent { + window_id, + event: WindowEvent::ScaleFactorChanged { + scale_factor: new_monitor.scale_factor, + new_inner_size: &mut new_inner_size, + }, + }); + + if new_inner_size != old_inner_size { + let (new_width, new_height) = new_inner_size.into(); + window + .set_inner_size_physical(new_width, new_height); + } } } } @@ -1280,9 +1297,9 @@ impl EventProcessor { } } } - } - _ => {} + _ => {} + } } // Handle IME requests. diff --git a/src/platform_impl/linux/x11/ime.rs b/src/platform_impl/linux/x11/ime.rs new file mode 100644 index 00000000000..4bfc6c6c28b --- /dev/null +++ b/src/platform_impl/linux/x11/ime.rs @@ -0,0 +1,495 @@ +//! Implementation of input method handling using `xim-rs`. + +use super::XConnection; + +use std::collections::VecDeque; +use std::ffi::{CStr, CString}; +use std::fmt; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{Arc, Mutex}; + +use xim::x11rb::{HasConnection, X11rbClient}; +use xim::AHashMap; +use xim::{AttributeName, InputStyle, Point}; +use xim::{Client as _, ClientError, ClientHandler}; + +use x11rb::connection::Connection; +use x11rb::protocol::xproto::Window; +use x11rb::protocol::Event; +use x11rb::xcb_ffi::XCBConnection; + +/// Lock a mutex and ignore poison. +macro_rules! lock { + ($mutex:expr) => { + $mutex.lock().unwrap_or_else(|e| e.into_inner()) + }; +} + +/// Get the current locale. +fn locale() -> String { + // Get the pointer to the current locale. + let locale_ptr = unsafe { libc::setlocale(libc::LC_CTYPE, std::ptr::null()) }; + + // If locale_ptr is null, just default to en_US.UTF-8. + if locale_ptr.is_null() { + return "en_US.UTF-8".to_owned(); + } + + // Convert the pointer to a CStr. + let locale_cstr = unsafe { CStr::from_ptr(locale_ptr) }; + + // Convert the CStr to a String to prevent the result from getting clobbered. + locale_cstr.to_str().unwrap_or("en_US.UTF-8").to_owned() +} + +impl HasConnection for XConnection { + type Connection = XCBConnection; + + fn conn(&self) -> &Self::Connection { + &self.connection + } +} + +#[derive(Copy, Clone)] +enum Style { + Preedit, + Nothing, + None, +} + +/// A collection of the IME events that can occur. +#[derive(Debug, Clone, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum ImeEvent { + Enabled, + Start, + Update(String, usize), + End, + Disabled, +} + +type XimClient = X11rbClient>; + +/// IME-related information. +/// +/// TODO(notgull): This is a lot of synchronization for a structure that shouldn't be +/// synchronized that often. In an ideal world, these would all be `RefCell`s, but +/// right now we need to be able to store `XConnection` and therefore `ImeData` in +/// a static variable. In the future, we should probably avoid putting `XConnection` +/// in a `OnceCell` and instead find a way to make `XConnection` not need to be +/// `Sync`. +pub(super) struct ImeData { + /// The XIM client. + client: Mutex, + + /// State of the IME. + inner_data: Mutex, +} + +/// The current IME state. +#[derive(Default)] +struct ClientData { + /// The input method we are currently using. + input_method: Option, + + /// The registered input styles for the IM. + styles: Option<(Style, Style)>, + + /// The list of produced IME events. + events: Vec<(Window, ImeEvent)>, + + /// Whether the IME is currently disconnected. + disconnected: bool, + + /// Hash map of input context IDs to their data. + ic_data: AHashMap, + + /// Hash map of window IDs to their input context IDs. + window_data: AHashMap, + + /// Windows that are waiting for an input context. + pending_windows: VecDeque, +} + +/// Per-input context data. +struct IcData { + /// The identifier of the context. + id: u16, + + /// The window that the context is attached to. + window: Window, +} + +impl ImeData { + /// Create a new `ImeData`. + pub(super) fn new(xconn: &Arc, screen: usize) -> Result { + // IM servers to try, in order: + // - None, which defaults to the environment variable `XMODIFIERS` in xim's impl. + // - "local", which is the default for most IMEs. + // - empty string, which may work in some cases. + let input_methods = [None, Some("local"), Some("")]; + + let client = 'get_client: loop { + let mut last_error = None; + + for im in input_methods { + // Try to create a client. + match XimClient::init(xconn.clone(), screen, im) { + Ok(client) => break 'get_client client, + Err(err) => { + struct ImName(Option<&'static str>); + + impl fmt::Debug for ImName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self.0 { + Some(name) => write!(f, "\"{}\"", name), + None => write!(f, "default input method"), + } + } + } + + log::warn!("Failed to create XIM client for {:?}: {}", ImName(im), &err); + last_error = Some(err); + } + } + } + + return Err(last_error.unwrap_or({ + ClientError::NoXimServer + })); + }; + + Ok(Self { + client: Mutex::new(client), + inner_data: Mutex::new(ClientData { + disconnected: true, + ..Default::default() + }), + }) + } + + /// Run the IME if this event has any relevance to IME. + pub(super) fn filter_event(&self, event: &Event, mut conn: &XConnection) -> bool { + lock!(self.client) + .filter_event(event, &mut conn) + .expect("Failed to filter event") + } + + /// Tell whether or not there are pending IME events. + pub(super) fn has_pending_events(&self) -> bool { + !lock!(self.inner_data).events.is_empty() + } + + /// Has the IME been disconnected? + pub(super) fn is_disconnected(&self) -> bool { + lock!(self.inner_data).disconnected + } +} + +impl XConnection { + /// Get the `ImeData`. + /// + /// We call this from inside `ClientHandler`, so we can be sure that the `ImeData` is + /// initialized. + fn ime_data(&self) -> &ImeData { + self.ime_data + .as_ref() + .expect("Shouldn't be possible, IME should be initialized") + } + + /// Block until we've acted on a new IME event. + pub(super) fn block_for_ime(&self) { + let mut last_event = None; + + loop { + let mut event_queue = lock!(self.event_queue); + + // Check the last event we've seen. + if let Some(last_event) = last_event.as_ref() { + if self.ime_data().filter_event(last_event, self) { + return; + } + } + + // See if there's anything worth filtering in the event queue. + let mut filtered_any = false; + event_queue.retain(|event| { + if self.ime_data().filter_event(event, self) { + filtered_any = true; + false + } else { + true + } + }); + + event_queue.extend(last_event); + if filtered_any { + return; + } + + // Wait for a new event. + log::info!("x11(ime): Entering wait for IME event"); + last_event = Some( + self.connection + .wait_for_event() + .expect("Failed to wait for event"), + ); + } + } + + /// Create a new IME context for a window. + pub(super) fn create_context(&self, window: Window, with_preedit: bool, spot: Option) -> Result { + let mut client_data = lock!(self.ime_data().inner_data); + let mut client = lock!(self.ime_data().client); + + if client_data.disconnected { + return Ok(false); + } + + // Get the current style. + let style = match (client_data.styles, with_preedit) { + (None, _) => return Err(ClientError::Other("No input styles".into())), + (Some((preedit_style, _)), true) => preedit_style, + (Some((_, none_style)), false) => none_style + }; + + // Setup IC attributes. + let ic_attributes = { + let mut ic_attributes = client.build_ic_attributes() + .push(AttributeName::ClientWindow, window); + + let ic_style = match style { + Style::Preedit => InputStyle::PREEDIT_POSITION | InputStyle::STATUS_NOTHING, + Style::Nothing => InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING, + Style::None => InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE, + }; + + if let Some(spot) = spot { + ic_attributes = ic_attributes.push(AttributeName::SpotLocation, spot); + } + + ic_attributes.push(AttributeName::InputStyle, ic_style).build() + }; + + // Create the IC. + client.create_ic( + client_data.input_method.unwrap(), + ic_attributes, + )?; + + // Assign this window to the next IC. + client_data.pending_windows.push_back(window); + + Ok(true) + } +} + +impl ClientHandler for &XConnection { + fn handle_connect(&mut self, client: &mut XimClient) -> Result<(), ClientError> { + // We are now connected. Request an input method with our current locale. + lock!(self.ime_data().inner_data).disconnected = false; + client.open(&locale()) + } + + fn handle_open( + &mut self, + client: &mut XimClient, + input_method_id: u16, + ) -> Result<(), ClientError> { + // Store the client's input method ID. + let mut client_data = lock!(self.ime_data().inner_data); + debug_assert!(client_data.input_method.is_none()); + client_data.input_method = Some(input_method_id); + + // Ask for the IM's attributes. + client.get_im_values(input_method_id, &[AttributeName::QueryInputStyle]) + } + + fn handle_get_im_values( + &mut self, + _client: &mut XimClient, + input_method_id: u16, + mut attributes: xim::AHashMap>, + ) -> Result<(), ClientError> { + let mut client_data = lock!(self.ime_data().inner_data); + debug_assert_eq!(client_data.input_method, Some(input_method_id)); + + let mut preedit_style = None; + let mut none_style = None; + + // Get the input styles. + let styles = { + let style = attributes + .remove(&AttributeName::QueryInputStyle) + .expect("No query input style"); + let mut result = vec![0u32; style.len() / 4]; + + bytemuck::cast_slice_mut::(&mut result).copy_from_slice(&style); + + result + }; + + { + // The styles that we're looking for. + let lu_preedit_style = InputStyle::PREEDIT_CALLBACKS | InputStyle::STATUS_NOTHING; + let lu_nothing_style = InputStyle::PREEDIT_NOTHING | InputStyle::STATUS_NOTHING; + let lu_none_style = InputStyle::PREEDIT_NONE | InputStyle::STATUS_NONE; + + for style in styles { + let style = InputStyle::from_bits_truncate(style); + + if style == lu_preedit_style { + preedit_style = Some(Style::Preedit); + } else if style == lu_nothing_style { + preedit_style = Some(Style::Nothing); + } else if style == lu_none_style { + none_style = Some(Style::None); + } + } + } + + let (preedit_style, none_style) = match (preedit_style, none_style) { + (None, None) => { + log::error!("No supported input styles found"); + return Ok(()); + } + + (Some(style), None) | (None, Some(style)) => (style, style), + + (Some(preedit_style), Some(none_style)) => (preedit_style, none_style), + }; + + client_data.styles = Some((preedit_style, none_style)); + + Ok(()) + } + + fn handle_close( + &mut self, + _client: &mut XimClient, + input_method_id: u16, + ) -> Result<(), ClientError> { + // We're disconnected. + let mut client_data = lock!(self.ime_data().inner_data); + debug_assert_eq!(client_data.input_method, Some(input_method_id)); + client_data.input_method = None; + + Ok(()) + } + + fn handle_disconnect(&mut self) { + // Indicate that we are now disconnected. + let mut client_data = lock!(self.ime_data().inner_data); + client_data.disconnected = true; + } + + fn handle_commit( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + _text: &str, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_create_ic( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_destroy_ic( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_forward_event( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + _flag: xim::ForwardEventFlag, + _xev: ::XEvent, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_preedit_done( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_preedit_draw( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + _caret: i32, + _chg_first: i32, + _chg_len: i32, + _status: xim::PreeditDrawStatus, + _preedit_string: &str, + _feedbacks: Vec, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_preedit_start( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_query_extension( + &mut self, + _client: &mut XimClient, + _extensions: &[xim::Extension], + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_set_event_mask( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + _forward_event_mask: u32, + _synchronous_event_mask: u32, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } + + fn handle_set_ic_values( + &mut self, + _client: &mut XimClient, + _input_method_id: u16, + _input_context_id: u16, + ) -> Result<(), ClientError> { + // Don't care. + Ok(()) + } +} diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index 419984122ab..6c94b3e56cf 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -5,6 +5,7 @@ mod dnd; mod event_processor; mod events; pub mod ffi; +mod ime; mod monitor; pub mod util; mod window; @@ -20,7 +21,7 @@ pub use self::xdisplay::{XError, XNotSupported}; use std::{ cell::{Cell, RefCell}, - collections::{HashMap, HashSet, VecDeque}, + collections::{HashMap, HashSet}, fmt, ops::Deref, rc::Rc, @@ -309,7 +310,6 @@ impl EventLoop { first_touch: None, active_window: None, is_composing: false, - event_queue: VecDeque::new(), }; // Register for device hotplug events diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index bf840f0c07a..3ce6d8fdbd5 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -1,21 +1,23 @@ use std::{ - collections::HashMap, + collections::{HashMap, VecDeque}, error::Error, fmt, mem::ManuallyDrop, os::raw::c_int, ptr::{self, NonNull}, - sync::Mutex, + sync::{Arc, Mutex}, }; +use x11rb::connection::{Connection, RequestConnection}; +use x11rb::protocol::{xproto, Event}; use x11rb::resource_manager; use x11rb::xcb_ffi::XCBConnection; -use x11rb::{connection::Connection, protocol::xproto}; use crate::window::CursorIcon; use super::atoms::*; use super::ffi; +use super::ime::ImeData; /// A connection to an X server. pub(crate) struct XConnection { @@ -32,7 +34,7 @@ pub(crate) struct XConnection { /// /// We have to wrap the connection in a `ManuallyDrop` because the `XCBConnection` type /// needs to be dropped before the Xlib `Display` is dropped. - pub connection: ManuallyDrop, + pub connection: ManuallyDrop>, /// The X11 XRM database. pub database: resource_manager::Database, @@ -57,6 +59,12 @@ pub(crate) struct XConnection { /// The name of the window manager. pub wm_name: Mutex>, + + /// Information related to input method handling. + pub(super) ime_data: Option, + + /// The queue of events that we've received from the X server but haven't acted on yet. + pub(super) event_queue: Mutex>, } unsafe impl Send for XConnection {} @@ -104,17 +112,35 @@ impl XConnection { } // Create the x11rb wrapper. - unsafe { XCBConnection::from_raw_xcb_connection(raw_conn, false) } - .expect("Failed to create x11rb connection") + let wrapper = unsafe { XCBConnection::from_raw_xcb_connection(raw_conn, false) } + .expect("Failed to create x11rb connection"); + + // Put it in an `Arc` to pass it around more easily. + Arc::new(wrapper) }; + // Prefetch the extensions that we'll use. + macro_rules! prefetch { + ($($ext:ident),*) => { + $( + connection.prefetch_extension_information(x11rb::protocol::$ext::X11_EXTENSION_NAME) + .expect(concat!("Failed to prefetch ", stringify!($ext))); + )* + } + } + + prefetch!(randr, xinput); + // Begin loading the atoms. let atom_cookie = Atoms::request(&connection).expect("Failed to load atoms"); // Load the resource manager database. - let database = resource_manager::new_from_default(&connection) + let database = resource_manager::new_from_default(&*connection) .expect("Failed to load resource manager database"); + // Load the input method data. + let ime_data = ImeData::new(&connection, default_screen).ok(); + // Finish loading the atoms. let atoms = atom_cookie.reply().expect("Failed to load atoms"); @@ -131,6 +157,8 @@ impl XConnection { cursor_cache: Default::default(), supported_hints: Mutex::new(Vec::new()), wm_name: Mutex::new(None), + ime_data, + event_queue: Mutex::new(VecDeque::new()), }) }