diff --git a/Cargo.toml b/Cargo.toml index 45916253b2..b025833557 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -20,8 +20,9 @@ targets = ["i686-pc-windows-msvc", "x86_64-pc-windows-msvc", "i686-unknown-linux default = ["x11", "wayland"] web-sys = ["web_sys", "wasm-bindgen", "instant/wasm-bindgen"] stdweb = ["std_web", "instant/stdweb"] -x11 = ["x11-dl", "mio", "mio-misc", "percent-encoding", "parking_lot"] -wayland = ["wayland-client", "sctk"] +x11 = ["mio", "mio-misc", "percent-encoding", "parking_lot", "xcb-dl", "xcb-dl-util", "thiserror"] +xlib = ["x11", "x11-dl"] +wayland = ["wayland-client", "sctk", "memmap2"] [dependencies] instant = "0.1" @@ -31,6 +32,7 @@ log = "0.4" serde = { version = "1", optional = true, features = ["serde_derive"] } raw-window-handle = "0.3" bitflags = "1" +nameof = "1" mint = { version = "0.5.6", optional = true } [dev-dependencies] @@ -59,6 +61,7 @@ features = ["display_link"] [target.'cfg(target_os = "windows")'.dependencies] parking_lot = "0.11" +unicode-segmentation = "1.7.1" [target.'cfg(target_os = "windows")'.dependencies.winapi] version = "0.3.6" @@ -82,6 +85,7 @@ features = [ "winerror", "wingdi", "winnt", + "winnls", "winuser", ] @@ -90,9 +94,14 @@ wayland-client = { version = "0.28", features = [ "dlopen"] , optional = true } sctk = { package = "smithay-client-toolkit", version = "0.12.3", optional = true } mio = { version = "0.7", features = ["os-ext"], optional = true } mio-misc = { version = "1.0", optional = true } -x11-dl = { version = "2.18.5", optional = true } +xcb-dl = { version = "0.2.0", optional = true } +x11-dl = { version = "2.19.1", optional = true } +xcb-dl-util = { version = "0.2.0", features = ["xcb_xfixes", "xcb_xinput", "xcb_xkb", "xcb_render", "xcb_randr"], optional = true } +thiserror = { version = "1.0.30", optional = true } percent-encoding = { version = "2.0", optional = true } parking_lot = { version = "0.11.0", optional = true } +memmap2 = { version = "0.2.1", optional = true } +xkbcommon-dl = { git = "https://github.com/mahkoh/xkbcommon-dl", rev = "dd6a9033b1e45a2668700f14c2d47e48aeb3194f" } [target.'cfg(target_arch = "wasm32")'.dependencies.web_sys] package = "web-sys" diff --git a/examples/control_flow.rs b/examples/control_flow.rs index 64372a9bc8..6e876a3eaf 100644 --- a/examples/control_flow.rs +++ b/examples/control_flow.rs @@ -2,8 +2,9 @@ use std::{thread, time}; use simple_logger::SimpleLogger; use winit::{ - event::{Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::Key, window::WindowBuilder, }; @@ -38,7 +39,7 @@ fn main() { let mut close_requested = false; event_loop.run(move |event, _, control_flow| { - use winit::event::{ElementState, StartCause, VirtualKeyCode}; + use winit::event::StartCause; println!("{:?}", event); match event { Event::NewEvents(start_cause) => { @@ -52,31 +53,33 @@ fn main() { close_requested = true; } WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), + event: + KeyEvent { + logical_key: key, state: ElementState::Pressed, .. }, .. - } => match virtual_code { - VirtualKeyCode::Key1 => { + } => match key { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + Key::Character("1") => { mode = Mode::Wait; println!("\nmode: {:?}\n", mode); } - VirtualKeyCode::Key2 => { + Key::Character("2") => { mode = Mode::WaitUntil; println!("\nmode: {:?}\n", mode); } - VirtualKeyCode::Key3 => { + Key::Character("3") => { mode = Mode::Poll; println!("\nmode: {:?}\n", mode); } - VirtualKeyCode::R => { + Key::Character("r") => { request_redraw = !request_redraw; println!("\nrequest_redraw: {}\n", request_redraw); } - VirtualKeyCode::Escape => { + Key::Escape => { close_requested = true; } _ => (), diff --git a/examples/cursor.rs b/examples/cursor.rs index a466e889a8..2b6fe5b207 100644 --- a/examples/cursor.rs +++ b/examples/cursor.rs @@ -1,6 +1,6 @@ use simple_logger::SimpleLogger; use winit::{ - event::{ElementState, Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::{CursorIcon, WindowBuilder}, }; @@ -21,8 +21,8 @@ fn main() { Event::WindowEvent { event: WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Pressed, .. }, diff --git a/examples/cursor_grab.rs b/examples/cursor_grab.rs index 90a94764de..746bf426ba 100644 --- a/examples/cursor_grab.rs +++ b/examples/cursor_grab.rs @@ -1,7 +1,8 @@ use simple_logger::SimpleLogger; use winit::{ - event::{DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, WindowEvent}, + event::{DeviceEvent, ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, ModifiersState}, window::WindowBuilder, }; @@ -23,19 +24,23 @@ fn main() { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { + logical_key: key, state: ElementState::Released, - virtual_keycode: Some(key), .. }, .. } => { - use winit::event::VirtualKeyCode::*; + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example match key { - Escape => *control_flow = ControlFlow::Exit, - G => window.set_cursor_grab(!modifiers.shift()).unwrap(), - H => window.set_cursor_visible(modifiers.shift()), + Key::Escape => *control_flow = ControlFlow::Exit, + Key::Character(ch) => match ch.to_lowercase().as_str() { + "g" => window.set_cursor_grab(!modifiers.shift_key()).unwrap(), + "h" => window.set_cursor_visible(modifiers.shift_key()), + _ => (), + }, _ => (), } } diff --git a/examples/drag_window.rs b/examples/drag_window.rs index a408c7c722..58920a0928 100644 --- a/examples/drag_window.rs +++ b/examples/drag_window.rs @@ -1,9 +1,8 @@ use simple_logger::SimpleLogger; use winit::{ - event::{ - ElementState, Event, KeyboardInput, MouseButton, StartCause, VirtualKeyCode, WindowEvent, - }, + event::{ElementState, Event, KeyEvent, MouseButton, StartCause, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::Key, window::{Window, WindowBuilder, WindowId}, }; @@ -43,10 +42,10 @@ fn main() { name_windows(entered_id, switched, &window_1, &window_2) } WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Released, - virtual_keycode: Some(VirtualKeyCode::X), + logical_key: Key::Character("x"), .. }, .. diff --git a/examples/fullscreen.rs b/examples/fullscreen.rs index 83fbde30db..4566632be6 100644 --- a/examples/fullscreen.rs +++ b/examples/fullscreen.rs @@ -1,8 +1,9 @@ use std::io::{stdin, stdout, Write}; use simple_logger::SimpleLogger; -use winit::event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; +use winit::event::{ElementState, Event, KeyEvent, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; +use winit::keyboard::Key; use winit::monitor::{MonitorHandle, VideoMode}; use winit::window::{Fullscreen, WindowBuilder}; @@ -38,30 +39,33 @@ fn main() { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), - state, + event: + KeyEvent { + logical_key: key, + state: ElementState::Pressed, .. }, .. - } => match (virtual_code, state) { - (VirtualKeyCode::Escape, _) => *control_flow = ControlFlow::Exit, - (VirtualKeyCode::F, ElementState::Pressed) => { + } => match key { + Key::Escape => *control_flow = ControlFlow::Exit, + + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + Key::Character("f") => { if window.fullscreen().is_some() { window.set_fullscreen(None); } else { window.set_fullscreen(fullscreen.clone()); } } - (VirtualKeyCode::S, ElementState::Pressed) => { + Key::Character("s") => { println!("window.fullscreen {:?}", window.fullscreen()); } - (VirtualKeyCode::M, ElementState::Pressed) => { + Key::Character("m") => { let is_maximized = window.is_maximized(); window.set_maximized(!is_maximized); } - (VirtualKeyCode::D, ElementState::Pressed) => { + Key::Character("d") => { decorations = !decorations; window.set_decorations(decorations); } diff --git a/examples/handling_close.rs b/examples/handling_close.rs index 8334c1773f..283cf48195 100644 --- a/examples/handling_close.rs +++ b/examples/handling_close.rs @@ -1,7 +1,8 @@ use simple_logger::SimpleLogger; use winit::{ - event::{Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::Key, window::WindowBuilder, }; @@ -17,10 +18,6 @@ fn main() { let mut close_requested = false; event_loop.run(move |event, _, control_flow| { - use winit::event::{ - ElementState::Released, - VirtualKeyCode::{N, Y}, - }; *control_flow = ControlFlow::Wait; match event { @@ -44,16 +41,18 @@ fn main() { // the Y key. } WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(virtual_code), - state: Released, + event: + KeyEvent { + logical_key: key, + state: ElementState::Released, .. }, .. } => { - match virtual_code { - Y => { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + match key { + Key::Character("y") => { if close_requested { // This is where you'll want to do any cleanup you need. println!("Buh-bye!"); @@ -66,7 +65,7 @@ fn main() { *control_flow = ControlFlow::Exit; } } - N => { + Key::Character("n") => { if close_requested { println!("Your window will continue to stay by your side."); close_requested = false; diff --git a/examples/key_binding.rs b/examples/key_binding.rs new file mode 100644 index 0000000000..6f9bb1fe83 --- /dev/null +++ b/examples/key_binding.rs @@ -0,0 +1,58 @@ +use simple_logger::SimpleLogger; +use winit::{ + dpi::LogicalSize, + event::{ElementState, Event, KeyEvent, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, ModifiersState}, + window::WindowBuilder, +}; + +///////////////////////////////////////////////////////////////////////////// +// WARNING: This is not available on all platforms (for example on the web). +use winit::platform::modifier_supplement::KeyEventExtModifierSupplement; +///////////////////////////////////////////////////////////////////////////// + +fn main() { + SimpleLogger::new().init().unwrap(); + let event_loop = EventLoop::new(); + + let _window = WindowBuilder::new() + .with_inner_size(LogicalSize::new(400.0, 200.0)) + .build(&event_loop) + .unwrap(); + + let mut modifiers = ModifiersState::default(); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { event, .. } => match event { + WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, + WindowEvent::ModifiersChanged(new_state) => { + modifiers = new_state; + } + WindowEvent::KeyboardInput { event, .. } => { + handle_key_event(modifiers, event); + } + _ => (), + }, + _ => (), + }; + }); +} + +fn handle_key_event(modifiers: ModifiersState, event: KeyEvent) { + if event.state == ElementState::Pressed && !event.repeat { + match event.key_without_modifiers() { + Key::Character("1") => { + if modifiers.shift_key() { + println!("Shift + 1 | logical_key: {:?}", event.logical_key); + } else { + println!("1"); + } + } + _ => (), + } + } +} diff --git a/examples/minimize.rs b/examples/minimize.rs index eb02a752c9..3c8b7f84ed 100644 --- a/examples/minimize.rs +++ b/examples/minimize.rs @@ -1,8 +1,10 @@ extern crate winit; use simple_logger::SimpleLogger; -use winit::event::{Event, VirtualKeyCode, WindowEvent}; + +use winit::event::{Event, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; +use winit::keyboard::Key; use winit::window::WindowBuilder; fn main() { @@ -25,12 +27,14 @@ fn main() { // Keyboard input event to handle minimize via a hotkey Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, + event: WindowEvent::KeyboardInput { event, .. }, window_id, } => { if window_id == window.id() { - // Pressing the 'M' key will minimize the window - if input.virtual_keycode == Some(VirtualKeyCode::M) { + // Pressing the 'm' key will minimize the window + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + if let Key::Character("m") = event.logical_key { window.set_minimized(true); } } diff --git a/examples/multithreaded.rs b/examples/multithreaded.rs index 68cdb60b78..00235b114c 100644 --- a/examples/multithreaded.rs +++ b/examples/multithreaded.rs @@ -5,8 +5,9 @@ fn main() { use simple_logger::SimpleLogger; use winit::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, ModifiersState}, window::{CursorIcon, Fullscreen, WindowBuilder}, }; @@ -27,6 +28,7 @@ fn main() { let (tx, rx) = mpsc::channel(); window_senders.insert(window.id(), tx); + let mut modifiers = ModifiersState::default(); thread::spawn(move || { while let Ok(event) = rx.recv() { match event { @@ -49,32 +51,92 @@ fn main() { ); } } - #[allow(deprecated)] + WindowEvent::ModifiersChanged(mod_state) => { + modifiers = mod_state; + } WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Released, - virtual_keycode: Some(key), - modifiers, + logical_key: key, .. }, .. } => { + use Key::{ArrowLeft, ArrowRight, Character}; window.set_title(&format!("{:?}", key)); - let state = !modifiers.shift(); - use VirtualKeyCode::*; - match key { - A => window.set_always_on_top(state), - C => window.set_cursor_icon(match state { - true => CursorIcon::Progress, - false => CursorIcon::Default, - }), - D => window.set_decorations(!state), - // Cycle through video modes - Right | Left => { - video_mode_id = match key { - Left => video_mode_id.saturating_sub(1), - Right => (video_modes.len() - 1).min(video_mode_id + 1), + let state = !modifiers.shift_key(); + match &key { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + Character(string) => match string.to_lowercase().as_str() { + "a" => window.set_always_on_top(state), + "c" => window.set_cursor_icon(match state { + true => CursorIcon::Progress, + false => CursorIcon::Default, + }), + "d" => window.set_decorations(!state), + "f" => window.set_fullscreen(match (state, modifiers.alt_key()) { + (true, false) => Some(Fullscreen::Borderless(None)), + (true, true) => Some(Fullscreen::Exclusive( + video_modes.iter().nth(video_mode_id).unwrap().clone(), + )), + (false, _) => None, + }), + "g" => window.set_cursor_grab(state).unwrap(), + "h" => window.set_cursor_visible(!state), + "i" => { + println!("Info:"); + println!("-> outer_position : {:?}", window.outer_position()); + println!("-> inner_position : {:?}", window.inner_position()); + println!("-> outer_size : {:?}", window.outer_size()); + println!("-> inner_size : {:?}", window.inner_size()); + println!("-> fullscreen : {:?}", window.fullscreen()); + } + "l" => window.set_min_inner_size(match state { + true => Some(WINDOW_SIZE), + false => None, + }), + "m" => window.set_maximized(state), + "p" => window.set_outer_position({ + let mut position = window.outer_position().unwrap(); + let sign = if state { 1 } else { -1 }; + position.x += 10 * sign; + position.y += 10 * sign; + position + }), + "q" => window.request_redraw(), + "r" => window.set_resizable(state), + "s" => window.set_inner_size(match state { + true => PhysicalSize::new( + WINDOW_SIZE.width + 100, + WINDOW_SIZE.height + 100, + ), + false => WINDOW_SIZE, + }), + "w" => { + if let Size::Physical(size) = WINDOW_SIZE.into() { + window + .set_cursor_position(Position::Physical( + PhysicalPosition::new( + size.width as i32 / 2, + size.height as i32 / 2, + ), + )) + .unwrap() + } + } + "z" => { + window.set_visible(false); + thread::sleep(Duration::from_secs(1)); + window.set_visible(true); + } + _ => (), + }, + ArrowRight | ArrowLeft => { + video_mode_id = match &key { + ArrowLeft => video_mode_id.saturating_sub(1), + ArrowRight => (video_modes.len() - 1).min(video_mode_id + 1), _ => unreachable!(), }; println!( @@ -82,61 +144,6 @@ fn main() { video_modes.iter().nth(video_mode_id).unwrap() ); } - F => window.set_fullscreen(match (state, modifiers.alt()) { - (true, false) => Some(Fullscreen::Borderless(None)), - (true, true) => Some(Fullscreen::Exclusive( - video_modes.iter().nth(video_mode_id).unwrap().clone(), - )), - (false, _) => None, - }), - G => window.set_cursor_grab(state).unwrap(), - H => window.set_cursor_visible(!state), - I => { - println!("Info:"); - println!("-> outer_position : {:?}", window.outer_position()); - println!("-> inner_position : {:?}", window.inner_position()); - println!("-> outer_size : {:?}", window.outer_size()); - println!("-> inner_size : {:?}", window.inner_size()); - println!("-> fullscreen : {:?}", window.fullscreen()); - } - L => window.set_min_inner_size(match state { - true => Some(WINDOW_SIZE), - false => None, - }), - M => window.set_maximized(state), - P => window.set_outer_position({ - let mut position = window.outer_position().unwrap(); - let sign = if state { 1 } else { -1 }; - position.x += 10 * sign; - position.y += 10 * sign; - position - }), - Q => window.request_redraw(), - R => window.set_resizable(state), - S => window.set_inner_size(match state { - true => PhysicalSize::new( - WINDOW_SIZE.width + 100, - WINDOW_SIZE.height + 100, - ), - false => WINDOW_SIZE, - }), - W => { - if let Size::Physical(size) = WINDOW_SIZE.into() { - window - .set_cursor_position(Position::Physical( - PhysicalPosition::new( - size.width as i32 / 2, - size.height as i32 / 2, - ), - )) - .unwrap() - } - } - Z => { - window.set_visible(false); - thread::sleep(Duration::from_secs(1)); - window.set_visible(true); - } _ => (), } } @@ -155,10 +162,10 @@ fn main() { WindowEvent::CloseRequested | WindowEvent::Destroyed | WindowEvent::KeyboardInput { - input: - KeyboardInput { + event: + KeyEvent { state: ElementState::Released, - virtual_keycode: Some(VirtualKeyCode::Escape), + logical_key: Key::Escape, .. }, .. diff --git a/examples/multiwindow.rs b/examples/multiwindow.rs index ec97eee097..924a55296b 100644 --- a/examples/multiwindow.rs +++ b/examples/multiwindow.rs @@ -2,7 +2,7 @@ use std::collections::HashMap; use simple_logger::SimpleLogger; use winit::{ - event::{ElementState, Event, KeyboardInput, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::Window, }; @@ -34,9 +34,9 @@ fn main() { } } WindowEvent::KeyboardInput { - input: - KeyboardInput { - state: ElementState::Pressed, + event: + KeyEvent { + state: ElementState::Released, .. }, .. diff --git a/examples/resizable.rs b/examples/resizable.rs index 17892d8741..8d3387cabb 100644 --- a/examples/resizable.rs +++ b/examples/resizable.rs @@ -1,8 +1,9 @@ use simple_logger::SimpleLogger; use winit::{ dpi::LogicalSize, - event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{ElementState, Event, KeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::KeyCode, window::WindowBuilder, }; @@ -26,9 +27,9 @@ fn main() { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::KeyboardInput { - input: - KeyboardInput { - virtual_keycode: Some(VirtualKeyCode::Space), + event: + KeyEvent { + physical_key: KeyCode::Space, state: ElementState::Released, .. }, diff --git a/examples/window_debug.rs b/examples/window_debug.rs index 577ad5cd73..4a12cf47b9 100644 --- a/examples/window_debug.rs +++ b/examples/window_debug.rs @@ -3,8 +3,9 @@ use simple_logger::SimpleLogger; use winit::{ dpi::{LogicalSize, PhysicalSize}, - event::{DeviceEvent, ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, + event::{DeviceEvent, ElementState, Event, KeyEvent, RawKeyEvent, WindowEvent}, event_loop::{ControlFlow, EventLoop}, + keyboard::{Key, KeyCode}, window::{Fullscreen, WindowBuilder}, }; @@ -34,22 +35,24 @@ fn main() { *control_flow = ControlFlow::Wait; match event { + // This used to use the virtual key, but the new API + // only provides the `physical_key` (`Code`). Event::DeviceEvent { event: - DeviceEvent::Key(KeyboardInput { - virtual_keycode: Some(key), - state: ElementState::Pressed, + DeviceEvent::Key(RawKeyEvent { + physical_key, + state: ElementState::Released, .. }), .. - } => match key { - VirtualKeyCode::M => { + } => match physical_key { + KeyCode::KeyM => { if minimized { minimized = !minimized; window.set_minimized(minimized); } } - VirtualKeyCode::V => { + KeyCode::KeyV => { if !visible { visible = !visible; window.set_visible(visible); @@ -58,61 +61,65 @@ fn main() { _ => (), }, Event::WindowEvent { - event: WindowEvent::KeyboardInput { input, .. }, + event: + WindowEvent::KeyboardInput { + event: + KeyEvent { + logical_key: Key::Character(key_str), + state: ElementState::Released, + .. + }, + .. + }, .. - } => match input { - KeyboardInput { - virtual_keycode: Some(key), - state: ElementState::Pressed, - .. - } => match key { - VirtualKeyCode::E => { - fn area(size: PhysicalSize) -> u32 { - size.width * size.height - } - - let monitor = window.current_monitor().unwrap(); - if let Some(mode) = monitor - .video_modes() - .max_by(|a, b| area(a.size()).cmp(&area(b.size()))) - { - window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); - } else { - eprintln!("no video modes available"); - } - } - VirtualKeyCode::F => { - if window.fullscreen().is_some() { - window.set_fullscreen(None); - } else { - let monitor = window.current_monitor(); - window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); - } - } - VirtualKeyCode::P => { - if window.fullscreen().is_some() { - window.set_fullscreen(None); - } else { - window.set_fullscreen(Some(Fullscreen::Borderless(None))); - } + } => match key_str { + // WARNING: Consider using `key_without_modifers()` if available on your platform. + // See the `key_binding` example + "e" => { + fn area(size: PhysicalSize) -> u32 { + size.width * size.height } - VirtualKeyCode::M => { - minimized = !minimized; - window.set_minimized(minimized); - } - VirtualKeyCode::Q => { - *control_flow = ControlFlow::Exit; + + let monitor = window.current_monitor().unwrap(); + if let Some(mode) = monitor + .video_modes() + .max_by(|a, b| area(a.size()).cmp(&area(b.size()))) + { + window.set_fullscreen(Some(Fullscreen::Exclusive(mode))); + } else { + eprintln!("no video modes available"); } - VirtualKeyCode::V => { - visible = !visible; - window.set_visible(visible); + } + "f" => { + if window.fullscreen().is_some() { + window.set_fullscreen(None); + } else { + let monitor = window.current_monitor(); + window.set_fullscreen(Some(Fullscreen::Borderless(monitor))); } - VirtualKeyCode::X => { - let is_maximized = window.is_maximized(); - window.set_maximized(!is_maximized); + } + "p" => { + if window.fullscreen().is_some() { + window.set_fullscreen(None); + } else { + window.set_fullscreen(Some(Fullscreen::Borderless(None))); } - _ => (), - }, + } + "m" => { + minimized = !minimized; + window.set_minimized(minimized); + } + "q" => { + *control_flow = ControlFlow::Exit; + } + "v" => { + visible = !visible; + window.set_visible(visible); + } + "x" => { + let is_maximized = window.is_maximized(); + window.set_maximized(!is_maximized); + } _ => (), }, Event::WindowEvent { diff --git a/src/event.rs b/src/event.rs index f39ddaac50..6bd9009a0b 100644 --- a/src/event.rs +++ b/src/event.rs @@ -38,6 +38,7 @@ use std::path::PathBuf; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, + keyboard::{self, ModifiersState}, platform_impl, window::{Theme, WindowId}, }; @@ -239,8 +240,12 @@ pub enum WindowEvent<'a> { /// hovered. HoveredFileCancelled, - /// The window received a unicode character. - ReceivedCharacter(char), + /// The user commited an IME string for this window. + /// + /// This is a temporary API until [#1497] gets completed. + /// + /// [#1497]: https://github.com/rust-windowing/winit/issues/1497 + ReceivedImeText(String), /// The window gained or lost focus. /// @@ -248,9 +253,15 @@ pub enum WindowEvent<'a> { Focused(bool), /// An event from the keyboard has been received. + /// + /// ## Platform-specific + /// - **Windows:** The shift key overrides NumLock. In other words, while shift is held down, + /// numpad keys act as if NumLock wasn't active. When this is used, the OS sends fake key + /// events which are not marked as `is_synthetic`. KeyboardInput { device_id: DeviceId, - input: KeyboardInput, + event: KeyEvent, + /// If `true`, the event was generated synthetically by winit /// in one of the following circumstances: /// @@ -365,18 +376,17 @@ impl Clone for WindowEvent<'static> { DroppedFile(file) => DroppedFile(file.clone()), HoveredFile(file) => HoveredFile(file.clone()), HoveredFileCancelled => HoveredFileCancelled, - ReceivedCharacter(c) => ReceivedCharacter(*c), + ReceivedImeText(s) => ReceivedImeText(s.clone()), Focused(f) => Focused(*f), KeyboardInput { device_id, - input, + event, is_synthetic, } => KeyboardInput { device_id: *device_id, - input: *input, + event: event.clone(), is_synthetic: *is_synthetic, }, - ModifiersChanged(modifiers) => ModifiersChanged(modifiers.clone()), #[allow(deprecated)] CursorMoved { @@ -456,15 +466,15 @@ impl<'a> WindowEvent<'a> { DroppedFile(file) => Some(DroppedFile(file)), HoveredFile(file) => Some(HoveredFile(file)), HoveredFileCancelled => Some(HoveredFileCancelled), - ReceivedCharacter(c) => Some(ReceivedCharacter(c)), + ReceivedImeText(s) => Some(ReceivedImeText(s)), Focused(focused) => Some(Focused(focused)), KeyboardInput { device_id, - input, + event, is_synthetic, } => Some(KeyboardInput { device_id, - input, + event, is_synthetic, }), ModifiersChanged(modifiers) => Some(ModifiersChanged(modifiers)), @@ -589,38 +599,82 @@ pub enum DeviceEvent { state: ElementState, }, - Key(KeyboardInput), + Key(RawKeyEvent), Text { codepoint: char, }, } -/// Describes a keyboard input event. -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +/// Describes a keyboard input as a raw device event. +/// +/// Note that holding down a key may produce repeated `RawKeyEvent`s. The +/// operating system doesn't provide information whether such an event is a +/// repeat or the initial keypress. An application may emulate this by, for +/// example keeping a Map/Set of pressed keys and determining whether a keypress +/// corresponds to an already pressed key. +#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub struct KeyboardInput { - /// Identifies the physical key pressed - /// - /// This should not change if the user adjusts the host's keyboard map. Use when the physical location of the - /// key is more important than the key's host GUI semantics, such as for movement controls in a first-person - /// game. - pub scancode: ScanCode, - +pub struct RawKeyEvent { + pub physical_key: keyboard::KeyCode, pub state: ElementState, +} - /// Identifies the semantic meaning of the key +/// Describes a keyboard input targeting a window. +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEvent { + /// Represents the position of a key independent of the currently active layout. + /// + /// It also uniquely identifies the physical key (i.e. it's mostly synonymous with a scancode). + /// The most prevalent use case for this is games. For example the default keys for the player + /// to move around might be the W, A, S, and D keys on a US layout. The position of these keys + /// is more important than their label, so they should map to Z, Q, S, and D on an "AZERTY" + /// layout. (This value is `KeyCode::KeyW` for the Z key on an AZERTY layout.) /// - /// Use when the semantics of the key are more important than the physical location of the key, such as when - /// implementing appropriate behavior for "page up." - pub virtual_keycode: Option, + /// Note that `Fn` and `FnLock` key events are not guaranteed to be emitted by `winit`. These + /// keys are usually handled at the hardware or OS level. + pub physical_key: keyboard::KeyCode, - /// Modifier keys active at the time of this input. + /// This value is affected by all modifiers except Ctrl. + /// + /// This has two use cases: + /// - Allows querying whether the current input is a Dead key. + /// - Allows handling key-bindings on platforms which don't + /// support [`key_without_modifiers`]. /// - /// This is tracked internally to avoid tracking errors arising from modifier key state changes when events from - /// this device are not being delivered to the application, e.g. due to keyboard focus being elsewhere. - #[deprecated = "Deprecated in favor of WindowEvent::ModifiersChanged"] - pub modifiers: ModifiersState, + /// ## Platform-specific + /// - **Web:** Dead keys might be reported as the real key instead + /// of `Dead` depending on the browser/OS. + /// + /// [`key_without_modifiers`]: crate::platform::modifier_supplement::KeyEventExtModifierSupplement::key_without_modifiers + pub logical_key: keyboard::Key<'static>, + + /// Contains the text produced by this keypress. + /// + /// In most cases this is identical to the content + /// of the `Character` variant of `logical_key`. + /// However, on Windows when a dead key was pressed earlier + /// but cannot be combined with the character from this + /// keypress, the produced text will consist of two characters: + /// the dead-key-character followed by the character resulting + /// from this keypress. + /// + /// An additional difference from `logical_key` is that + /// this field stores the text representation of any key + /// that has such a representation. For example when + /// `logical_key` is `Key::Enter`, this field is `Some("\r")`. + /// + /// This is `None` if the current keypress cannot + /// be interpreted as text. + /// + /// See also: `text_with_all_modifiers()` + pub text: Option<&'static str>, + + pub location: keyboard::KeyLocation, + pub state: ElementState, + pub repeat: bool, + + pub(crate) platform_specific: platform_impl::KeyEventExtra, } /// Describes touch-screen input state. @@ -721,9 +775,6 @@ impl Force { } } -/// Hardware-dependent keyboard scan code. -pub type ScanCode = u32; - /// Identifier for a specific analog axis on some device. pub type AxisId = u32; @@ -766,303 +817,3 @@ pub enum MouseScrollDelta { /// platform. PixelDelta(PhysicalPosition), } - -/// Symbolic name for a keyboard key. -#[derive(Debug, Hash, Ord, PartialOrd, PartialEq, Eq, Clone, Copy)] -#[repr(u32)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -pub enum VirtualKeyCode { - /// The '1' key over the letters. - Key1, - /// The '2' key over the letters. - Key2, - /// The '3' key over the letters. - Key3, - /// The '4' key over the letters. - Key4, - /// The '5' key over the letters. - Key5, - /// The '6' key over the letters. - Key6, - /// The '7' key over the letters. - Key7, - /// The '8' key over the letters. - Key8, - /// The '9' key over the letters. - Key9, - /// The '0' key over the 'O' and 'P' keys. - Key0, - - A, - B, - C, - D, - E, - F, - G, - H, - I, - J, - K, - L, - M, - N, - O, - P, - Q, - R, - S, - T, - U, - V, - W, - X, - Y, - Z, - - /// The Escape key, next to F1. - Escape, - - F1, - F2, - F3, - F4, - F5, - F6, - F7, - F8, - F9, - F10, - F11, - F12, - F13, - F14, - F15, - F16, - F17, - F18, - F19, - F20, - F21, - F22, - F23, - F24, - - /// Print Screen/SysRq. - Snapshot, - /// Scroll Lock. - Scroll, - /// Pause/Break key, next to Scroll lock. - Pause, - - /// `Insert`, next to Backspace. - Insert, - Home, - Delete, - End, - PageDown, - PageUp, - - Left, - Up, - Right, - Down, - - /// The Backspace key, right over Enter. - // TODO: rename - Back, - /// The Enter key. - Return, - /// The space bar. - Space, - - /// The "Compose" key on Linux. - Compose, - - Caret, - - Numlock, - Numpad0, - Numpad1, - Numpad2, - Numpad3, - Numpad4, - Numpad5, - Numpad6, - Numpad7, - Numpad8, - Numpad9, - NumpadAdd, - NumpadDivide, - NumpadDecimal, - NumpadComma, - NumpadEnter, - NumpadEquals, - NumpadMultiply, - NumpadSubtract, - - AbntC1, - AbntC2, - Apostrophe, - Apps, - Asterisk, - At, - Ax, - Backslash, - Calculator, - Capital, - Colon, - Comma, - Convert, - Equals, - Grave, - Kana, - Kanji, - LAlt, - LBracket, - LControl, - LShift, - LWin, - Mail, - MediaSelect, - MediaStop, - Minus, - Mute, - MyComputer, - // also called "Next" - NavigateForward, - // also called "Prior" - NavigateBackward, - NextTrack, - NoConvert, - OEM102, - Period, - PlayPause, - Plus, - Power, - PrevTrack, - RAlt, - RBracket, - RControl, - RShift, - RWin, - Semicolon, - Slash, - Sleep, - Stop, - Sysrq, - Tab, - Underline, - Unlabeled, - VolumeDown, - VolumeUp, - Wake, - WebBack, - WebFavorites, - WebForward, - WebHome, - WebRefresh, - WebSearch, - WebStop, - Yen, - Copy, - Paste, - Cut, -} - -impl ModifiersState { - /// Returns `true` if the shift key is pressed. - pub fn shift(&self) -> bool { - self.intersects(Self::SHIFT) - } - /// Returns `true` if the control key is pressed. - pub fn ctrl(&self) -> bool { - self.intersects(Self::CTRL) - } - /// Returns `true` if the alt key is pressed. - pub fn alt(&self) -> bool { - self.intersects(Self::ALT) - } - /// Returns `true` if the logo key is pressed. - pub fn logo(&self) -> bool { - self.intersects(Self::LOGO) - } -} - -bitflags! { - /// Represents the current state of the keyboard modifiers - /// - /// Each flag represents a modifier and is set if this modifier is active. - #[derive(Default)] - pub struct ModifiersState: u32 { - // left and right modifiers are currently commented out, but we should be able to support - // them in a future release - /// The "shift" key. - const SHIFT = 0b100 << 0; - // const LSHIFT = 0b010 << 0; - // const RSHIFT = 0b001 << 0; - /// The "control" key. - const CTRL = 0b100 << 3; - // const LCTRL = 0b010 << 3; - // const RCTRL = 0b001 << 3; - /// The "alt" key. - const ALT = 0b100 << 6; - // const LALT = 0b010 << 6; - // const RALT = 0b001 << 6; - /// This is the "windows" key on PC and "command" key on Mac. - const LOGO = 0b100 << 9; - // const LLOGO = 0b010 << 9; - // const RLOGO = 0b001 << 9; - } -} - -#[cfg(feature = "serde")] -mod modifiers_serde { - use super::ModifiersState; - use serde::{Deserialize, Deserializer, Serialize, Serializer}; - - #[derive(Default, Serialize, Deserialize)] - #[serde(default)] - #[serde(rename = "ModifiersState")] - pub struct ModifiersStateSerialize { - pub shift: bool, - pub ctrl: bool, - pub alt: bool, - pub logo: bool, - } - - impl Serialize for ModifiersState { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - let s = ModifiersStateSerialize { - shift: self.shift(), - ctrl: self.ctrl(), - alt: self.alt(), - logo: self.logo(), - }; - s.serialize(serializer) - } - } - - impl<'de> Deserialize<'de> for ModifiersState { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let ModifiersStateSerialize { - shift, - ctrl, - alt, - logo, - } = ModifiersStateSerialize::deserialize(deserializer)?; - let mut m = ModifiersState::empty(); - m.set(ModifiersState::SHIFT, shift); - m.set(ModifiersState::CTRL, ctrl); - m.set(ModifiersState::ALT, alt); - m.set(ModifiersState::LOGO, logo); - Ok(m) - } - } -} diff --git a/src/keyboard.rs b/src/keyboard.rs new file mode 100644 index 0000000000..3aadb14435 --- /dev/null +++ b/src/keyboard.rs @@ -0,0 +1,1422 @@ +//! Types related to the keyboard. + +// This file contains a substantial portion of the UI Events Specification by the W3C. In +// particular, the variant names within `Key` and `KeyCode` and their documentation are modified +// versions of contents of the aforementioned specification. +// +// The original documents are: +// +// ### For `Key` +// UI Events KeyboardEvent key Values +// https://www.w3.org/TR/2017/CR-uievents-key-20170601/ +// Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). +// +// ### For `KeyCode` +// UI Events KeyboardEvent code Values +// https://www.w3.org/TR/2017/CR-uievents-code-20170601/ +// Copyright © 2017 W3C® (MIT, ERCIM, Keio, Beihang). +// +// These documents were used under the terms of the following license. This W3C license as well as +// the W3C short notice apply to the `Key` and `KeyCode` enums and their variants and the +// documentation attached to their variants. + +// --------- BEGGINING OF W3C LICENSE -------------------------------------------------------------- +// +// License +// +// By obtaining and/or copying this work, you (the licensee) agree that you have read, understood, +// and will comply with the following terms and conditions. +// +// Permission to copy, modify, and distribute this work, with or without modification, for any +// purpose and without fee or royalty is hereby granted, provided that you include the following on +// ALL copies of the work or portions thereof, including modifications: +// +// - The full text of this NOTICE in a location viewable to users of the redistributed or derivative +// work. +// - Any pre-existing intellectual property disclaimers, notices, or terms and conditions. If none +// exist, the W3C Software and Document Short Notice should be included. +// - Notice of any changes or modifications, through a copyright statement on the new code or +// document such as "This software or document includes material copied from or derived from +// [title and URI of the W3C document]. Copyright © [YEAR] W3C® (MIT, ERCIM, Keio, Beihang)." +// +// Disclaimers +// +// THIS WORK IS PROVIDED "AS IS," AND COPYRIGHT HOLDERS MAKE NO REPRESENTATIONS OR WARRANTIES, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO, WARRANTIES OF MERCHANTABILITY OR FITNESS FOR +// ANY PARTICULAR PURPOSE OR THAT THE USE OF THE SOFTWARE OR DOCUMENT WILL NOT INFRINGE ANY THIRD +// PARTY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. +// +// COPYRIGHT HOLDERS WILL NOT BE LIABLE FOR ANY DIRECT, INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES +// ARISING OUT OF ANY USE OF THE SOFTWARE OR DOCUMENT. +// +// The name and trademarks of copyright holders may NOT be used in advertising or publicity +// pertaining to the work without specific, written prior permission. Title to copyright in this +// work will at all times remain with copyright holders. +// +// --------- END OF W3C LICENSE -------------------------------------------------------------------- + +// --------- BEGGINING OF W3C SHORT NOTICE --------------------------------------------------------- +// +// winit: https://github.com/rust-windowing/winit +// +// Copyright © 2021 World Wide Web Consortium, (Massachusetts Institute of Technology, European +// Research Consortium for Informatics and Mathematics, Keio University, Beihang). All Rights +// Reserved. This work is distributed under the W3C® Software License [1] in the hope that it will +// be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or +// FITNESS FOR A PARTICULAR PURPOSE. +// +// [1] http://www.w3.org/Consortium/Legal/copyright-software +// +// --------- END OF W3C SHORT NOTICE --------------------------------------------------------------- + +use nameof::name_of; + +impl ModifiersState { + /// Returns `true` if the shift key is pressed. + pub fn shift_key(&self) -> bool { + self.intersects(Self::SHIFT) + } + /// Returns `true` if the control key is pressed. + pub fn control_key(&self) -> bool { + self.intersects(Self::CONTROL) + } + /// Returns `true` if the alt key is pressed. + pub fn alt_key(&self) -> bool { + self.intersects(Self::ALT) + } + /// Returns `true` if the super key is pressed. + pub fn super_key(&self) -> bool { + self.intersects(Self::SUPER) + } +} + +bitflags! { + /// Represents the current state of the keyboard modifiers + /// + /// Each flag represents a modifier and is set if this modifier is active. + #[derive(Default)] + pub struct ModifiersState: u32 { + // left and right modifiers are currently commented out, but we should be able to support + // them in a future release + /// The "shift" key. + const SHIFT = 0b100 << 0; + // const LSHIFT = 0b010 << 0; + // const RSHIFT = 0b001 << 0; + /// The "control" key. + const CONTROL = 0b100 << 3; + // const LCTRL = 0b010 << 3; + // const RCTRL = 0b001 << 3; + /// The "alt" key. + const ALT = 0b100 << 6; + // const LALT = 0b010 << 6; + // const RALT = 0b001 << 6; + /// This is the "windows" key on PC and "command" key on Mac. + const SUPER = 0b100 << 9; + // const LSUPER = 0b010 << 9; + // const RSUPER = 0b001 << 9; + } +} + +#[cfg(feature = "serde")] +mod modifiers_serde { + use super::ModifiersState; + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + + #[derive(Default, Serialize, Deserialize)] + #[serde(default)] + #[serde(rename = "ModifiersState")] + pub struct ModifiersStateSerialize { + pub shift_key: bool, + pub control_key: bool, + pub alt_key: bool, + pub super_key: bool, + } + + impl Serialize for ModifiersState { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let s = ModifiersStateSerialize { + shift_key: self.shift_key(), + control_key: self.control_key(), + alt_key: self.alt_key(), + super_key: self.super_key(), + }; + s.serialize(serializer) + } + } + + impl<'de> Deserialize<'de> for ModifiersState { + fn deserialize(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + let ModifiersStateSerialize { + shift_key, + control_key, + alt_key, + super_key, + } = ModifiersStateSerialize::deserialize(deserializer)?; + let mut m = ModifiersState::empty(); + m.set(ModifiersState::SHIFT, shift_key); + m.set(ModifiersState::CONTROL, control_key); + m.set(ModifiersState::ALT, alt_key); + m.set(ModifiersState::SUPER, super_key); + Ok(m) + } + } +} + +/// Contains the platform-native physical key identifier (aka scancode) +#[derive(Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum NativeKeyCode { + Unidentified, + Windows(u16), + MacOS(u32), + XkbCode(u32), + XkbSym(u32), +} +impl std::fmt::Debug for NativeKeyCode { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + use NativeKeyCode::{MacOS, Unidentified, Windows, XkbCode, XkbSym}; + let mut debug_tuple; + match self { + Unidentified => { + debug_tuple = f.debug_tuple(name_of!(Unidentified)); + } + Windows(v) => { + debug_tuple = f.debug_tuple(name_of!(Windows)); + debug_tuple.field(&format_args!("0x{:04X}", v)); + } + MacOS(v) => { + debug_tuple = f.debug_tuple(name_of!(MacOS)); + debug_tuple.field(v); + } + XkbCode(v) => { + debug_tuple = f.debug_tuple(name_of!(XkbCode)); + debug_tuple.field(&format_args!("0x{:04X}", v)); + } + XkbSym(v) => { + debug_tuple = f.debug_tuple(name_of!(XkbSym)); + debug_tuple.field(&format_args!("0x{:04X}", v)); + } + } + debug_tuple.finish() + } +} + +/// Represents the location of a physical key. +/// +/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.code`] with a few +/// exceptions: +/// - The keys that the specification calls "MetaLeft" and "MetaRight" are named "SuperLeft" and +/// "SuperRight" here. +/// - The key that the specification calls "Super" is reported as `Unidentified` here. +/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`. +/// +/// [`KeyboardEvent.code`]: https://w3c.github.io/uievents-code/#code-value-tables +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum KeyCode { + /// This variant is used when the key cannot be translated to any + /// other variant. + /// + /// The native scancode is provided (if available) in order + /// to allow the user to specify keybindings for keys which + /// are not defined by this API. + Unidentified(NativeKeyCode), + /// ` on a US keyboard. This is the 半角/全角/漢字 + /// (hankaku/zenkaku/kanji) key on Japanese keyboards + Backquote, + /// Used for both the US \\ (on the 101-key layout) and also for the key + /// located between the " and Enter keys on row C of the 102-, + /// 104- and 106-key layouts. + /// Labeled # on a UK (102) keyboard. + Backslash, + /// [ on a US keyboard. + BracketLeft, + /// ] on a US keyboard. + BracketRight, + /// , on a US keyboard. + Comma, + /// 0 on a US keyboard. + Digit0, + /// 1 on a US keyboard. + Digit1, + /// 2 on a US keyboard. + Digit2, + /// 3 on a US keyboard. + Digit3, + /// 4 on a US keyboard. + Digit4, + /// 5 on a US keyboard. + Digit5, + /// 6 on a US keyboard. + Digit6, + /// 7 on a US keyboard. + Digit7, + /// 8 on a US keyboard. + Digit8, + /// 9 on a US keyboard. + Digit9, + /// = on a US keyboard. + Equal, + /// Located between the left Shift and Z keys. + /// Labeled \\ on a UK keyboard. + IntlBackslash, + /// Located between the / and right Shift keys. + /// Labeled \\ (ro) on a Japanese keyboard. + IntlRo, + /// Located between the = and Backspace keys. + /// Labeled ¥ (yen) on a Japanese keyboard. \\ on a + /// Russian keyboard. + IntlYen, + /// a on a US keyboard. + /// Labeled q on an AZERTY (e.g., French) keyboard. + KeyA, + /// b on a US keyboard. + KeyB, + /// c on a US keyboard. + KeyC, + /// d on a US keyboard. + KeyD, + /// e on a US keyboard. + KeyE, + /// f on a US keyboard. + KeyF, + /// g on a US keyboard. + KeyG, + /// h on a US keyboard. + KeyH, + /// i on a US keyboard. + KeyI, + /// j on a US keyboard. + KeyJ, + /// k on a US keyboard. + KeyK, + /// l on a US keyboard. + KeyL, + /// m on a US keyboard. + KeyM, + /// n on a US keyboard. + KeyN, + /// o on a US keyboard. + KeyO, + /// p on a US keyboard. + KeyP, + /// q on a US keyboard. + /// Labeled a on an AZERTY (e.g., French) keyboard. + KeyQ, + /// r on a US keyboard. + KeyR, + /// s on a US keyboard. + KeyS, + /// t on a US keyboard. + KeyT, + /// u on a US keyboard. + KeyU, + /// v on a US keyboard. + KeyV, + /// w on a US keyboard. + /// Labeled z on an AZERTY (e.g., French) keyboard. + KeyW, + /// x on a US keyboard. + KeyX, + /// y on a US keyboard. + /// Labeled z on a QWERTZ (e.g., German) keyboard. + KeyY, + /// z on a US keyboard. + /// Labeled w on an AZERTY (e.g., French) keyboard, and y on a + /// QWERTZ (e.g., German) keyboard. + KeyZ, + /// - on a US keyboard. + Minus, + /// . on a US keyboard. + Period, + /// ' on a US keyboard. + Quote, + /// ; on a US keyboard. + Semicolon, + /// / on a US keyboard. + Slash, + /// Alt, Option, or . + AltLeft, + /// Alt, Option, or . + /// This is labeled AltGr on many keyboard layouts. + AltRight, + /// Backspace or . + /// Labeled Delete on Apple keyboards. + Backspace, + /// CapsLock or + CapsLock, + /// The application context menu key, which is typically found between the right + /// Super key and the right Control key. + ContextMenu, + /// Control or + ControlLeft, + /// Control or + ControlRight, + /// Enter or . Labeled Return on Apple keyboards. + Enter, + /// The Windows, , Command, or other OS symbol key. + SuperLeft, + /// The Windows, , Command, or other OS symbol key. + SuperRight, + /// Shift or + ShiftLeft, + /// Shift or + ShiftRight, + ///   (space) + Space, + /// Tab or + Tab, + /// Japanese: (henkan) + Convert, + /// Japanese: カタカナ/ひらがな/ローマ字 (katakana/hiragana/romaji) + KanaMode, + /// Korean: HangulMode 한/영 (han/yeong) + /// + /// Japanese (Mac keyboard): (kana) + Lang1, + /// Korean: Hanja (hanja) + /// + /// Japanese (Mac keyboard): (eisu) + Lang2, + /// Japanese (word-processing keyboard): Katakana + Lang3, + /// Japanese (word-processing keyboard): Hiragana + Lang4, + /// Japanese (word-processing keyboard): Zenkaku/Hankaku + Lang5, + /// Japanese: 無変換 (muhenkan) + NonConvert, + /// . The forward delete key. + /// Note that on Apple keyboards, the key labelled Delete on the main part of + /// the keyboard is encoded as [`Backspace`]. + /// + /// [`Backspace`]: Self::Backspace + Delete, + /// Page Down, End, or + End, + /// Help. Not present on standard PC keyboards. + Help, + /// Home or + Home, + /// Insert or Ins. Not present on Apple keyboards. + Insert, + /// Page Down, PgDn, or + PageDown, + /// Page Up, PgUp, or + PageUp, + /// + ArrowDown, + /// + ArrowLeft, + /// + ArrowRight, + /// + ArrowUp, + /// On the Mac, this is used for the numpad Clear key. + NumLock, + /// 0 Ins on a keyboard. 0 on a phone or remote control + Numpad0, + /// 1 End on a keyboard. 1 or 1 QZ on a phone or remote control + Numpad1, + /// 2 ↓ on a keyboard. 2 ABC on a phone or remote control + Numpad2, + /// 3 PgDn on a keyboard. 3 DEF on a phone or remote control + Numpad3, + /// 4 ← on a keyboard. 4 GHI on a phone or remote control + Numpad4, + /// 5 on a keyboard. 5 JKL on a phone or remote control + Numpad5, + /// 6 → on a keyboard. 6 MNO on a phone or remote control + Numpad6, + /// 7 Home on a keyboard. 7 PQRS or 7 PRS on a phone + /// or remote control + Numpad7, + /// 8 ↑ on a keyboard. 8 TUV on a phone or remote control + Numpad8, + /// 9 PgUp on a keyboard. 9 WXYZ or 9 WXY on a phone + /// or remote control + Numpad9, + /// + + NumpadAdd, + /// Found on the Microsoft Natural Keyboard. + NumpadBackspace, + /// C or A (All Clear). Also for use with numpads that have a + /// Clear key that is separate from the NumLock key. On the Mac, the + /// numpad Clear key is encoded as [`NumLock`]. + /// + /// [`NumLock`]: Self::NumLock + NumpadClear, + /// C (Clear Entry) + NumpadClearEntry, + /// , (thousands separator). For locales where the thousands separator + /// is a "." (e.g., Brazil), this key may generate a .. + NumpadComma, + /// . Del. For locales where the decimal separator is "," (e.g., + /// Brazil), this key may generate a ,. + NumpadDecimal, + /// / + NumpadDivide, + NumpadEnter, + /// = + NumpadEqual, + /// # on a phone or remote control device. This key is typically found + /// below the 9 key and to the right of the 0 key. + NumpadHash, + /// M Add current entry to the value stored in memory. + NumpadMemoryAdd, + /// M Clear the value stored in memory. + NumpadMemoryClear, + /// M Replace the current entry with the value stored in memory. + NumpadMemoryRecall, + /// M Replace the value stored in memory with the current entry. + NumpadMemoryStore, + /// M Subtract current entry from the value stored in memory. + NumpadMemorySubtract, + /// * on a keyboard. For use with numpads that provide mathematical + /// operations (+, - * and /). + /// + /// Use `NumpadStar` for the * key on phones and remote controls. + NumpadMultiply, + /// ( Found on the Microsoft Natural Keyboard. + NumpadParenLeft, + /// ) Found on the Microsoft Natural Keyboard. + NumpadParenRight, + /// * on a phone or remote control device. + /// + /// This key is typically found below the 7 key and to the left of + /// the 0 key. + /// + /// Use "NumpadMultiply" for the * key on + /// numeric keypads. + NumpadStar, + /// - + NumpadSubtract, + /// Esc or + Escape, + /// Fn This is typically a hardware key that does not generate a separate code. + Fn, + /// FLock or FnLock. Function Lock key. Found on the Microsoft + /// Natural Keyboard. + FnLock, + /// PrtScr SysRq or Print Screen + PrintScreen, + /// Scroll Lock + ScrollLock, + /// Pause Break + Pause, + /// Some laptops place this key to the left of the key. + BrowserBack, + BrowserFavorites, + /// Some laptops place this key to the right of the key. + BrowserForward, + BrowserHome, + BrowserRefresh, + BrowserSearch, + BrowserStop, + /// Eject or . This key is placed in the function section on some Apple + /// keyboards. + Eject, + /// Sometimes labelled My Computer on the keyboard + LaunchApp1, + /// Sometimes labelled Calculator on the keyboard + LaunchApp2, + LaunchMail, + MediaPlayPause, + MediaSelect, + MediaStop, + MediaTrackNext, + MediaTrackPrevious, + /// This key is placed in the function section on some Apple keyboards, replacing the + /// Eject key. + Power, + Sleep, + AudioVolumeDown, + AudioVolumeMute, + AudioVolumeUp, + WakeUp, + Hyper, + Turbo, + Abort, + Resume, + Suspend, + /// Found on Sun’s USB keyboard. + Again, + /// Found on Sun’s USB keyboard. + Copy, + /// Found on Sun’s USB keyboard. + Cut, + /// Found on Sun’s USB keyboard. + Find, + /// Found on Sun’s USB keyboard. + Open, + /// Found on Sun’s USB keyboard. + Paste, + /// Found on Sun’s USB keyboard. + Props, + /// Found on Sun’s USB keyboard. + Select, + /// Found on Sun’s USB keyboard. + Undo, + /// Use for dedicated ひらがな key found on some Japanese word processing keyboards. + Hiragana, + /// Use for dedicated カタカナ key found on some Japanese word processing keyboards. + Katakana, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F1, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F2, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F3, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F4, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F5, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F6, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F7, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F8, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F9, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F10, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F11, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F12, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F13, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F14, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F15, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F16, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F17, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F18, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F19, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F20, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F21, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F22, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F23, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F24, + /// General-purpose function key. + F25, + /// General-purpose function key. + F26, + /// General-purpose function key. + F27, + /// General-purpose function key. + F28, + /// General-purpose function key. + F29, + /// General-purpose function key. + F30, + /// General-purpose function key. + F31, + /// General-purpose function key. + F32, + /// General-purpose function key. + F33, + /// General-purpose function key. + F34, + /// General-purpose function key. + F35, +} + +/// Key represents the meaning of a keypress. +/// +/// This mostly conforms to the UI Events Specification's [`KeyboardEvent.key`] with a few +/// exceptions: +/// - The `Super` variant here, is named `Meta` in the aforementioned specification. (There's +/// another key which the specification calls `Super`. That does not exist here.) +/// - The `Space` variant here, can be identified by the character it generates in the +/// specificaiton. +/// - The `Unidentified` variant here, can still identifiy a key through it's `NativeKeyCode`. +/// - The `Dead` variant here, can specify the character which is inserted when pressing the +/// dead-key twice. +/// +/// [`KeyboardEvent.key`]: https://w3c.github.io/uievents-key/ +#[non_exhaustive] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum Key<'a> { + /// A key string that corresponds to the character typed by the user, taking into account the + /// user’s current locale setting, and any system-level keyboard mapping overrides that are in + /// effect. + Character(&'a str), + + /// This variant is used when the key cannot be translated to any other variant. + /// + /// The native scancode is provided (if available) in order to allow the user to specify + /// keybindings for keys which are not defined by this API. + Unidentified(NativeKeyCode), + + /// Contains the text representation of the dead-key when available. + /// + /// ## Platform-specific + /// - **Web:** Always contains `None` + Dead(Option), + + /// The `Alt` (Alternative) key. + /// + /// This key enables the alternate modifier function for interpreting concurrent or subsequent + /// keyboard input. This key value is also used for the Apple Option key. + Alt, + /// The Alternate Graphics (AltGr or AltGraph) key. + /// + /// This key is used enable the ISO Level 3 shift modifier (the standard `Shift` key is the + /// level 2 modifier). + AltGraph, + /// The `Caps Lock` (Capital) key. + /// + /// Toggle capital character lock function for interpreting subsequent keyboard input event. + CapsLock, + /// The `Control` or `Ctrl` key. + /// + /// Used to enable control modifier function for interpreting concurrent or subsequent keyboard + /// input. + Control, + /// The Function switch `Fn` key. Activating this key simultaneously with another key changes + /// that key’s value to an alternate character or function. This key is often handled directly + /// in the keyboard hardware and does not usually generate key events. + Fn, + /// The Function-Lock (`FnLock` or `F-Lock`) key. Activating this key switches the mode of the + /// keyboard to changes some keys' values to an alternate character or function. This key is + /// often handled directly in the keyboard hardware and does not usually generate key events. + FnLock, + /// The `NumLock` or Number Lock key. Used to toggle numpad mode function for interpreting + /// subsequent keyboard input. + NumLock, + /// Toggle between scrolling and cursor movement modes. + ScrollLock, + /// Used to enable shift modifier function for interpreting concurrent or subsequent keyboard + /// input. + Shift, + /// The Symbol modifier key (used on some virtual keyboards). + Symbol, + SymbolLock, + Hyper, + /// Used to enable "super" modifier function for interpreting concurrent or subsequent keyboard + /// input. This key value is used for the "Windows Logo" key and the Apple `Command` or `⌘` key. + /// + /// Note: In some contexts (e.g. the Web) this is referred to as the "Meta" key. + Super, + /// The `Enter` or `↵` key. Used to activate current selection or accept current input. This key + /// value is also used for the `Return` (Macintosh numpad) key. This key value is also used for + /// the Android `KEYCODE_DPAD_CENTER`. + Enter, + /// The Horizontal Tabulation `Tab` key. + Tab, + /// Used in text to insert a space between words. Usually located below the character keys. + Space, + /// Navigate or traverse downward. (`KEYCODE_DPAD_DOWN`) + ArrowDown, + /// Navigate or traverse leftward. (`KEYCODE_DPAD_LEFT`) + ArrowLeft, + /// Navigate or traverse rightward. (`KEYCODE_DPAD_RIGHT`) + ArrowRight, + /// Navigate or traverse upward. (`KEYCODE_DPAD_UP`) + ArrowUp, + /// The End key, used with keyboard entry to go to the end of content (`KEYCODE_MOVE_END`). + End, + /// The Home key, used with keyboard entry, to go to start of content (`KEYCODE_MOVE_HOME`). + /// For the mobile phone `Home` key (which goes to the phone’s main screen), use [`GoHome`]. + /// + /// [`GoHome`]: Self::GoHome + Home, + /// Scroll down or display next page of content. + PageDown, + /// Scroll up or display previous page of content. + PageUp, + /// Used to remove the character to the left of the cursor. This key value is also used for + /// the key labeled `Delete` on MacOS keyboards. + Backspace, + /// Remove the currently selected input. + Clear, + /// Copy the current selection. (`APPCOMMAND_COPY`) + Copy, + /// The Cursor Select key. + CrSel, + /// Cut the current selection. (`APPCOMMAND_CUT`) + Cut, + /// Used to delete the character to the right of the cursor. This key value is also used for the + /// key labeled `Delete` on MacOS keyboards when `Fn` is active. + Delete, + /// The Erase to End of Field key. This key deletes all characters from the current cursor + /// position to the end of the current field. + EraseEof, + /// The Extend Selection (Exsel) key. + ExSel, + /// Toggle between text modes for insertion or overtyping. + /// (`KEYCODE_INSERT`) + Insert, + /// The Paste key. (`APPCOMMAND_PASTE`) + Paste, + /// Redo the last action. (`APPCOMMAND_REDO`) + Redo, + /// Undo the last action. (`APPCOMMAND_UNDO`) + Undo, + /// The Accept (Commit, OK) key. Accept current option or input method sequence conversion. + Accept, + /// Redo or repeat an action. + Again, + /// The Attention (Attn) key. + Attn, + Cancel, + /// Show the application’s context menu. + /// This key is commonly found between the right `Super` key and the right `Control` key. + ContextMenu, + /// The `Esc` key. This key was originally used to initiate an escape sequence, but is + /// now more generally used to exit or "escape" the current context, such as closing a dialog + /// or exiting full screen mode. + Escape, + Execute, + /// Open the Find dialog. (`APPCOMMAND_FIND`) + Find, + /// Open a help dialog or toggle display of help information. (`APPCOMMAND_HELP`, + /// `KEYCODE_HELP`) + Help, + /// Pause the current state or application (as appropriate). + /// + /// Note: Do not use this value for the `Pause` button on media controllers. Use `"MediaPause"` + /// instead. + Pause, + /// Play or resume the current state or application (as appropriate). + /// + /// Note: Do not use this value for the `Play` button on media controllers. Use `"MediaPlay"` + /// instead. + Play, + /// The properties (Props) key. + Props, + Select, + /// The ZoomIn key. (`KEYCODE_ZOOM_IN`) + ZoomIn, + /// The ZoomOut key. (`KEYCODE_ZOOM_OUT`) + ZoomOut, + /// The Brightness Down key. Typically controls the display brightness. + /// (`KEYCODE_BRIGHTNESS_DOWN`) + BrightnessDown, + /// The Brightness Up key. Typically controls the display brightness. (`KEYCODE_BRIGHTNESS_UP`) + BrightnessUp, + /// Toggle removable media to eject (open) and insert (close) state. (`KEYCODE_MEDIA_EJECT`) + Eject, + LogOff, + /// Toggle power state. (`KEYCODE_POWER`) + /// Note: Note: Some devices might not expose this key to the operating environment. + Power, + /// The `PowerOff` key. Sometime called `PowerDown`. + PowerOff, + /// Initiate print-screen function. + PrintScreen, + /// The Hibernate key. This key saves the current state of the computer to disk so that it can + /// be restored. The computer will then shutdown. + Hibernate, + /// The Standby key. This key turns off the display and places the computer into a low-power + /// mode without completely shutting down. It is sometimes labelled `Suspend` or `Sleep` key. + /// (`KEYCODE_SLEEP`) + Standby, + /// The WakeUp key. (`KEYCODE_WAKEUP`) + WakeUp, + /// Initate the multi-candidate mode. + AllCandidates, + Alphanumeric, + /// Initiate the Code Input mode to allow characters to be entered by + /// their code points. + CodeInput, + /// The Compose key, also known as "Multi_key" on the X Window System. This key acts in a + /// manner similar to a dead key, triggering a mode where subsequent key presses are combined to + /// produce a different character. + Compose, + /// Convert the current input method sequence. + Convert, + /// The Final Mode `Final` key used on some Asian keyboards, to enable the final mode for IMEs. + FinalMode, + /// Switch to the first character group. (ISO/IEC 9995) + GroupFirst, + /// Switch to the last character group. (ISO/IEC 9995) + GroupLast, + /// Switch to the next character group. (ISO/IEC 9995) + GroupNext, + /// Switch to the previous character group. (ISO/IEC 9995) + GroupPrevious, + /// Toggle between or cycle through input modes of IMEs. + ModeChange, + NextCandidate, + /// Accept current input method sequence without + /// conversion in IMEs. + NonConvert, + PreviousCandidate, + Process, + SingleCandidate, + /// Toggle between Hangul and English modes. + HangulMode, + HanjaMode, + JunjaMode, + /// The Eisu key. This key may close the IME, but its purpose is defined by the current IME. + /// (`KEYCODE_EISU`) + Eisu, + /// The (Half-Width) Characters key. + Hankaku, + /// The Hiragana (Japanese Kana characters) key. + Hiragana, + /// The Hiragana/Katakana toggle key. (`KEYCODE_KATAKANA_HIRAGANA`) + HiraganaKatakana, + /// The Kana Mode (Kana Lock) key. This key is used to enter hiragana mode (typically from + /// romaji mode). + KanaMode, + /// The Kanji (Japanese name for ideographic characters of Chinese origin) Mode key. This key is + /// typically used to switch to a hiragana keyboard for the purpose of converting input into + /// kanji. (`KEYCODE_KANA`) + KanjiMode, + /// The Katakana (Japanese Kana characters) key. + Katakana, + /// The Roman characters function key. + Romaji, + /// The Zenkaku (Full-Width) Characters key. + Zenkaku, + /// The Zenkaku/Hankaku (full-width/half-width) toggle key. (`KEYCODE_ZENKAKU_HANKAKU`) + ZenkakuHankaku, + /// General purpose virtual function key, as index 1. + Soft1, + /// General purpose virtual function key, as index 2. + Soft2, + /// General purpose virtual function key, as index 3. + Soft3, + /// General purpose virtual function key, as index 4. + Soft4, + /// Select next (numerically or logically) lower channel. (`APPCOMMAND_MEDIA_CHANNEL_DOWN`, + /// `KEYCODE_CHANNEL_DOWN`) + ChannelDown, + /// Select next (numerically or logically) higher channel. (`APPCOMMAND_MEDIA_CHANNEL_UP`, + /// `KEYCODE_CHANNEL_UP`) + ChannelUp, + /// Close the current document or message (Note: This doesn’t close the application). + /// (`APPCOMMAND_CLOSE`) + Close, + /// Open an editor to forward the current message. (`APPCOMMAND_FORWARD_MAIL`) + MailForward, + /// Open an editor to reply to the current message. (`APPCOMMAND_REPLY_TO_MAIL`) + MailReply, + /// Send the current message. (`APPCOMMAND_SEND_MAIL`) + MailSend, + /// Close the current media, for example to close a CD or DVD tray. (`KEYCODE_MEDIA_CLOSE`) + MediaClose, + /// Initiate or continue forward playback at faster than normal speed, or increase speed if + /// already fast forwarding. (`APPCOMMAND_MEDIA_FAST_FORWARD`, `KEYCODE_MEDIA_FAST_FORWARD`) + MediaFastForward, + /// Pause the currently playing media. (`APPCOMMAND_MEDIA_PAUSE`, `KEYCODE_MEDIA_PAUSE`) + /// + /// Note: Media controller devices should use this value rather than `"Pause"` for their pause + /// keys. + MediaPause, + /// Initiate or continue media playback at normal speed, if not currently playing at normal + /// speed. (`APPCOMMAND_MEDIA_PLAY`, `KEYCODE_MEDIA_PLAY`) + MediaPlay, + /// Toggle media between play and pause states. (`APPCOMMAND_MEDIA_PLAY_PAUSE`, + /// `KEYCODE_MEDIA_PLAY_PAUSE`) + MediaPlayPause, + /// Initiate or resume recording of currently selected media. (`APPCOMMAND_MEDIA_RECORD`, + /// `KEYCODE_MEDIA_RECORD`) + MediaRecord, + /// Initiate or continue reverse playback at faster than normal speed, or increase speed if + /// already rewinding. (`APPCOMMAND_MEDIA_REWIND`, `KEYCODE_MEDIA_REWIND`) + MediaRewind, + /// Stop media playing, pausing, forwarding, rewinding, or recording, if not already stopped. + /// (`APPCOMMAND_MEDIA_STOP`, `KEYCODE_MEDIA_STOP`) + MediaStop, + /// Seek to next media or program track. (`APPCOMMAND_MEDIA_NEXTTRACK`, `KEYCODE_MEDIA_NEXT`) + MediaTrackNext, + /// Seek to previous media or program track. (`APPCOMMAND_MEDIA_PREVIOUSTRACK`, + /// `KEYCODE_MEDIA_PREVIOUS`) + MediaTrackPrevious, + /// Open a new document or message. (`APPCOMMAND_NEW`) + New, + /// Open an existing document or message. (`APPCOMMAND_OPEN`) + Open, + /// Print the current document or message. (`APPCOMMAND_PRINT`) + Print, + /// Save the current document or message. (`APPCOMMAND_SAVE`) + Save, + /// Spellcheck the current document or selection. (`APPCOMMAND_SPELL_CHECK`) + SpellCheck, + /// The `11` key found on media numpads that + /// have buttons from `1` ... `12`. + Key11, + /// The `12` key found on media numpads that + /// have buttons from `1` ... `12`. + Key12, + /// Adjust audio balance leftward. (`VK_AUDIO_BALANCE_LEFT`) + AudioBalanceLeft, + /// Adjust audio balance rightward. (`VK_AUDIO_BALANCE_RIGHT`) + AudioBalanceRight, + /// Decrease audio bass boost or cycle down through bass boost states. (`APPCOMMAND_BASS_DOWN`, + /// `VK_BASS_BOOST_DOWN`) + AudioBassBoostDown, + /// Toggle bass boost on/off. (`APPCOMMAND_BASS_BOOST`) + AudioBassBoostToggle, + /// Increase audio bass boost or cycle up through bass boost states. (`APPCOMMAND_BASS_UP`, + /// `VK_BASS_BOOST_UP`) + AudioBassBoostUp, + /// Adjust audio fader towards front. (`VK_FADER_FRONT`) + AudioFaderFront, + /// Adjust audio fader towards rear. (`VK_FADER_REAR`) + AudioFaderRear, + /// Advance surround audio mode to next available mode. (`VK_SURROUND_MODE_NEXT`) + AudioSurroundModeNext, + /// Decrease treble. (`APPCOMMAND_TREBLE_DOWN`) + AudioTrebleDown, + /// Increase treble. (`APPCOMMAND_TREBLE_UP`) + AudioTrebleUp, + /// Decrease audio volume. (`APPCOMMAND_VOLUME_DOWN`, `KEYCODE_VOLUME_DOWN`) + AudioVolumeDown, + /// Increase audio volume. (`APPCOMMAND_VOLUME_UP`, `KEYCODE_VOLUME_UP`) + AudioVolumeUp, + /// Toggle between muted state and prior volume level. (`APPCOMMAND_VOLUME_MUTE`, + /// `KEYCODE_VOLUME_MUTE`) + AudioVolumeMute, + /// Toggle the microphone on/off. (`APPCOMMAND_MIC_ON_OFF_TOGGLE`) + MicrophoneToggle, + /// Decrease microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_DOWN`) + MicrophoneVolumeDown, + /// Increase microphone volume. (`APPCOMMAND_MICROPHONE_VOLUME_UP`) + MicrophoneVolumeUp, + /// Mute the microphone. (`APPCOMMAND_MICROPHONE_VOLUME_MUTE`, `KEYCODE_MUTE`) + MicrophoneVolumeMute, + /// Show correction list when a word is incorrectly identified. (`APPCOMMAND_CORRECTION_LIST`) + SpeechCorrectionList, + /// Toggle between dictation mode and command/control mode. + /// (`APPCOMMAND_DICTATE_OR_COMMAND_CONTROL_TOGGLE`) + SpeechInputToggle, + /// The first generic "LaunchApplication" key. This is commonly associated with launching "My + /// Computer", and may have a computer symbol on the key. (`APPCOMMAND_LAUNCH_APP1`) + LaunchApplication1, + /// The second generic "LaunchApplication" key. This is commonly associated with launching + /// "Calculator", and may have a calculator symbol on the key. (`APPCOMMAND_LAUNCH_APP2`, + /// `KEYCODE_CALCULATOR`) + LaunchApplication2, + /// The "Calendar" key. (`KEYCODE_CALENDAR`) + LaunchCalendar, + /// The "Contacts" key. (`KEYCODE_CONTACTS`) + LaunchContacts, + /// The "Mail" key. (`APPCOMMAND_LAUNCH_MAIL`) + LaunchMail, + /// The "Media Player" key. (`APPCOMMAND_LAUNCH_MEDIA_SELECT`) + LaunchMediaPlayer, + LaunchMusicPlayer, + LaunchPhone, + LaunchScreenSaver, + LaunchSpreadsheet, + LaunchWebBrowser, + LaunchWebCam, + LaunchWordProcessor, + /// Navigate to previous content or page in current history. (`APPCOMMAND_BROWSER_BACKWARD`) + BrowserBack, + /// Open the list of browser favorites. (`APPCOMMAND_BROWSER_FAVORITES`) + BrowserFavorites, + /// Navigate to next content or page in current history. (`APPCOMMAND_BROWSER_FORWARD`) + BrowserForward, + /// Go to the user’s preferred home page. (`APPCOMMAND_BROWSER_HOME`) + BrowserHome, + /// Refresh the current page or content. (`APPCOMMAND_BROWSER_REFRESH`) + BrowserRefresh, + /// Call up the user’s preferred search page. (`APPCOMMAND_BROWSER_SEARCH`) + BrowserSearch, + /// Stop loading the current page or content. (`APPCOMMAND_BROWSER_STOP`) + BrowserStop, + /// The Application switch key, which provides a list of recent apps to switch between. + /// (`KEYCODE_APP_SWITCH`) + AppSwitch, + /// The Call key. (`KEYCODE_CALL`) + Call, + /// The Camera key. (`KEYCODE_CAMERA`) + Camera, + /// The Camera focus key. (`KEYCODE_FOCUS`) + CameraFocus, + /// The End Call key. (`KEYCODE_ENDCALL`) + EndCall, + /// The Back key. (`KEYCODE_BACK`) + GoBack, + /// The Home key, which goes to the phone’s main screen. (`KEYCODE_HOME`) + GoHome, + /// The Headset Hook key. (`KEYCODE_HEADSETHOOK`) + HeadsetHook, + LastNumberRedial, + /// The Notification key. (`KEYCODE_NOTIFICATION`) + Notification, + /// Toggle between manner mode state: silent, vibrate, ring, ... (`KEYCODE_MANNER_MODE`) + MannerMode, + VoiceDial, + /// Switch to viewing TV. (`KEYCODE_TV`) + TV, + /// TV 3D Mode. (`KEYCODE_3D_MODE`) + TV3DMode, + /// Toggle between antenna and cable input. (`KEYCODE_TV_ANTENNA_CABLE`) + TVAntennaCable, + /// Audio description. (`KEYCODE_TV_AUDIO_DESCRIPTION`) + TVAudioDescription, + /// Audio description mixing volume down. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_DOWN`) + TVAudioDescriptionMixDown, + /// Audio description mixing volume up. (`KEYCODE_TV_AUDIO_DESCRIPTION_MIX_UP`) + TVAudioDescriptionMixUp, + /// Contents menu. (`KEYCODE_TV_CONTENTS_MENU`) + TVContentsMenu, + /// Contents menu. (`KEYCODE_TV_DATA_SERVICE`) + TVDataService, + /// Switch the input mode on an external TV. (`KEYCODE_TV_INPUT`) + TVInput, + /// Switch to component input #1. (`KEYCODE_TV_INPUT_COMPONENT_1`) + TVInputComponent1, + /// Switch to component input #2. (`KEYCODE_TV_INPUT_COMPONENT_2`) + TVInputComponent2, + /// Switch to composite input #1. (`KEYCODE_TV_INPUT_COMPOSITE_1`) + TVInputComposite1, + /// Switch to composite input #2. (`KEYCODE_TV_INPUT_COMPOSITE_2`) + TVInputComposite2, + /// Switch to HDMI input #1. (`KEYCODE_TV_INPUT_HDMI_1`) + TVInputHDMI1, + /// Switch to HDMI input #2. (`KEYCODE_TV_INPUT_HDMI_2`) + TVInputHDMI2, + /// Switch to HDMI input #3. (`KEYCODE_TV_INPUT_HDMI_3`) + TVInputHDMI3, + /// Switch to HDMI input #4. (`KEYCODE_TV_INPUT_HDMI_4`) + TVInputHDMI4, + /// Switch to VGA input #1. (`KEYCODE_TV_INPUT_VGA_1`) + TVInputVGA1, + /// Media context menu. (`KEYCODE_TV_MEDIA_CONTEXT_MENU`) + TVMediaContext, + /// Toggle network. (`KEYCODE_TV_NETWORK`) + TVNetwork, + /// Number entry. (`KEYCODE_TV_NUMBER_ENTRY`) + TVNumberEntry, + /// Toggle the power on an external TV. (`KEYCODE_TV_POWER`) + TVPower, + /// Radio. (`KEYCODE_TV_RADIO_SERVICE`) + TVRadioService, + /// Satellite. (`KEYCODE_TV_SATELLITE`) + TVSatellite, + /// Broadcast Satellite. (`KEYCODE_TV_SATELLITE_BS`) + TVSatelliteBS, + /// Communication Satellite. (`KEYCODE_TV_SATELLITE_CS`) + TVSatelliteCS, + /// Toggle between available satellites. (`KEYCODE_TV_SATELLITE_SERVICE`) + TVSatelliteToggle, + /// Analog Terrestrial. (`KEYCODE_TV_TERRESTRIAL_ANALOG`) + TVTerrestrialAnalog, + /// Digital Terrestrial. (`KEYCODE_TV_TERRESTRIAL_DIGITAL`) + TVTerrestrialDigital, + /// Timer programming. (`KEYCODE_TV_TIMER_PROGRAMMING`) + TVTimer, + /// Switch the input mode on an external AVR (audio/video receiver). (`KEYCODE_AVR_INPUT`) + AVRInput, + /// Toggle the power on an external AVR (audio/video receiver). (`KEYCODE_AVR_POWER`) + AVRPower, + /// General purpose color-coded media function key, as index 0 (red). (`VK_COLORED_KEY_0`, + /// `KEYCODE_PROG_RED`) + ColorF0Red, + /// General purpose color-coded media function key, as index 1 (green). (`VK_COLORED_KEY_1`, + /// `KEYCODE_PROG_GREEN`) + ColorF1Green, + /// General purpose color-coded media function key, as index 2 (yellow). (`VK_COLORED_KEY_2`, + /// `KEYCODE_PROG_YELLOW`) + ColorF2Yellow, + /// General purpose color-coded media function key, as index 3 (blue). (`VK_COLORED_KEY_3`, + /// `KEYCODE_PROG_BLUE`) + ColorF3Blue, + /// General purpose color-coded media function key, as index 4 (grey). (`VK_COLORED_KEY_4`) + ColorF4Grey, + /// General purpose color-coded media function key, as index 5 (brown). (`VK_COLORED_KEY_5`) + ColorF5Brown, + /// Toggle the display of Closed Captions. (`VK_CC`, `KEYCODE_CAPTIONS`) + ClosedCaptionToggle, + /// Adjust brightness of device, by toggling between or cycling through states. (`VK_DIMMER`) + Dimmer, + /// Swap video sources. (`VK_DISPLAY_SWAP`) + DisplaySwap, + /// Select Digital Video Rrecorder. (`KEYCODE_DVR`) + DVR, + /// Exit the current application. (`VK_EXIT`) + Exit, + /// Clear program or content stored as favorite 0. (`VK_CLEAR_FAVORITE_0`) + FavoriteClear0, + /// Clear program or content stored as favorite 1. (`VK_CLEAR_FAVORITE_1`) + FavoriteClear1, + /// Clear program or content stored as favorite 2. (`VK_CLEAR_FAVORITE_2`) + FavoriteClear2, + /// Clear program or content stored as favorite 3. (`VK_CLEAR_FAVORITE_3`) + FavoriteClear3, + /// Select (recall) program or content stored as favorite 0. (`VK_RECALL_FAVORITE_0`) + FavoriteRecall0, + /// Select (recall) program or content stored as favorite 1. (`VK_RECALL_FAVORITE_1`) + FavoriteRecall1, + /// Select (recall) program or content stored as favorite 2. (`VK_RECALL_FAVORITE_2`) + FavoriteRecall2, + /// Select (recall) program or content stored as favorite 3. (`VK_RECALL_FAVORITE_3`) + FavoriteRecall3, + /// Store current program or content as favorite 0. (`VK_STORE_FAVORITE_0`) + FavoriteStore0, + /// Store current program or content as favorite 1. (`VK_STORE_FAVORITE_1`) + FavoriteStore1, + /// Store current program or content as favorite 2. (`VK_STORE_FAVORITE_2`) + FavoriteStore2, + /// Store current program or content as favorite 3. (`VK_STORE_FAVORITE_3`) + FavoriteStore3, + /// Toggle display of program or content guide. (`VK_GUIDE`, `KEYCODE_GUIDE`) + Guide, + /// If guide is active and displayed, then display next day’s content. (`VK_NEXT_DAY`) + GuideNextDay, + /// If guide is active and displayed, then display previous day’s content. (`VK_PREV_DAY`) + GuidePreviousDay, + /// Toggle display of information about currently selected context or media. (`VK_INFO`, + /// `KEYCODE_INFO`) + Info, + /// Toggle instant replay. (`VK_INSTANT_REPLAY`) + InstantReplay, + /// Launch linked content, if available and appropriate. (`VK_LINK`) + Link, + /// List the current program. (`VK_LIST`) + ListProgram, + /// Toggle display listing of currently available live content or programs. (`VK_LIVE`) + LiveContent, + /// Lock or unlock current content or program. (`VK_LOCK`) + Lock, + /// Show a list of media applications: audio/video players and image viewers. (`VK_APPS`) + /// + /// Note: Do not confuse this key value with the Windows' `VK_APPS` / `VK_CONTEXT_MENU` key, + /// which is encoded as `"ContextMenu"`. + MediaApps, + /// Audio track key. (`KEYCODE_MEDIA_AUDIO_TRACK`) + MediaAudioTrack, + /// Select previously selected channel or media. (`VK_LAST`, `KEYCODE_LAST_CHANNEL`) + MediaLast, + /// Skip backward to next content or program. (`KEYCODE_MEDIA_SKIP_BACKWARD`) + MediaSkipBackward, + /// Skip forward to next content or program. (`VK_SKIP`, `KEYCODE_MEDIA_SKIP_FORWARD`) + MediaSkipForward, + /// Step backward to next content or program. (`KEYCODE_MEDIA_STEP_BACKWARD`) + MediaStepBackward, + /// Step forward to next content or program. (`KEYCODE_MEDIA_STEP_FORWARD`) + MediaStepForward, + /// Media top menu. (`KEYCODE_MEDIA_TOP_MENU`) + MediaTopMenu, + /// Navigate in. (`KEYCODE_NAVIGATE_IN`) + NavigateIn, + /// Navigate to next key. (`KEYCODE_NAVIGATE_NEXT`) + NavigateNext, + /// Navigate out. (`KEYCODE_NAVIGATE_OUT`) + NavigateOut, + /// Navigate to previous key. (`KEYCODE_NAVIGATE_PREVIOUS`) + NavigatePrevious, + /// Cycle to next favorite channel (in favorites list). (`VK_NEXT_FAVORITE_CHANNEL`) + NextFavoriteChannel, + /// Cycle to next user profile (if there are multiple user profiles). (`VK_USER`) + NextUserProfile, + /// Access on-demand content or programs. (`VK_ON_DEMAND`) + OnDemand, + /// Pairing key to pair devices. (`KEYCODE_PAIRING`) + Pairing, + /// Move picture-in-picture window down. (`VK_PINP_DOWN`) + PinPDown, + /// Move picture-in-picture window. (`VK_PINP_MOVE`) + PinPMove, + /// Toggle display of picture-in-picture window. (`VK_PINP_TOGGLE`) + PinPToggle, + /// Move picture-in-picture window up. (`VK_PINP_UP`) + PinPUp, + /// Decrease media playback speed. (`VK_PLAY_SPEED_DOWN`) + PlaySpeedDown, + /// Reset playback to normal speed. (`VK_PLAY_SPEED_RESET`) + PlaySpeedReset, + /// Increase media playback speed. (`VK_PLAY_SPEED_UP`) + PlaySpeedUp, + /// Toggle random media or content shuffle mode. (`VK_RANDOM_TOGGLE`) + RandomToggle, + /// Not a physical key, but this key code is sent when the remote control battery is low. + /// (`VK_RC_LOW_BATTERY`) + RcLowBattery, + /// Toggle or cycle between media recording speeds. (`VK_RECORD_SPEED_NEXT`) + RecordSpeedNext, + /// Toggle RF (radio frequency) input bypass mode (pass RF input directly to the RF output). + /// (`VK_RF_BYPASS`) + RfBypass, + /// Toggle scan channels mode. (`VK_SCAN_CHANNELS_TOGGLE`) + ScanChannelsToggle, + /// Advance display screen mode to next available mode. (`VK_SCREEN_MODE_NEXT`) + ScreenModeNext, + /// Toggle display of device settings screen. (`VK_SETTINGS`, `KEYCODE_SETTINGS`) + Settings, + /// Toggle split screen mode. (`VK_SPLIT_SCREEN_TOGGLE`) + SplitScreenToggle, + /// Switch the input mode on an external STB (set top box). (`KEYCODE_STB_INPUT`) + STBInput, + /// Toggle the power on an external STB (set top box). (`KEYCODE_STB_POWER`) + STBPower, + /// Toggle display of subtitles, if available. (`VK_SUBTITLE`) + Subtitle, + /// Toggle display of teletext, if available (`VK_TELETEXT`, `KEYCODE_TV_TELETEXT`). + Teletext, + /// Advance video mode to next available mode. (`VK_VIDEO_MODE_NEXT`) + VideoModeNext, + /// Cause device to identify itself in some manner, e.g., audibly or visibly. (`VK_WINK`) + Wink, + /// Toggle between full-screen and scaled content, or alter magnification level. (`VK_ZOOM`, + /// `KEYCODE_TV_ZOOM_MODE`) + ZoomToggle, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F1, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F2, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F3, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F4, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F5, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F6, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F7, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F8, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F9, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F10, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F11, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F12, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F13, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F14, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F15, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F16, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F17, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F18, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F19, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F20, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F21, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F22, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F23, + /// General-purpose function key. + /// Usually found at the top of the keyboard. + F24, + /// General-purpose function key. + F25, + /// General-purpose function key. + F26, + /// General-purpose function key. + F27, + /// General-purpose function key. + F28, + /// General-purpose function key. + F29, + /// General-purpose function key. + F30, + /// General-purpose function key. + F31, + /// General-purpose function key. + F32, + /// General-purpose function key. + F33, + /// General-purpose function key. + F34, + /// General-purpose function key. + F35, +} + +impl<'a> Key<'a> { + pub fn to_text(&self) -> Option<&'a str> { + match self { + Key::Character(ch) => Some(*ch), + Key::Enter => Some("\r"), + Key::Backspace => Some("\x08"), + Key::Tab => Some("\t"), + Key::Space => Some(" "), + Key::Escape => Some("\x1b"), + _ => None, + } + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum KeyLocation { + Standard, + Left, + Right, + Numpad, +} diff --git a/src/lib.rs b/src/lib.rs index 51f4a8634a..06556e88f2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -155,6 +155,7 @@ pub mod error; pub mod event; pub mod event_loop; mod icon; +pub mod keyboard; pub mod monitor; mod platform_impl; pub mod window; diff --git a/src/platform/mod.rs b/src/platform/mod.rs index 1ee5fce2bb..84f016c2d2 100644 --- a/src/platform/mod.rs +++ b/src/platform/mod.rs @@ -21,5 +21,7 @@ pub mod macos; pub mod unix; pub mod windows; +pub mod modifier_supplement; pub mod run_return; +pub mod scancode; pub mod web; diff --git a/src/platform/modifier_supplement.rs b/src/platform/modifier_supplement.rs new file mode 100644 index 0000000000..361ed05d04 --- /dev/null +++ b/src/platform/modifier_supplement.rs @@ -0,0 +1,32 @@ +#![cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] + +use crate::keyboard::Key; + +/// Additional methods for the `KeyEvent` which cannot be implemented on all +/// platforms. +pub trait KeyEventExtModifierSupplement { + /// Identical to `KeyEvent::text` but this is affected by Ctrl. + /// + /// For example, pressing Ctrl+a produces `Some("\x01")`. + fn text_with_all_modifiers(&self) -> Option<&str>; + + /// This value ignores all modifiers including, + /// but not limited to Shift, Caps Lock, + /// and Ctrl. In most cases this means that the + /// unicode character in the resulting string is lowercase. + /// + /// This is useful for key-bindings / shortcut key combinations. + /// + /// In case `logical_key` reports `Dead`, this will still report the + /// key as `Character` according to the current keyboard layout. This value + /// cannot be `Dead`. + fn key_without_modifiers(&self) -> Key<'static>; +} diff --git a/src/platform/scancode.rs b/src/platform/scancode.rs new file mode 100644 index 0000000000..00188c6212 --- /dev/null +++ b/src/platform/scancode.rs @@ -0,0 +1,32 @@ +#![cfg(any( + target_os = "windows", + target_os = "macos", + target_os = "linux", + target_os = "dragonfly", + target_os = "freebsd", + target_os = "netbsd", + target_os = "openbsd" +))] + +// TODO: Maybe merge this with `modifier_supplement` if the two are indeed supported on the same +// set of platforms + +use crate::keyboard::KeyCode; + +pub trait KeyCodeExtScancode { + /// The raw value of the platform-specific physical key identifier. + /// + /// Returns `Some(key_id)` if the conversion was succesful; returns `None` otherwise. + /// + /// ## Platform-specific + /// - **Windows:** A 16bit extended scancode + /// - **Wayland/X11**: A 32-bit X11-style keycode. + // TODO: Describe what this value contains for each platform + fn to_scancode(self) -> Option; + + /// Constructs a `KeyCode` from a platform-specific physical key identifier. + /// + /// Note that this conversion may be lossy, i.e. converting the returned `KeyCode` back + /// using `to_scancode` might not yield the original value. + fn from_scancode(scancode: u32) -> KeyCode; +} diff --git a/src/platform/unix.rs b/src/platform/unix.rs index 8662e45690..b92eb6e12b 100644 --- a/src/platform/unix.rs +++ b/src/platform/unix.rs @@ -7,28 +7,25 @@ ))] use std::os::raw; -#[cfg(feature = "x11")] -use std::{ptr, sync::Arc}; use crate::{ + event::KeyEvent, event_loop::{EventLoop, EventLoopWindowTarget}, + keyboard::{Key, KeyCode}, monitor::MonitorHandle, + platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, + platform_impl::common::keymap, window::{Window, WindowBuilder}, }; #[cfg(feature = "x11")] use crate::dpi::Size; -#[cfg(feature = "x11")] -use crate::platform_impl::x11::{ffi::XVisualInfo, XConnection}; +use crate::event::DeviceId; use crate::platform_impl::{ EventLoop as LinuxEventLoop, EventLoopWindowTarget as LinuxEventLoopWindowTarget, Window as LinuxWindow, }; -// TODO: stupid hack so that glutin can do its work -#[doc(hidden)] -#[cfg(feature = "x11")] -pub use crate::platform_impl::x11; #[cfg(feature = "x11")] pub use crate::platform_impl::{x11::util::WindowType as XWindowType, XNotSupported}; @@ -42,9 +39,22 @@ pub trait EventLoopWindowTargetExtUnix { #[cfg(feature = "x11")] fn is_x11(&self) -> bool; - #[doc(hidden)] + /// This function returns the underlying xlib `Display`. + /// + /// Returns `None` if the event loop doesn't use X11 or if xlib support was disabled by + /// setting the `WINIT_DISABLE_XLIB` environment variable. + /// + /// The pointer will become invalid when the `EventLoop` is destroyed. + #[cfg(feature = "xlib")] + fn xlib_display(&self) -> Option<*mut raw::c_void>; + + /// This function returns the underlying `xcb_connection_t`. + /// + /// Returns `None` if the event loop doesn't use X11 (if it uses wayland for example). + /// + /// The pointer will become invalid when the `EventLoop` is destroyed. #[cfg(feature = "x11")] - fn xlib_xconnection(&self) -> Option>; + fn xcb_connection(&self) -> Option<*mut raw::c_void>; /// Returns a pointer to the `wl_display` object of wayland that is used by this /// `EventLoopWindowTarget`. @@ -70,14 +80,24 @@ impl EventLoopWindowTargetExtUnix for EventLoopWindowTarget { } #[inline] - #[doc(hidden)] + #[cfg(feature = "xlib")] + fn xlib_display(&self) -> Option<*mut raw::c_void> { + if let LinuxEventLoopWindowTarget::X(e) = &self.p { + if let Some(xlib) = &e.x_connection().xlib { + return Some(xlib.dpy as _); + } + } + None + } + + #[inline] #[cfg(feature = "x11")] - fn xlib_xconnection(&self) -> Option> { - match self.p { - LinuxEventLoopWindowTarget::X(ref e) => Some(e.x_connection().clone()), - #[cfg(feature = "wayland")] - _ => None, + fn xcb_connection(&self) -> Option<*mut raw::c_void> { + #[allow(irrefutable_let_patterns)] + if let LinuxEventLoopWindowTarget::X(e) = &self.p { + return Some(e.x_connection().c as _); } + None } #[inline] @@ -192,32 +212,32 @@ impl EventLoopExtUnix for EventLoop { /// Additional methods on `Window` that are specific to Unix. pub trait WindowExtUnix { - /// Returns the ID of the `Window` xlib object that is used by this window. + /// Returns the ID of the X11 window. /// - /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). + /// Returns `None` if the window doesn't use X11 (if it uses wayland for example). #[cfg(feature = "x11")] - fn xlib_window(&self) -> Option; + fn x11_window(&self) -> Option; - /// Returns a pointer to the `Display` object of xlib that is used by this window. - /// - /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). + /// Returns the ID of the X11 screen. /// - /// The pointer will become invalid when the glutin `Window` is destroyed. + /// Returns `None` if the window doesn't use X11 (if it uses wayland for example). #[cfg(feature = "x11")] - fn xlib_display(&self) -> Option<*mut raw::c_void>; + fn x11_screen_id(&self) -> Option; - #[cfg(feature = "x11")] - fn xlib_screen_id(&self) -> Option; - - #[doc(hidden)] - #[cfg(feature = "x11")] - fn xlib_xconnection(&self) -> Option>; + /// This function returns the underlying xlib `Display`. + /// + /// Returns `None` if the event loop doesn't use X11 or if xlib support was disabled by + /// setting the `WINIT_DISABLE_XLIB` environment variable. + /// + /// The pointer will become invalid when the `EventLoop` is destroyed. + #[cfg(feature = "xlib")] + fn xlib_display(&self) -> Option<*mut raw::c_void>; - /// This function returns the underlying `xcb_connection_t` of an xlib `Display`. + /// This function returns the underlying `xcb_connection_t`. /// - /// Returns `None` if the window doesn't use xlib (if it uses wayland for example). + /// Returns `None` if the event loop doesn't use X11 (if it uses wayland for example). /// - /// The pointer will become invalid when the glutin `Window` is destroyed. + /// The pointer will become invalid when the `Window` is destroyed. #[cfg(feature = "x11")] fn xcb_connection(&self) -> Option<*mut raw::c_void>; @@ -254,50 +274,40 @@ pub trait WindowExtUnix { impl WindowExtUnix for Window { #[inline] #[cfg(feature = "x11")] - fn xlib_window(&self) -> Option { - match self.window { - LinuxWindow::X(ref w) => Some(w.xlib_window()), - #[cfg(feature = "wayland")] - _ => None, + fn x11_window(&self) -> Option { + #[allow(irrefutable_let_patterns)] + if let LinuxWindow::X(w) = &self.window { + return Some(w.xwindow); } + None } #[inline] #[cfg(feature = "x11")] - fn xlib_display(&self) -> Option<*mut raw::c_void> { + fn x11_screen_id(&self) -> Option { match self.window { - LinuxWindow::X(ref w) => Some(w.xlib_display()), + LinuxWindow::X(ref w) => Some(w.screen.screen_id as _), #[cfg(feature = "wayland")] _ => None, } } #[inline] - #[cfg(feature = "x11")] - fn xlib_screen_id(&self) -> Option { - match self.window { - LinuxWindow::X(ref w) => Some(w.xlib_screen_id()), - #[cfg(feature = "wayland")] - _ => None, - } - } - - #[inline] - #[doc(hidden)] - #[cfg(feature = "x11")] - fn xlib_xconnection(&self) -> Option> { - match self.window { - LinuxWindow::X(ref w) => Some(w.xlib_xconnection()), - #[cfg(feature = "wayland")] - _ => None, + #[cfg(feature = "xlib")] + fn xlib_display(&self) -> Option<*mut raw::c_void> { + if let LinuxWindow::X(e) = &self.window { + if let Some(xlib) = &e.xconn.xlib { + return Some(xlib.dpy as _); + } } + None } #[inline] #[cfg(feature = "x11")] fn xcb_connection(&self) -> Option<*mut raw::c_void> { match self.window { - LinuxWindow::X(ref w) => Some(w.xcb_connection()), + LinuxWindow::X(ref w) => Some(w.xconn.c as _), #[cfg(feature = "wayland")] _ => None, } @@ -342,9 +352,9 @@ impl WindowExtUnix for Window { /// Additional methods on `WindowBuilder` that are specific to Unix. pub trait WindowBuilderExtUnix { #[cfg(feature = "x11")] - fn with_x11_visual(self, visual_infos: *const T) -> Self; + fn with_x11_visual(self, visual_infos: XVisualInfos) -> Self; #[cfg(feature = "x11")] - fn with_x11_screen(self, screen_id: i32) -> Self; + fn with_x11_screen(self, screen_id: u32) -> Self; /// Build window with `WM_CLASS` hint; defaults to the name of the binary. Only relevant on X11. #[cfg(feature = "x11")] @@ -374,20 +384,23 @@ pub trait WindowBuilderExtUnix { fn with_app_id(self, app_id: String) -> Self; } +#[derive(Copy, Clone, Debug, Default)] +pub struct XVisualInfos { + pub visual_id: Option, + pub depth: Option, +} + impl WindowBuilderExtUnix for WindowBuilder { #[inline] #[cfg(feature = "x11")] - fn with_x11_visual(mut self, visual_infos: *const T) -> Self { - { - self.platform_specific.visual_infos = - Some(unsafe { ptr::read(visual_infos as *const XVisualInfo) }); - } + fn with_x11_visual(mut self, visual_infos: XVisualInfos) -> Self { + self.platform_specific.visual_infos = visual_infos; self } #[inline] #[cfg(feature = "x11")] - fn with_x11_screen(mut self, screen_id: i32) -> Self { + fn with_x11_screen(mut self, screen_id: u32) -> Self { self.platform_specific.screen_id = Some(screen_id); self } @@ -442,6 +455,27 @@ impl WindowBuilderExtUnix for WindowBuilder { } } +/// Additional methods on `DeviceId` that are specific to Unix. +pub trait DeviceIdExtUnix { + /// Returns the native xinput identifier of the device. + /// + /// Returns `None` if the `DeviceId` does not belong to an X server. + #[cfg(feature = "x11")] + fn xinput_id(&self) -> Option; +} + +impl DeviceIdExtUnix for DeviceId { + #[inline] + #[cfg(feature = "x11")] + fn xinput_id(&self) -> Option { + #[allow(irrefutable_let_patterns)] + if let crate::platform_impl::DeviceId::X(id) = self.0 { + return Some(id.0 as _); + } + None + } +} + /// Additional methods on `MonitorHandle` that are specific to Linux. pub trait MonitorHandleExtUnix { /// Returns the inner identifier of the monitor. @@ -529,3 +563,25 @@ pub struct ARGBColor { pub g: u8, pub b: u8, } + +impl KeyEventExtModifierSupplement for KeyEvent { + #[inline] + fn text_with_all_modifiers(&self) -> Option<&str> { + self.platform_specific.text_with_all_modifiers + } + + #[inline] + fn key_without_modifiers(&self) -> Key<'static> { + self.platform_specific.key_without_modifiers + } +} + +impl KeyCodeExtScancode for KeyCode { + fn from_scancode(scancode: u32) -> KeyCode { + keymap::raw_keycode_to_keycode(scancode) + } + + fn to_scancode(self) -> Option { + keymap::keycode_to_raw(self) + } +} diff --git a/src/platform/windows.rs b/src/platform/windows.rs index 83a33c6373..bba2de5070 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -4,14 +4,24 @@ use std::os::raw::c_void; use std::path::Path; use libc; -use winapi::shared::minwindef::WORD; -use winapi::shared::windef::{HMENU, HWND}; +use winapi::{ + shared::{ + minwindef::{LOWORD, WORD}, + windef::{HMENU, HWND}, + }, + um::{ + winnt::{LANG_KOREAN, PRIMARYLANGID}, + winuser::GetKeyboardLayout, + }, +}; use crate::{ dpi::PhysicalSize, - event::DeviceId, + event::{DeviceId, KeyEvent}, event_loop::EventLoop, + keyboard::{Key, KeyCode, NativeKeyCode}, monitor::MonitorHandle, + platform::{modifier_supplement::KeyEventExtModifierSupplement, scancode::KeyCodeExtScancode}, platform_impl::{EventLoop as WindowsEventLoop, Parent, WinIcon}, window::{BadIcon, Icon, Theme, Window, WindowBuilder}, }; @@ -294,3 +304,349 @@ impl IconExtWindows for Icon { Ok(Icon { inner: win_icon }) } } + +impl KeyEventExtModifierSupplement for KeyEvent { + #[inline] + fn text_with_all_modifiers(&self) -> Option<&str> { + self.platform_specific.text_with_all_modifers + } + + #[inline] + fn key_without_modifiers(&self) -> Key<'static> { + self.platform_specific.key_without_modifiers + } +} + +impl KeyCodeExtScancode for KeyCode { + fn to_scancode(self) -> Option { + // See `from_scancode` for more info + + let hkl = unsafe { GetKeyboardLayout(0) }; + + let primary_lang_id = PRIMARYLANGID(LOWORD(hkl as u32)); + let is_korean = primary_lang_id == LANG_KOREAN; + + match self { + KeyCode::Backquote => Some(0x0029), + KeyCode::Backslash => Some(0x002B), + KeyCode::Backspace => Some(0x000E), + KeyCode::BracketLeft => Some(0x001A), + KeyCode::BracketRight => Some(0x001B), + KeyCode::Comma => Some(0x0033), + KeyCode::Digit0 => Some(0x000B), + KeyCode::Digit1 => Some(0x0002), + KeyCode::Digit2 => Some(0x0003), + KeyCode::Digit3 => Some(0x0004), + KeyCode::Digit4 => Some(0x0005), + KeyCode::Digit5 => Some(0x0006), + KeyCode::Digit6 => Some(0x0007), + KeyCode::Digit7 => Some(0x0008), + KeyCode::Digit8 => Some(0x0009), + KeyCode::Digit9 => Some(0x000A), + KeyCode::Equal => Some(0x000D), + KeyCode::IntlBackslash => Some(0x0056), + KeyCode::IntlRo => Some(0x0073), + KeyCode::IntlYen => Some(0x007D), + KeyCode::KeyA => Some(0x001E), + KeyCode::KeyB => Some(0x0030), + KeyCode::KeyC => Some(0x002E), + KeyCode::KeyD => Some(0x0020), + KeyCode::KeyE => Some(0x0012), + KeyCode::KeyF => Some(0x0021), + KeyCode::KeyG => Some(0x0022), + KeyCode::KeyH => Some(0x0023), + KeyCode::KeyI => Some(0x0017), + KeyCode::KeyJ => Some(0x0024), + KeyCode::KeyK => Some(0x0025), + KeyCode::KeyL => Some(0x0026), + KeyCode::KeyM => Some(0x0032), + KeyCode::KeyN => Some(0x0031), + KeyCode::KeyO => Some(0x0018), + KeyCode::KeyP => Some(0x0019), + KeyCode::KeyQ => Some(0x0010), + KeyCode::KeyR => Some(0x0013), + KeyCode::KeyS => Some(0x001F), + KeyCode::KeyT => Some(0x0014), + KeyCode::KeyU => Some(0x0016), + KeyCode::KeyV => Some(0x002F), + KeyCode::KeyW => Some(0x0011), + KeyCode::KeyX => Some(0x002D), + KeyCode::KeyY => Some(0x0015), + KeyCode::KeyZ => Some(0x002C), + KeyCode::Minus => Some(0x000C), + KeyCode::Period => Some(0x0034), + KeyCode::Quote => Some(0x0028), + KeyCode::Semicolon => Some(0x0027), + KeyCode::Slash => Some(0x0035), + KeyCode::AltLeft => Some(0x0038), + KeyCode::AltRight => Some(0xE038), + KeyCode::CapsLock => Some(0x003A), + KeyCode::ContextMenu => Some(0xE05D), + KeyCode::ControlLeft => Some(0x001D), + KeyCode::ControlRight => Some(0xE01D), + KeyCode::Enter => Some(0x001C), + KeyCode::SuperLeft => Some(0xE05B), + KeyCode::SuperRight => Some(0xE05C), + KeyCode::ShiftLeft => Some(0x002A), + KeyCode::ShiftRight => Some(0x0036), + KeyCode::Space => Some(0x0039), + KeyCode::Tab => Some(0x000F), + KeyCode::Convert => Some(0x0079), + KeyCode::Lang1 => { + if is_korean { + Some(0xE0F2) + } else { + Some(0x0072) + } + } + KeyCode::Lang2 => { + if is_korean { + Some(0xE0F1) + } else { + Some(0x0071) + } + } + KeyCode::KanaMode => Some(0x0070), + KeyCode::NonConvert => Some(0x007B), + KeyCode::Delete => Some(0xE053), + KeyCode::End => Some(0xE04F), + KeyCode::Home => Some(0xE047), + KeyCode::Insert => Some(0xE052), + KeyCode::PageDown => Some(0xE051), + KeyCode::PageUp => Some(0xE049), + KeyCode::ArrowDown => Some(0xE050), + KeyCode::ArrowLeft => Some(0xE04B), + KeyCode::ArrowRight => Some(0xE04D), + KeyCode::ArrowUp => Some(0xE048), + KeyCode::NumLock => Some(0xE045), + KeyCode::Numpad0 => Some(0x0052), + KeyCode::Numpad1 => Some(0x004F), + KeyCode::Numpad2 => Some(0x0050), + KeyCode::Numpad3 => Some(0x0051), + KeyCode::Numpad4 => Some(0x004B), + KeyCode::Numpad5 => Some(0x004C), + KeyCode::Numpad6 => Some(0x004D), + KeyCode::Numpad7 => Some(0x0047), + KeyCode::Numpad8 => Some(0x0048), + KeyCode::Numpad9 => Some(0x0049), + KeyCode::NumpadAdd => Some(0x004E), + KeyCode::NumpadComma => Some(0x007E), + KeyCode::NumpadDecimal => Some(0x0053), + KeyCode::NumpadDivide => Some(0xE035), + KeyCode::NumpadEnter => Some(0xE01C), + KeyCode::NumpadEqual => Some(0x0059), + KeyCode::NumpadMultiply => Some(0x0037), + KeyCode::NumpadSubtract => Some(0x004A), + KeyCode::Escape => Some(0x0001), + KeyCode::F1 => Some(0x003B), + KeyCode::F2 => Some(0x003C), + KeyCode::F3 => Some(0x003D), + KeyCode::F4 => Some(0x003E), + KeyCode::F5 => Some(0x003F), + KeyCode::F6 => Some(0x0040), + KeyCode::F7 => Some(0x0041), + KeyCode::F8 => Some(0x0042), + KeyCode::F9 => Some(0x0043), + KeyCode::F10 => Some(0x0044), + KeyCode::F11 => Some(0x0057), + KeyCode::F12 => Some(0x0058), + KeyCode::F13 => Some(0x0064), + KeyCode::F14 => Some(0x0065), + KeyCode::F15 => Some(0x0066), + KeyCode::F16 => Some(0x0067), + KeyCode::F17 => Some(0x0068), + KeyCode::F18 => Some(0x0069), + KeyCode::F19 => Some(0x006A), + KeyCode::F20 => Some(0x006B), + KeyCode::F21 => Some(0x006C), + KeyCode::F22 => Some(0x006D), + KeyCode::F23 => Some(0x006E), + KeyCode::F24 => Some(0x0076), + KeyCode::PrintScreen => Some(0xE037), + //KeyCode::PrintScreen => Some(0x0054), // Alt + PrintScreen + KeyCode::ScrollLock => Some(0x0046), + KeyCode::Pause => Some(0x0045), + //KeyCode::Pause => Some(0xE046), // Ctrl + Pause + KeyCode::BrowserBack => Some(0xE06A), + KeyCode::BrowserFavorites => Some(0xE066), + KeyCode::BrowserForward => Some(0xE069), + KeyCode::BrowserHome => Some(0xE032), + KeyCode::BrowserRefresh => Some(0xE067), + KeyCode::BrowserSearch => Some(0xE065), + KeyCode::BrowserStop => Some(0xE068), + KeyCode::LaunchApp1 => Some(0xE06B), + KeyCode::LaunchApp2 => Some(0xE021), + KeyCode::LaunchMail => Some(0xE06C), + KeyCode::MediaPlayPause => Some(0xE022), + KeyCode::MediaSelect => Some(0xE06D), + KeyCode::MediaStop => Some(0xE024), + KeyCode::MediaTrackNext => Some(0xE019), + KeyCode::MediaTrackPrevious => Some(0xE010), + KeyCode::Power => Some(0xE05E), + KeyCode::AudioVolumeDown => Some(0xE02E), + KeyCode::AudioVolumeMute => Some(0xE020), + KeyCode::AudioVolumeUp => Some(0xE030), + KeyCode::Unidentified(NativeKeyCode::Windows(scancode)) => Some(scancode as u32), + _ => None, + } + } + + fn from_scancode(scancode: u32) -> KeyCode { + // See: https://www.win.tue.nl/~aeb/linux/kbd/scancodes-1.html + // and: https://www.w3.org/TR/uievents-code/ + // and: The widget/NativeKeyToDOMCodeName.h file in the firefox source + + match scancode { + 0x0029 => KeyCode::Backquote, + 0x002B => KeyCode::Backslash, + 0x000E => KeyCode::Backspace, + 0x001A => KeyCode::BracketLeft, + 0x001B => KeyCode::BracketRight, + 0x0033 => KeyCode::Comma, + 0x000B => KeyCode::Digit0, + 0x0002 => KeyCode::Digit1, + 0x0003 => KeyCode::Digit2, + 0x0004 => KeyCode::Digit3, + 0x0005 => KeyCode::Digit4, + 0x0006 => KeyCode::Digit5, + 0x0007 => KeyCode::Digit6, + 0x0008 => KeyCode::Digit7, + 0x0009 => KeyCode::Digit8, + 0x000A => KeyCode::Digit9, + 0x000D => KeyCode::Equal, + 0x0056 => KeyCode::IntlBackslash, + 0x0073 => KeyCode::IntlRo, + 0x007D => KeyCode::IntlYen, + 0x001E => KeyCode::KeyA, + 0x0030 => KeyCode::KeyB, + 0x002E => KeyCode::KeyC, + 0x0020 => KeyCode::KeyD, + 0x0012 => KeyCode::KeyE, + 0x0021 => KeyCode::KeyF, + 0x0022 => KeyCode::KeyG, + 0x0023 => KeyCode::KeyH, + 0x0017 => KeyCode::KeyI, + 0x0024 => KeyCode::KeyJ, + 0x0025 => KeyCode::KeyK, + 0x0026 => KeyCode::KeyL, + 0x0032 => KeyCode::KeyM, + 0x0031 => KeyCode::KeyN, + 0x0018 => KeyCode::KeyO, + 0x0019 => KeyCode::KeyP, + 0x0010 => KeyCode::KeyQ, + 0x0013 => KeyCode::KeyR, + 0x001F => KeyCode::KeyS, + 0x0014 => KeyCode::KeyT, + 0x0016 => KeyCode::KeyU, + 0x002F => KeyCode::KeyV, + 0x0011 => KeyCode::KeyW, + 0x002D => KeyCode::KeyX, + 0x0015 => KeyCode::KeyY, + 0x002C => KeyCode::KeyZ, + 0x000C => KeyCode::Minus, + 0x0034 => KeyCode::Period, + 0x0028 => KeyCode::Quote, + 0x0027 => KeyCode::Semicolon, + 0x0035 => KeyCode::Slash, + 0x0038 => KeyCode::AltLeft, + 0xE038 => KeyCode::AltRight, + 0x003A => KeyCode::CapsLock, + 0xE05D => KeyCode::ContextMenu, + 0x001D => KeyCode::ControlLeft, + 0xE01D => KeyCode::ControlRight, + 0x001C => KeyCode::Enter, + 0xE05B => KeyCode::SuperLeft, + 0xE05C => KeyCode::SuperRight, + 0x002A => KeyCode::ShiftLeft, + 0x0036 => KeyCode::ShiftRight, + 0x0039 => KeyCode::Space, + 0x000F => KeyCode::Tab, + 0x0079 => KeyCode::Convert, + 0x0072 => KeyCode::Lang1, // for non-Korean layout + 0xE0F2 => KeyCode::Lang1, // for Korean layout + 0x0071 => KeyCode::Lang2, // for non-Korean layout + 0xE0F1 => KeyCode::Lang2, // for Korean layout + 0x0070 => KeyCode::KanaMode, + 0x007B => KeyCode::NonConvert, + 0xE053 => KeyCode::Delete, + 0xE04F => KeyCode::End, + 0xE047 => KeyCode::Home, + 0xE052 => KeyCode::Insert, + 0xE051 => KeyCode::PageDown, + 0xE049 => KeyCode::PageUp, + 0xE050 => KeyCode::ArrowDown, + 0xE04B => KeyCode::ArrowLeft, + 0xE04D => KeyCode::ArrowRight, + 0xE048 => KeyCode::ArrowUp, + 0xE045 => KeyCode::NumLock, + 0x0052 => KeyCode::Numpad0, + 0x004F => KeyCode::Numpad1, + 0x0050 => KeyCode::Numpad2, + 0x0051 => KeyCode::Numpad3, + 0x004B => KeyCode::Numpad4, + 0x004C => KeyCode::Numpad5, + 0x004D => KeyCode::Numpad6, + 0x0047 => KeyCode::Numpad7, + 0x0048 => KeyCode::Numpad8, + 0x0049 => KeyCode::Numpad9, + 0x004E => KeyCode::NumpadAdd, + 0x007E => KeyCode::NumpadComma, + 0x0053 => KeyCode::NumpadDecimal, + 0xE035 => KeyCode::NumpadDivide, + 0xE01C => KeyCode::NumpadEnter, + 0x0059 => KeyCode::NumpadEqual, + 0x0037 => KeyCode::NumpadMultiply, + 0x004A => KeyCode::NumpadSubtract, + 0x0001 => KeyCode::Escape, + 0x003B => KeyCode::F1, + 0x003C => KeyCode::F2, + 0x003D => KeyCode::F3, + 0x003E => KeyCode::F4, + 0x003F => KeyCode::F5, + 0x0040 => KeyCode::F6, + 0x0041 => KeyCode::F7, + 0x0042 => KeyCode::F8, + 0x0043 => KeyCode::F9, + 0x0044 => KeyCode::F10, + 0x0057 => KeyCode::F11, + 0x0058 => KeyCode::F12, + 0x0064 => KeyCode::F13, + 0x0065 => KeyCode::F14, + 0x0066 => KeyCode::F15, + 0x0067 => KeyCode::F16, + 0x0068 => KeyCode::F17, + 0x0069 => KeyCode::F18, + 0x006A => KeyCode::F19, + 0x006B => KeyCode::F20, + 0x006C => KeyCode::F21, + 0x006D => KeyCode::F22, + 0x006E => KeyCode::F23, + 0x0076 => KeyCode::F24, + 0xE037 => KeyCode::PrintScreen, + 0x0054 => KeyCode::PrintScreen, // Alt + PrintScreen + 0x0046 => KeyCode::ScrollLock, + 0x0045 => KeyCode::Pause, + 0xE046 => KeyCode::Pause, // Ctrl + Pause + 0xE06A => KeyCode::BrowserBack, + 0xE066 => KeyCode::BrowserFavorites, + 0xE069 => KeyCode::BrowserForward, + 0xE032 => KeyCode::BrowserHome, + 0xE067 => KeyCode::BrowserRefresh, + 0xE065 => KeyCode::BrowserSearch, + 0xE068 => KeyCode::BrowserStop, + 0xE06B => KeyCode::LaunchApp1, + 0xE021 => KeyCode::LaunchApp2, + 0xE06C => KeyCode::LaunchMail, + 0xE022 => KeyCode::MediaPlayPause, + 0xE06D => KeyCode::MediaSelect, + 0xE024 => KeyCode::MediaStop, + 0xE019 => KeyCode::MediaTrackNext, + 0xE010 => KeyCode::MediaTrackPrevious, + 0xE05E => KeyCode::Power, + 0xE02E => KeyCode::AudioVolumeDown, + 0xE020 => KeyCode::AudioVolumeMute, + 0xE030 => KeyCode::AudioVolumeUp, + _ => KeyCode::Unidentified(NativeKeyCode::Windows(scancode as u16)), + } + } +} diff --git a/src/platform_impl/linux/common/keymap.rs b/src/platform_impl/linux/common/keymap.rs new file mode 100644 index 0000000000..51083d6d38 --- /dev/null +++ b/src/platform_impl/linux/common/keymap.rs @@ -0,0 +1,937 @@ +//! Convert Wayland keys to winit keys. + +use crate::keyboard::{Key, KeyCode, KeyLocation, NativeKeyCode}; + +/// Map the raw X11-style keycode to the `KeyCode` enum. +/// +/// X11-style keycodes are offset by 8 from the keycodes the Linux kernel uses. +pub fn raw_keycode_to_keycode(keycode: u32) -> KeyCode { + let rawkey = keycode - 8; + // The keycode values are taken from linux/include/uapi/linux/input-event-codes.h, as + // libxkbcommon's documentation indicates that the keycode values we're getting from it are + // defined by the Linux kernel. If Winit programs end up being run on other Unix-likes which + // also use libxkbcommon, then I dearly hope the keycode values mean the same thing. + // + // Some of the keycodes are likely superfluous for our purposes, and some are ones which are + // difficult to test the correctness of, or discover the purpose of. Because of this, they've + // either been commented out here, or not included at all. + match rawkey { + 0 => KeyCode::Unidentified(NativeKeyCode::XkbCode(0)), + 1 => KeyCode::Escape, + 2 => KeyCode::Digit1, + 3 => KeyCode::Digit2, + 4 => KeyCode::Digit3, + 5 => KeyCode::Digit4, + 6 => KeyCode::Digit5, + 7 => KeyCode::Digit6, + 8 => KeyCode::Digit7, + 9 => KeyCode::Digit8, + 10 => KeyCode::Digit9, + 11 => KeyCode::Digit0, + 12 => KeyCode::Minus, + 13 => KeyCode::Equal, + 14 => KeyCode::Backspace, + 15 => KeyCode::Tab, + 16 => KeyCode::KeyQ, + 17 => KeyCode::KeyW, + 18 => KeyCode::KeyE, + 19 => KeyCode::KeyR, + 20 => KeyCode::KeyT, + 21 => KeyCode::KeyY, + 22 => KeyCode::KeyU, + 23 => KeyCode::KeyI, + 24 => KeyCode::KeyO, + 25 => KeyCode::KeyP, + 26 => KeyCode::BracketLeft, + 27 => KeyCode::BracketRight, + 28 => KeyCode::Enter, + 29 => KeyCode::ControlLeft, + 30 => KeyCode::KeyA, + 31 => KeyCode::KeyS, + 32 => KeyCode::KeyD, + 33 => KeyCode::KeyF, + 34 => KeyCode::KeyG, + 35 => KeyCode::KeyH, + 36 => KeyCode::KeyJ, + 37 => KeyCode::KeyK, + 38 => KeyCode::KeyL, + 39 => KeyCode::Semicolon, + 40 => KeyCode::Quote, + 41 => KeyCode::Backquote, + 42 => KeyCode::ShiftLeft, + 43 => KeyCode::Backslash, + 44 => KeyCode::KeyZ, + 45 => KeyCode::KeyX, + 46 => KeyCode::KeyC, + 47 => KeyCode::KeyV, + 48 => KeyCode::KeyB, + 49 => KeyCode::KeyN, + 50 => KeyCode::KeyM, + 51 => KeyCode::Comma, + 52 => KeyCode::Period, + 53 => KeyCode::Slash, + 54 => KeyCode::ShiftRight, + 55 => KeyCode::NumpadMultiply, + 56 => KeyCode::AltLeft, + 57 => KeyCode::Space, + 58 => KeyCode::CapsLock, + 59 => KeyCode::F1, + 60 => KeyCode::F2, + 61 => KeyCode::F3, + 62 => KeyCode::F4, + 63 => KeyCode::F5, + 64 => KeyCode::F6, + 65 => KeyCode::F7, + 66 => KeyCode::F8, + 67 => KeyCode::F9, + 68 => KeyCode::F10, + 69 => KeyCode::NumLock, + 70 => KeyCode::ScrollLock, + 71 => KeyCode::Numpad7, + 72 => KeyCode::Numpad8, + 73 => KeyCode::Numpad9, + 74 => KeyCode::NumpadSubtract, + 75 => KeyCode::Numpad4, + 76 => KeyCode::Numpad5, + 77 => KeyCode::Numpad6, + 78 => KeyCode::NumpadAdd, + 79 => KeyCode::Numpad1, + 80 => KeyCode::Numpad2, + 81 => KeyCode::Numpad3, + 82 => KeyCode::Numpad0, + 83 => KeyCode::NumpadDecimal, + 85 => KeyCode::Lang5, + 86 => KeyCode::IntlBackslash, + 87 => KeyCode::F11, + 88 => KeyCode::F12, + 89 => KeyCode::IntlRo, + 90 => KeyCode::Lang3, + 91 => KeyCode::Lang4, + 92 => KeyCode::Convert, + 93 => KeyCode::KanaMode, + 94 => KeyCode::NonConvert, + // 95 => KeyCode::KPJPCOMMA, + 96 => KeyCode::NumpadEnter, + 97 => KeyCode::ControlRight, + 98 => KeyCode::NumpadDivide, + 99 => KeyCode::PrintScreen, + 100 => KeyCode::AltRight, + // 101 => KeyCode::LINEFEED, + 102 => KeyCode::Home, + 103 => KeyCode::ArrowUp, + 104 => KeyCode::PageUp, + 105 => KeyCode::ArrowLeft, + 106 => KeyCode::ArrowRight, + 107 => KeyCode::End, + 108 => KeyCode::ArrowDown, + 109 => KeyCode::PageDown, + 110 => KeyCode::Insert, + 111 => KeyCode::Delete, + // 112 => KeyCode::MACRO, + 113 => KeyCode::AudioVolumeMute, + 114 => KeyCode::AudioVolumeDown, + 115 => KeyCode::AudioVolumeUp, + // 116 => KeyCode::POWER, + 117 => KeyCode::NumpadEqual, + // 118 => KeyCode::KPPLUSMINUS, + 119 => KeyCode::Pause, + // 120 => KeyCode::SCALE, + 121 => KeyCode::NumpadComma, + 122 => KeyCode::Lang1, + 123 => KeyCode::Lang2, + 124 => KeyCode::IntlYen, + 125 => KeyCode::SuperLeft, + 126 => KeyCode::SuperRight, + 127 => KeyCode::ContextMenu, + // 128 => KeyCode::STOP, + // 129 => KeyCode::AGAIN, + // 130 => KeyCode::PROPS, + // 131 => KeyCode::UNDO, + // 132 => KeyCode::FRONT, + // 133 => KeyCode::COPY, + // 134 => KeyCode::OPEN, + // 135 => KeyCode::PASTE, + // 136 => KeyCode::FIND, + // 137 => KeyCode::CUT, + // 138 => KeyCode::HELP, + // 139 => KeyCode::MENU, + // 140 => KeyCode::CALC, + // 141 => KeyCode::SETUP, + // 142 => KeyCode::SLEEP, + // 143 => KeyCode::WAKEUP, + // 144 => KeyCode::FILE, + // 145 => KeyCode::SENDFILE, + // 146 => KeyCode::DELETEFILE, + // 147 => KeyCode::XFER, + // 148 => KeyCode::PROG1, + // 149 => KeyCode::PROG2, + // 150 => KeyCode::WWW, + // 151 => KeyCode::MSDOS, + // 152 => KeyCode::COFFEE, + // 153 => KeyCode::ROTATE_DISPLAY, + // 154 => KeyCode::CYCLEWINDOWS, + // 155 => KeyCode::MAIL, + // 156 => KeyCode::BOOKMARKS, + // 157 => KeyCode::COMPUTER, + // 158 => KeyCode::BACK, + // 159 => KeyCode::FORWARD, + // 160 => KeyCode::CLOSECD, + // 161 => KeyCode::EJECTCD, + // 162 => KeyCode::EJECTCLOSECD, + 163 => KeyCode::MediaTrackNext, + 164 => KeyCode::MediaPlayPause, + 165 => KeyCode::MediaTrackPrevious, + 166 => KeyCode::MediaStop, + // 167 => KeyCode::RECORD, + // 168 => KeyCode::REWIND, + // 169 => KeyCode::PHONE, + // 170 => KeyCode::ISO, + // 171 => KeyCode::CONFIG, + // 172 => KeyCode::HOMEPAGE, + // 173 => KeyCode::REFRESH, + // 174 => KeyCode::EXIT, + // 175 => KeyCode::MOVE, + // 176 => KeyCode::EDIT, + // 177 => KeyCode::SCROLLUP, + // 178 => KeyCode::SCROLLDOWN, + // 179 => KeyCode::KPLEFTPAREN, + // 180 => KeyCode::KPRIGHTPAREN, + // 181 => KeyCode::NEW, + // 182 => KeyCode::REDO, + 183 => KeyCode::F13, + 184 => KeyCode::F14, + 185 => KeyCode::F15, + 186 => KeyCode::F16, + 187 => KeyCode::F17, + 188 => KeyCode::F18, + 189 => KeyCode::F19, + 190 => KeyCode::F20, + 191 => KeyCode::F21, + 192 => KeyCode::F22, + 193 => KeyCode::F23, + 194 => KeyCode::F24, + // 200 => KeyCode::PLAYCD, + // 201 => KeyCode::PAUSECD, + // 202 => KeyCode::PROG3, + // 203 => KeyCode::PROG4, + // 204 => KeyCode::DASHBOARD, + // 205 => KeyCode::SUSPEND, + // 206 => KeyCode::CLOSE, + // 207 => KeyCode::PLAY, + // 208 => KeyCode::FASTFORWARD, + // 209 => KeyCode::BASSBOOST, + // 210 => KeyCode::PRINT, + // 211 => KeyCode::HP, + // 212 => KeyCode::CAMERA, + // 213 => KeyCode::SOUND, + // 214 => KeyCode::QUESTION, + // 215 => KeyCode::EMAIL, + // 216 => KeyCode::CHAT, + // 217 => KeyCode::SEARCH, + // 218 => KeyCode::CONNECT, + // 219 => KeyCode::FINANCE, + // 220 => KeyCode::SPORT, + // 221 => KeyCode::SHOP, + // 222 => KeyCode::ALTERASE, + // 223 => KeyCode::CANCEL, + // 224 => KeyCode::BRIGHTNESSDOW, + // 225 => KeyCode::BRIGHTNESSU, + // 226 => KeyCode::MEDIA, + // 227 => KeyCode::SWITCHVIDEOMODE, + // 228 => KeyCode::KBDILLUMTOGGLE, + // 229 => KeyCode::KBDILLUMDOWN, + // 230 => KeyCode::KBDILLUMUP, + // 231 => KeyCode::SEND, + // 232 => KeyCode::REPLY, + // 233 => KeyCode::FORWARDMAIL, + // 234 => KeyCode::SAVE, + // 235 => KeyCode::DOCUMENTS, + // 236 => KeyCode::BATTERY, + // 237 => KeyCode::BLUETOOTH, + // 238 => KeyCode::WLAN, + // 239 => KeyCode::UWB, + 240 => KeyCode::Unidentified(NativeKeyCode::Unidentified), + // 241 => KeyCode::VIDEO_NEXT, + // 242 => KeyCode::VIDEO_PREV, + // 243 => KeyCode::BRIGHTNESS_CYCLE, + // 244 => KeyCode::BRIGHTNESS_AUTO, + // 245 => KeyCode::DISPLAY_OFF, + // 246 => KeyCode::WWAN, + // 247 => KeyCode::RFKILL, + // 248 => KeyCode::KEY_MICMUTE, + _ => KeyCode::Unidentified(NativeKeyCode::XkbCode(rawkey)), + } +} + +pub fn keycode_to_raw(keycode: KeyCode) -> Option { + match keycode { + KeyCode::Unidentified(NativeKeyCode::Unidentified) => Some(240), + KeyCode::Unidentified(NativeKeyCode::XkbCode(raw)) => Some(raw), + KeyCode::Escape => Some(1), + KeyCode::Digit1 => Some(2), + KeyCode::Digit2 => Some(3), + KeyCode::Digit3 => Some(4), + KeyCode::Digit4 => Some(5), + KeyCode::Digit5 => Some(6), + KeyCode::Digit6 => Some(7), + KeyCode::Digit7 => Some(8), + KeyCode::Digit8 => Some(9), + KeyCode::Digit9 => Some(10), + KeyCode::Digit0 => Some(11), + KeyCode::Minus => Some(12), + KeyCode::Equal => Some(13), + KeyCode::Backspace => Some(14), + KeyCode::Tab => Some(15), + KeyCode::KeyQ => Some(16), + KeyCode::KeyW => Some(17), + KeyCode::KeyE => Some(18), + KeyCode::KeyR => Some(19), + KeyCode::KeyT => Some(20), + KeyCode::KeyY => Some(21), + KeyCode::KeyU => Some(22), + KeyCode::KeyI => Some(23), + KeyCode::KeyO => Some(24), + KeyCode::KeyP => Some(25), + KeyCode::BracketLeft => Some(26), + KeyCode::BracketRight => Some(27), + KeyCode::Enter => Some(28), + KeyCode::ControlLeft => Some(29), + KeyCode::KeyA => Some(30), + KeyCode::KeyS => Some(31), + KeyCode::KeyD => Some(32), + KeyCode::KeyF => Some(33), + KeyCode::KeyG => Some(34), + KeyCode::KeyH => Some(35), + KeyCode::KeyJ => Some(36), + KeyCode::KeyK => Some(37), + KeyCode::KeyL => Some(38), + KeyCode::Semicolon => Some(39), + KeyCode::Quote => Some(40), + KeyCode::Backquote => Some(41), + KeyCode::ShiftLeft => Some(42), + KeyCode::Backslash => Some(43), + KeyCode::KeyZ => Some(44), + KeyCode::KeyX => Some(45), + KeyCode::KeyC => Some(46), + KeyCode::KeyV => Some(47), + KeyCode::KeyB => Some(48), + KeyCode::KeyN => Some(49), + KeyCode::KeyM => Some(50), + KeyCode::Comma => Some(51), + KeyCode::Period => Some(52), + KeyCode::Slash => Some(53), + KeyCode::ShiftRight => Some(54), + KeyCode::NumpadMultiply => Some(55), + KeyCode::AltLeft => Some(56), + KeyCode::Space => Some(57), + KeyCode::CapsLock => Some(58), + KeyCode::F1 => Some(59), + KeyCode::F2 => Some(60), + KeyCode::F3 => Some(61), + KeyCode::F4 => Some(62), + KeyCode::F5 => Some(63), + KeyCode::F6 => Some(64), + KeyCode::F7 => Some(65), + KeyCode::F8 => Some(66), + KeyCode::F9 => Some(67), + KeyCode::F10 => Some(68), + KeyCode::NumLock => Some(69), + KeyCode::ScrollLock => Some(70), + KeyCode::Numpad7 => Some(71), + KeyCode::Numpad8 => Some(72), + KeyCode::Numpad9 => Some(73), + KeyCode::NumpadSubtract => Some(74), + KeyCode::Numpad4 => Some(75), + KeyCode::Numpad5 => Some(76), + KeyCode::Numpad6 => Some(77), + KeyCode::NumpadAdd => Some(78), + KeyCode::Numpad1 => Some(79), + KeyCode::Numpad2 => Some(80), + KeyCode::Numpad3 => Some(81), + KeyCode::Numpad0 => Some(82), + KeyCode::NumpadDecimal => Some(83), + KeyCode::Lang5 => Some(85), + KeyCode::IntlBackslash => Some(86), + KeyCode::F11 => Some(87), + KeyCode::F12 => Some(88), + KeyCode::IntlRo => Some(89), + KeyCode::Lang3 => Some(90), + KeyCode::Lang4 => Some(91), + KeyCode::Convert => Some(92), + KeyCode::KanaMode => Some(93), + KeyCode::NonConvert => Some(94), + KeyCode::NumpadEnter => Some(96), + KeyCode::ControlRight => Some(97), + KeyCode::NumpadDivide => Some(98), + KeyCode::PrintScreen => Some(99), + KeyCode::AltRight => Some(100), + KeyCode::Home => Some(102), + KeyCode::ArrowUp => Some(103), + KeyCode::PageUp => Some(104), + KeyCode::ArrowLeft => Some(105), + KeyCode::ArrowRight => Some(106), + KeyCode::End => Some(107), + KeyCode::ArrowDown => Some(108), + KeyCode::PageDown => Some(109), + KeyCode::Insert => Some(110), + KeyCode::Delete => Some(111), + KeyCode::AudioVolumeMute => Some(113), + KeyCode::AudioVolumeDown => Some(114), + KeyCode::AudioVolumeUp => Some(115), + KeyCode::NumpadEqual => Some(117), + KeyCode::Pause => Some(119), + KeyCode::NumpadComma => Some(121), + KeyCode::Lang1 => Some(122), + KeyCode::Lang2 => Some(123), + KeyCode::IntlYen => Some(124), + KeyCode::SuperLeft => Some(125), + KeyCode::SuperRight => Some(126), + KeyCode::ContextMenu => Some(127), + KeyCode::MediaTrackNext => Some(163), + KeyCode::MediaPlayPause => Some(164), + KeyCode::MediaTrackPrevious => Some(165), + KeyCode::MediaStop => Some(166), + KeyCode::F13 => Some(183), + KeyCode::F14 => Some(184), + KeyCode::F15 => Some(185), + KeyCode::F16 => Some(186), + KeyCode::F17 => Some(187), + KeyCode::F18 => Some(188), + KeyCode::F19 => Some(189), + KeyCode::F20 => Some(190), + KeyCode::F21 => Some(191), + KeyCode::F22 => Some(192), + KeyCode::F23 => Some(193), + KeyCode::F24 => Some(194), + _ => None, + } + .map(|raw| raw + 8) +} + +pub fn keysym_to_key(keysym: u32) -> Key<'static> { + use xkbcommon_dl::keysyms; + match keysym { + // TTY function keys + keysyms::XKB_KEY_BackSpace => Key::Backspace, + keysyms::XKB_KEY_Tab => Key::Tab, + // keysyms::XKB_KEY_Linefeed => Key::Linefeed, + keysyms::XKB_KEY_Clear => Key::Clear, + keysyms::XKB_KEY_Return => Key::Enter, + keysyms::XKB_KEY_Pause => Key::Pause, + keysyms::XKB_KEY_Scroll_Lock => Key::ScrollLock, + keysyms::XKB_KEY_Sys_Req => Key::PrintScreen, + keysyms::XKB_KEY_Escape => Key::Escape, + keysyms::XKB_KEY_Delete => Key::Delete, + + // IME keys + keysyms::XKB_KEY_Multi_key => Key::Compose, + keysyms::XKB_KEY_Codeinput => Key::CodeInput, + keysyms::XKB_KEY_SingleCandidate => Key::SingleCandidate, + keysyms::XKB_KEY_MultipleCandidate => Key::AllCandidates, + keysyms::XKB_KEY_PreviousCandidate => Key::PreviousCandidate, + + // Japanese keys + keysyms::XKB_KEY_Kanji => Key::KanjiMode, + keysyms::XKB_KEY_Muhenkan => Key::NonConvert, + keysyms::XKB_KEY_Henkan_Mode => Key::Convert, + keysyms::XKB_KEY_Romaji => Key::Romaji, + keysyms::XKB_KEY_Hiragana => Key::Hiragana, + keysyms::XKB_KEY_Hiragana_Katakana => Key::HiraganaKatakana, + keysyms::XKB_KEY_Zenkaku => Key::Zenkaku, + keysyms::XKB_KEY_Hankaku => Key::Hankaku, + keysyms::XKB_KEY_Zenkaku_Hankaku => Key::ZenkakuHankaku, + // keysyms::XKB_KEY_Touroku => Key::Touroku, + // keysyms::XKB_KEY_Massyo => Key::Massyo, + keysyms::XKB_KEY_Kana_Lock => Key::KanaMode, + // TODO: This seems a tad perverse, but I'm not really familiar with japanese keyboards. + // MDN documents this as a valid mapping, however. + // keysyms::XKB_KEY_Kana_Shift => Key::KanaMode, + // TODO: Is this the correct mapping? + // keysyms::XKB_KEY_Eisu_Shift => Key::Alphanumeric, + // keysyms::XKB_KEY_Eisu_toggle => Key::Alphanumeric, + // NOTE: The next three items are aliases for values we've already mapped. + // keysyms::XKB_KEY_Kanji_Bangou => Key::CodeInput, + // keysyms::XKB_KEY_Zen_Koho => Key::AllCandidates, + // keysyms::XKB_KEY_Mae_Koho => Key::PreviousCandidate, + + // Cursor control & motion + keysyms::XKB_KEY_Home => Key::Home, + keysyms::XKB_KEY_Left => Key::ArrowLeft, + keysyms::XKB_KEY_Up => Key::ArrowUp, + keysyms::XKB_KEY_Right => Key::ArrowRight, + keysyms::XKB_KEY_Down => Key::ArrowDown, + // keysyms::XKB_KEY_Prior => Key::PageUp, + keysyms::XKB_KEY_Page_Up => Key::PageUp, + // keysyms::XKB_KEY_Next => Key::PageDown, + keysyms::XKB_KEY_Page_Down => Key::PageDown, + keysyms::XKB_KEY_End => Key::End, + // keysyms::XKB_KEY_Begin => Key::Begin, + + // Misc. functions + keysyms::XKB_KEY_Select => Key::Select, + keysyms::XKB_KEY_Print => Key::PrintScreen, + keysyms::XKB_KEY_Execute => Key::Execute, + keysyms::XKB_KEY_Insert => Key::Insert, + keysyms::XKB_KEY_Undo => Key::Undo, + keysyms::XKB_KEY_Redo => Key::Redo, + keysyms::XKB_KEY_Menu => Key::ContextMenu, + keysyms::XKB_KEY_Find => Key::Find, + keysyms::XKB_KEY_Cancel => Key::Cancel, + keysyms::XKB_KEY_Help => Key::Help, + keysyms::XKB_KEY_Break => Key::Pause, + keysyms::XKB_KEY_Mode_switch => Key::ModeChange, + // keysyms::XKB_KEY_script_switch => Key::ModeChange, + keysyms::XKB_KEY_Num_Lock => Key::NumLock, + + // Keypad keys + // keysyms::XKB_KEY_KP_Space => Key::Character(" "), + keysyms::XKB_KEY_KP_Tab => Key::Tab, + keysyms::XKB_KEY_KP_Enter => Key::Enter, + keysyms::XKB_KEY_KP_F1 => Key::F1, + keysyms::XKB_KEY_KP_F2 => Key::F2, + keysyms::XKB_KEY_KP_F3 => Key::F3, + keysyms::XKB_KEY_KP_F4 => Key::F4, + keysyms::XKB_KEY_KP_Home => Key::Home, + keysyms::XKB_KEY_KP_Left => Key::ArrowLeft, + keysyms::XKB_KEY_KP_Up => Key::ArrowLeft, + keysyms::XKB_KEY_KP_Right => Key::ArrowRight, + keysyms::XKB_KEY_KP_Down => Key::ArrowDown, + // keysyms::XKB_KEY_KP_Prior => Key::PageUp, + keysyms::XKB_KEY_KP_Page_Up => Key::PageUp, + // keysyms::XKB_KEY_KP_Next => Key::PageDown, + keysyms::XKB_KEY_KP_Page_Down => Key::PageDown, + keysyms::XKB_KEY_KP_End => Key::End, + // TODO: What is this supposed to map to? + // This is the key labeled "5" on the numpad when NumLock is off. + // keysyms::XKB_KEY_KP_Begin => Key::Begin, + keysyms::XKB_KEY_KP_Insert => Key::Insert, + keysyms::XKB_KEY_KP_Delete => Key::Delete, + // keysyms::XKB_KEY_KP_Equal => Key::Equal, + // keysyms::XKB_KEY_KP_Multiply => Key::Multiply, + // keysyms::XKB_KEY_KP_Add => Key::Add, + // keysyms::XKB_KEY_KP_Separator => Key::Separator, + // keysyms::XKB_KEY_KP_Subtract => Key::Subtract, + // keysyms::XKB_KEY_KP_Decimal => Key::Decimal, + // keysyms::XKB_KEY_KP_Divide => Key::Divide, + + // keysyms::XKB_KEY_KP_0 => Key::Character("0"), + // keysyms::XKB_KEY_KP_1 => Key::Character("1"), + // keysyms::XKB_KEY_KP_2 => Key::Character("2"), + // keysyms::XKB_KEY_KP_3 => Key::Character("3"), + // keysyms::XKB_KEY_KP_4 => Key::Character("4"), + // keysyms::XKB_KEY_KP_5 => Key::Character("5"), + // keysyms::XKB_KEY_KP_6 => Key::Character("6"), + // keysyms::XKB_KEY_KP_7 => Key::Character("7"), + // keysyms::XKB_KEY_KP_8 => Key::Character("8"), + // keysyms::XKB_KEY_KP_9 => Key::Character("9"), + + // Function keys + keysyms::XKB_KEY_F1 => Key::F1, + keysyms::XKB_KEY_F2 => Key::F2, + keysyms::XKB_KEY_F3 => Key::F3, + keysyms::XKB_KEY_F4 => Key::F4, + keysyms::XKB_KEY_F5 => Key::F5, + keysyms::XKB_KEY_F6 => Key::F6, + keysyms::XKB_KEY_F7 => Key::F7, + keysyms::XKB_KEY_F8 => Key::F8, + keysyms::XKB_KEY_F9 => Key::F9, + keysyms::XKB_KEY_F10 => Key::F10, + keysyms::XKB_KEY_F11 => Key::F11, + keysyms::XKB_KEY_F12 => Key::F12, + keysyms::XKB_KEY_F13 => Key::F13, + keysyms::XKB_KEY_F14 => Key::F14, + keysyms::XKB_KEY_F15 => Key::F15, + keysyms::XKB_KEY_F16 => Key::F16, + keysyms::XKB_KEY_F17 => Key::F17, + keysyms::XKB_KEY_F18 => Key::F18, + keysyms::XKB_KEY_F19 => Key::F19, + keysyms::XKB_KEY_F20 => Key::F20, + keysyms::XKB_KEY_F21 => Key::F21, + keysyms::XKB_KEY_F22 => Key::F22, + keysyms::XKB_KEY_F23 => Key::F23, + keysyms::XKB_KEY_F24 => Key::F24, + keysyms::XKB_KEY_F25 => Key::F25, + keysyms::XKB_KEY_F26 => Key::F26, + keysyms::XKB_KEY_F27 => Key::F27, + keysyms::XKB_KEY_F28 => Key::F28, + keysyms::XKB_KEY_F29 => Key::F29, + keysyms::XKB_KEY_F30 => Key::F30, + keysyms::XKB_KEY_F31 => Key::F31, + keysyms::XKB_KEY_F32 => Key::F32, + keysyms::XKB_KEY_F33 => Key::F33, + keysyms::XKB_KEY_F34 => Key::F34, + keysyms::XKB_KEY_F35 => Key::F35, + + // Modifiers + keysyms::XKB_KEY_Shift_L => Key::Shift, + keysyms::XKB_KEY_Shift_R => Key::Shift, + keysyms::XKB_KEY_Control_L => Key::Control, + keysyms::XKB_KEY_Control_R => Key::Control, + keysyms::XKB_KEY_Caps_Lock => Key::CapsLock, + // keysyms::XKB_KEY_Shift_Lock => Key::ShiftLock, + + // keysyms::XKB_KEY_Meta_L => Key::Meta, + // keysyms::XKB_KEY_Meta_R => Key::Meta, + keysyms::XKB_KEY_Alt_L => Key::Alt, + keysyms::XKB_KEY_Alt_R => Key::Alt, + keysyms::XKB_KEY_Super_L => Key::Super, + keysyms::XKB_KEY_Super_R => Key::Super, + keysyms::XKB_KEY_Hyper_L => Key::Hyper, + keysyms::XKB_KEY_Hyper_R => Key::Hyper, + + // XKB function and modifier keys + // keysyms::XKB_KEY_ISO_Lock => Key::IsoLock, + // keysyms::XKB_KEY_ISO_Level2_Latch => Key::IsoLevel2Latch, + keysyms::XKB_KEY_ISO_Level3_Shift => Key::AltGraph, + keysyms::XKB_KEY_ISO_Level3_Latch => Key::AltGraph, + keysyms::XKB_KEY_ISO_Level3_Lock => Key::AltGraph, + // keysyms::XKB_KEY_ISO_Level5_Shift => Key::IsoLevel5Shift, + // keysyms::XKB_KEY_ISO_Level5_Latch => Key::IsoLevel5Latch, + // keysyms::XKB_KEY_ISO_Level5_Lock => Key::IsoLevel5Lock, + // keysyms::XKB_KEY_ISO_Group_Shift => Key::IsoGroupShift, + // keysyms::XKB_KEY_ISO_Group_Latch => Key::IsoGroupLatch, + // keysyms::XKB_KEY_ISO_Group_Lock => Key::IsoGroupLock, + keysyms::XKB_KEY_ISO_Next_Group => Key::GroupNext, + // keysyms::XKB_KEY_ISO_Next_Group_Lock => Key::GroupNextLock, + keysyms::XKB_KEY_ISO_Prev_Group => Key::GroupPrevious, + // keysyms::XKB_KEY_ISO_Prev_Group_Lock => Key::GroupPreviousLock, + keysyms::XKB_KEY_ISO_First_Group => Key::GroupFirst, + // keysyms::XKB_KEY_ISO_First_Group_Lock => Key::GroupFirstLock, + keysyms::XKB_KEY_ISO_Last_Group => Key::GroupLast, + // keysyms::XKB_KEY_ISO_Last_Group_Lock => Key::GroupLastLock, + // + keysyms::XKB_KEY_ISO_Left_Tab => Key::Tab, + // keysyms::XKB_KEY_ISO_Move_Line_Up => Key::IsoMoveLineUp, + // keysyms::XKB_KEY_ISO_Move_Line_Down => Key::IsoMoveLineDown, + // keysyms::XKB_KEY_ISO_Partial_Line_Up => Key::IsoPartialLineUp, + // keysyms::XKB_KEY_ISO_Partial_Line_Down => Key::IsoPartialLineDown, + // keysyms::XKB_KEY_ISO_Partial_Space_Left => Key::IsoPartialSpaceLeft, + // keysyms::XKB_KEY_ISO_Partial_Space_Right => Key::IsoPartialSpaceRight, + // keysyms::XKB_KEY_ISO_Set_Margin_Left => Key::IsoSetMarginLeft, + // keysyms::XKB_KEY_ISO_Set_Margin_Right => Key::IsoSetMarginRight, + // keysyms::XKB_KEY_ISO_Release_Margin_Left => Key::IsoReleaseMarginLeft, + // keysyms::XKB_KEY_ISO_Release_Margin_Right => Key::IsoReleaseMarginRight, + // keysyms::XKB_KEY_ISO_Release_Both_Margins => Key::IsoReleaseBothMargins, + // keysyms::XKB_KEY_ISO_Fast_Cursor_Left => Key::IsoFastCursorLeft, + // keysyms::XKB_KEY_ISO_Fast_Cursor_Right => Key::IsoFastCursorRight, + // keysyms::XKB_KEY_ISO_Fast_Cursor_Up => Key::IsoFastCursorUp, + // keysyms::XKB_KEY_ISO_Fast_Cursor_Down => Key::IsoFastCursorDown, + // keysyms::XKB_KEY_ISO_Continuous_Underline => Key::IsoContinuousUnderline, + // keysyms::XKB_KEY_ISO_Discontinuous_Underline => Key::IsoDiscontinuousUnderline, + // keysyms::XKB_KEY_ISO_Emphasize => Key::IsoEmphasize, + // keysyms::XKB_KEY_ISO_Center_Object => Key::IsoCenterObject, + keysyms::XKB_KEY_ISO_Enter => Key::Enter, + + // XKB_KEY_dead_grave..XKB_KEY_dead_currency + + // XKB_KEY_dead_lowline..XKB_KEY_dead_longsolidusoverlay + + // XKB_KEY_dead_a..XKB_KEY_dead_capital_schwa + + // XKB_KEY_dead_greek + + // XKB_KEY_First_Virtual_Screen..XKB_KEY_Terminate_Server + + // XKB_KEY_AccessX_Enable..XKB_KEY_AudibleBell_Enable + + // XKB_KEY_Pointer_Left..XKB_KEY_Pointer_Drag5 + + // XKB_KEY_Pointer_EnableKeys..XKB_KEY_Pointer_DfltBtnPrev + + // XKB_KEY_ch..XKB_KEY_C_H + + // 3270 terminal keys + // keysyms::XKB_KEY_3270_Duplicate => Key::Duplicate, + // keysyms::XKB_KEY_3270_FieldMark => Key::FieldMark, + // keysyms::XKB_KEY_3270_Right2 => Key::Right2, + // keysyms::XKB_KEY_3270_Left2 => Key::Left2, + // keysyms::XKB_KEY_3270_BackTab => Key::BackTab, + keysyms::XKB_KEY_3270_EraseEOF => Key::EraseEof, + // keysyms::XKB_KEY_3270_EraseInput => Key::EraseInput, + // keysyms::XKB_KEY_3270_Reset => Key::Reset, + // keysyms::XKB_KEY_3270_Quit => Key::Quit, + // keysyms::XKB_KEY_3270_PA1 => Key::Pa1, + // keysyms::XKB_KEY_3270_PA2 => Key::Pa2, + // keysyms::XKB_KEY_3270_PA3 => Key::Pa3, + // keysyms::XKB_KEY_3270_Test => Key::Test, + keysyms::XKB_KEY_3270_Attn => Key::Attn, + // keysyms::XKB_KEY_3270_CursorBlink => Key::CursorBlink, + // keysyms::XKB_KEY_3270_AltCursor => Key::AltCursor, + // keysyms::XKB_KEY_3270_KeyClick => Key::KeyClick, + // keysyms::XKB_KEY_3270_Jump => Key::Jump, + // keysyms::XKB_KEY_3270_Ident => Key::Ident, + // keysyms::XKB_KEY_3270_Rule => Key::Rule, + // keysyms::XKB_KEY_3270_Copy => Key::Copy, + keysyms::XKB_KEY_3270_Play => Key::Play, + // keysyms::XKB_KEY_3270_Setup => Key::Setup, + // keysyms::XKB_KEY_3270_Record => Key::Record, + // keysyms::XKB_KEY_3270_ChangeScreen => Key::ChangeScreen, + // keysyms::XKB_KEY_3270_DeleteWord => Key::DeleteWord, + keysyms::XKB_KEY_3270_ExSelect => Key::ExSel, + keysyms::XKB_KEY_3270_CursorSelect => Key::CrSel, + keysyms::XKB_KEY_3270_PrintScreen => Key::PrintScreen, + keysyms::XKB_KEY_3270_Enter => Key::Enter, + + keysyms::XKB_KEY_space => Key::Space, + // XKB_KEY_exclam..XKB_KEY_Sinh_kunddaliya + + // XFree86 + // keysyms::XKB_KEY_XF86ModeLock => Key::ModeLock, + + // XFree86 - Backlight controls + keysyms::XKB_KEY_XF86MonBrightnessUp => Key::BrightnessUp, + keysyms::XKB_KEY_XF86MonBrightnessDown => Key::BrightnessDown, + // keysyms::XKB_KEY_XF86KbdLightOnOff => Key::LightOnOff, + // keysyms::XKB_KEY_XF86KbdBrightnessUp => Key::KeyboardBrightnessUp, + // keysyms::XKB_KEY_XF86KbdBrightnessDown => Key::KeyboardBrightnessDown, + + // XFree86 - "Internet" + keysyms::XKB_KEY_XF86Standby => Key::Standby, + keysyms::XKB_KEY_XF86AudioLowerVolume => Key::AudioVolumeDown, + keysyms::XKB_KEY_XF86AudioRaiseVolume => Key::AudioVolumeUp, + keysyms::XKB_KEY_XF86AudioPlay => Key::MediaPlay, + keysyms::XKB_KEY_XF86AudioStop => Key::MediaStop, + keysyms::XKB_KEY_XF86AudioPrev => Key::MediaTrackPrevious, + keysyms::XKB_KEY_XF86AudioNext => Key::MediaTrackNext, + keysyms::XKB_KEY_XF86HomePage => Key::BrowserHome, + keysyms::XKB_KEY_XF86Mail => Key::LaunchMail, + // keysyms::XKB_KEY_XF86Start => Key::Start, + keysyms::XKB_KEY_XF86Search => Key::BrowserSearch, + keysyms::XKB_KEY_XF86AudioRecord => Key::MediaRecord, + + // XFree86 - PDA + keysyms::XKB_KEY_XF86Calculator => Key::LaunchApplication2, + // keysyms::XKB_KEY_XF86Memo => Key::Memo, + // keysyms::XKB_KEY_XF86ToDoList => Key::ToDoList, + keysyms::XKB_KEY_XF86Calendar => Key::LaunchCalendar, + keysyms::XKB_KEY_XF86PowerDown => Key::Power, + // keysyms::XKB_KEY_XF86ContrastAdjust => Key::AdjustContrast, + // keysyms::XKB_KEY_XF86RockerUp => Key::RockerUp, + // keysyms::XKB_KEY_XF86RockerDown => Key::RockerDown, + // keysyms::XKB_KEY_XF86RockerEnter => Key::RockerEnter, + + // XFree86 - More "Internet" + keysyms::XKB_KEY_XF86Back => Key::BrowserBack, + keysyms::XKB_KEY_XF86Forward => Key::BrowserForward, + // keysyms::XKB_KEY_XF86Stop => Key::Stop, + keysyms::XKB_KEY_XF86Refresh => Key::BrowserRefresh, + keysyms::XKB_KEY_XF86PowerOff => Key::Power, + keysyms::XKB_KEY_XF86WakeUp => Key::WakeUp, + keysyms::XKB_KEY_XF86Eject => Key::Eject, + keysyms::XKB_KEY_XF86ScreenSaver => Key::LaunchScreenSaver, + keysyms::XKB_KEY_XF86WWW => Key::LaunchWebBrowser, + keysyms::XKB_KEY_XF86Sleep => Key::Standby, + keysyms::XKB_KEY_XF86Favorites => Key::BrowserFavorites, + keysyms::XKB_KEY_XF86AudioPause => Key::MediaPause, + // keysyms::XKB_KEY_XF86AudioMedia => Key::AudioMedia, + keysyms::XKB_KEY_XF86MyComputer => Key::LaunchApplication1, + // keysyms::XKB_KEY_XF86VendorHome => Key::VendorHome, + // keysyms::XKB_KEY_XF86LightBulb => Key::LightBulb, + // keysyms::XKB_KEY_XF86Shop => Key::BrowserShop, + // keysyms::XKB_KEY_XF86History => Key::BrowserHistory, + // keysyms::XKB_KEY_XF86OpenURL => Key::OpenUrl, + // keysyms::XKB_KEY_XF86AddFavorite => Key::AddFavorite, + // keysyms::XKB_KEY_XF86HotLinks => Key::HotLinks, + // keysyms::XKB_KEY_XF86BrightnessAdjust => Key::BrightnessAdjust, + // keysyms::XKB_KEY_XF86Finance => Key::BrowserFinance, + // keysyms::XKB_KEY_XF86Community => Key::BrowserCommunity, + keysyms::XKB_KEY_XF86AudioRewind => Key::MediaRewind, + // keysyms::XKB_KEY_XF86BackForward => Key::???, + // XKB_KEY_XF86Launch0..XKB_KEY_XF86LaunchF + + // XKB_KEY_XF86ApplicationLeft..XKB_KEY_XF86CD + keysyms::XKB_KEY_XF86Calculater => Key::LaunchApplication2, // Nice typo, libxkbcommon :) + // XKB_KEY_XF86Clear + keysyms::XKB_KEY_XF86Close => Key::Close, + keysyms::XKB_KEY_XF86Copy => Key::Copy, + keysyms::XKB_KEY_XF86Cut => Key::Cut, + // XKB_KEY_XF86Display..XKB_KEY_XF86Documents + keysyms::XKB_KEY_XF86Excel => Key::LaunchSpreadsheet, + // XKB_KEY_XF86Explorer..XKB_KEY_XF86iTouch + keysyms::XKB_KEY_XF86LogOff => Key::LogOff, + // XKB_KEY_XF86Market..XKB_KEY_XF86MenuPB + keysyms::XKB_KEY_XF86MySites => Key::BrowserFavorites, + keysyms::XKB_KEY_XF86New => Key::New, + // XKB_KEY_XF86News..XKB_KEY_XF86OfficeHome + keysyms::XKB_KEY_XF86Open => Key::Open, + // XKB_KEY_XF86Option + keysyms::XKB_KEY_XF86Paste => Key::Paste, + keysyms::XKB_KEY_XF86Phone => Key::LaunchPhone, + // XKB_KEY_XF86Q + keysyms::XKB_KEY_XF86Reply => Key::MailReply, + keysyms::XKB_KEY_XF86Reload => Key::BrowserRefresh, + // XKB_KEY_XF86RotateWindows..XKB_KEY_XF86RotationKB + keysyms::XKB_KEY_XF86Save => Key::Save, + // XKB_KEY_XF86ScrollUp..XKB_KEY_XF86ScrollClick + keysyms::XKB_KEY_XF86Send => Key::MailSend, + keysyms::XKB_KEY_XF86Spell => Key::SpellCheck, + keysyms::XKB_KEY_XF86SplitScreen => Key::SplitScreenToggle, + // XKB_KEY_XF86Support..XKB_KEY_XF86User2KB + keysyms::XKB_KEY_XF86Video => Key::LaunchMediaPlayer, + // XKB_KEY_XF86WheelButton + keysyms::XKB_KEY_XF86Word => Key::LaunchWordProcessor, + // XKB_KEY_XF86Xfer + keysyms::XKB_KEY_XF86ZoomIn => Key::ZoomIn, + keysyms::XKB_KEY_XF86ZoomOut => Key::ZoomOut, + + // XKB_KEY_XF86Away..XKB_KEY_XF86Messenger + keysyms::XKB_KEY_XF86WebCam => Key::LaunchWebCam, + keysyms::XKB_KEY_XF86MailForward => Key::MailForward, + // XKB_KEY_XF86Pictures + keysyms::XKB_KEY_XF86Music => Key::LaunchMusicPlayer, + + // XKB_KEY_XF86Battery..XKB_KEY_XF86UWB + // + keysyms::XKB_KEY_XF86AudioForward => Key::MediaFastForward, + // XKB_KEY_XF86AudioRepeat + keysyms::XKB_KEY_XF86AudioRandomPlay => Key::RandomToggle, + keysyms::XKB_KEY_XF86Subtitle => Key::Subtitle, + keysyms::XKB_KEY_XF86AudioCycleTrack => Key::MediaAudioTrack, + // XKB_KEY_XF86CycleAngle..XKB_KEY_XF86Blue + // + keysyms::XKB_KEY_XF86Suspend => Key::Standby, + keysyms::XKB_KEY_XF86Hibernate => Key::Hibernate, + // XKB_KEY_XF86TouchpadToggle..XKB_KEY_XF86TouchpadOff + // + keysyms::XKB_KEY_XF86AudioMute => Key::AudioVolumeMute, + + // XKB_KEY_XF86Switch_VT_1..XKB_KEY_XF86Switch_VT_12 + + // XKB_KEY_XF86Ungrab..XKB_KEY_XF86ClearGrab + keysyms::XKB_KEY_XF86Next_VMode => Key::VideoModeNext, + // keysyms::XKB_KEY_XF86Prev_VMode => Key::VideoModePrevious, + // XKB_KEY_XF86LogWindowTree..XKB_KEY_XF86LogGrabInfo + + // XKB_KEY_SunFA_Grave..XKB_KEY_SunFA_Cedilla + + // keysyms::XKB_KEY_SunF36 => Key::F36 | Key::F11, + // keysyms::XKB_KEY_SunF37 => Key::F37 | Key::F12, + + // keysyms::XKB_KEY_SunSys_Req => Key::PrintScreen, + // The next couple of xkb (until XKB_KEY_SunStop) are already handled. + // XKB_KEY_SunPrint_Screen..XKB_KEY_SunPageDown + + // XKB_KEY_SunUndo..XKB_KEY_SunFront + keysyms::XKB_KEY_SunCopy => Key::Copy, + keysyms::XKB_KEY_SunOpen => Key::Open, + keysyms::XKB_KEY_SunPaste => Key::Paste, + keysyms::XKB_KEY_SunCut => Key::Cut, + + // XKB_KEY_SunPowerSwitch + keysyms::XKB_KEY_SunAudioLowerVolume => Key::AudioVolumeDown, + keysyms::XKB_KEY_SunAudioMute => Key::AudioVolumeMute, + keysyms::XKB_KEY_SunAudioRaiseVolume => Key::AudioVolumeUp, + // XKB_KEY_SunVideoDegauss + keysyms::XKB_KEY_SunVideoLowerBrightness => Key::BrightnessDown, + keysyms::XKB_KEY_SunVideoRaiseBrightness => Key::BrightnessUp, + // XKB_KEY_SunPowerSwitchShift + // + // Dead keys + keysyms::XKB_KEY_dead_greek => Key::Dead(None), + keysyms::XKB_KEY_dead_currency => Key::Dead(None), + keysyms::XKB_KEY_dead_stroke => Key::Dead(None), + keysyms::XKB_KEY_dead_voiced_sound => Key::Dead(None), + keysyms::XKB_KEY_dead_semivoiced_sound => Key::Dead(None), + keysyms::XKB_KEY_dead_lowline => Key::Dead(None), + keysyms::XKB_KEY_dead_aboveverticalline => Key::Dead(None), + keysyms::XKB_KEY_dead_belowverticalline => Key::Dead(None), + keysyms::XKB_KEY_dead_longsolidusoverlay => Key::Dead(None), + keysyms::XKB_KEY_dead_grave => Key::Dead(Some('`')), + keysyms::XKB_KEY_dead_acute => Key::Dead(Some('´')), + keysyms::XKB_KEY_dead_circumflex => Key::Dead(Some('^')), + keysyms::XKB_KEY_dead_tilde => Key::Dead(Some('~')), + keysyms::XKB_KEY_dead_macron => Key::Dead(Some('¯')), + keysyms::XKB_KEY_dead_breve => Key::Dead(Some('˘')), + keysyms::XKB_KEY_dead_abovedot => Key::Dead(Some('˙')), + keysyms::XKB_KEY_dead_diaeresis => Key::Dead(Some('¨')), + keysyms::XKB_KEY_dead_abovering => Key::Dead(Some('°')), + keysyms::XKB_KEY_dead_doubleacute => Key::Dead(Some('˝')), + keysyms::XKB_KEY_dead_caron => Key::Dead(Some('ˇ')), + keysyms::XKB_KEY_dead_cedilla => Key::Dead(Some('¸')), + keysyms::XKB_KEY_dead_ogonek => Key::Dead(Some('˛')), + keysyms::XKB_KEY_dead_iota => Key::Dead(Some('ͺ')), + keysyms::XKB_KEY_dead_belowdot => Key::Dead(None), + keysyms::XKB_KEY_dead_hook => Key::Dead(None), + keysyms::XKB_KEY_dead_horn => Key::Dead(None), + keysyms::XKB_KEY_dead_abovecomma => Key::Dead(None), + keysyms::XKB_KEY_dead_abovereversedcomma => Key::Dead(None), + keysyms::XKB_KEY_dead_doublegrave => Key::Dead(None), + keysyms::XKB_KEY_dead_belowring => Key::Dead(Some('˳')), + keysyms::XKB_KEY_dead_belowmacron => Key::Dead(Some('ˍ')), + keysyms::XKB_KEY_dead_belowcircumflex => Key::Dead(None), + keysyms::XKB_KEY_dead_belowtilde => Key::Dead(Some('˷')), + keysyms::XKB_KEY_dead_belowbreve => Key::Dead(None), + keysyms::XKB_KEY_dead_belowdiaeresis => Key::Dead(None), + keysyms::XKB_KEY_dead_invertedbreve => Key::Dead(None), + keysyms::XKB_KEY_dead_belowcomma => Key::Dead(None), + keysyms::XKB_KEY_dead_a => Key::Dead(Some('a')), + keysyms::XKB_KEY_dead_A => Key::Dead(Some('A')), + keysyms::XKB_KEY_dead_e => Key::Dead(Some('e')), + keysyms::XKB_KEY_dead_E => Key::Dead(Some('E')), + keysyms::XKB_KEY_dead_i => Key::Dead(Some('i')), + keysyms::XKB_KEY_dead_I => Key::Dead(Some('I')), + keysyms::XKB_KEY_dead_o => Key::Dead(Some('o')), + keysyms::XKB_KEY_dead_O => Key::Dead(Some('O')), + keysyms::XKB_KEY_dead_u => Key::Dead(Some('u')), + keysyms::XKB_KEY_dead_U => Key::Dead(Some('U')), + keysyms::XKB_KEY_dead_small_schwa => Key::Dead(Some('ə')), + keysyms::XKB_KEY_dead_capital_schwa => Key::Dead(Some('Ə')), + + 0 => Key::Unidentified(NativeKeyCode::Unidentified), + _ => Key::Unidentified(NativeKeyCode::XkbSym(keysym)), + } +} + +pub fn keysym_location(keysym: u32) -> KeyLocation { + use xkbcommon_dl::keysyms; + match keysym { + keysyms::XKB_KEY_Shift_L + | keysyms::XKB_KEY_Control_L + | keysyms::XKB_KEY_Meta_L + | keysyms::XKB_KEY_Alt_L + | keysyms::XKB_KEY_Super_L + | keysyms::XKB_KEY_Hyper_L => KeyLocation::Left, + keysyms::XKB_KEY_Shift_R + | keysyms::XKB_KEY_Control_R + | keysyms::XKB_KEY_Meta_R + | keysyms::XKB_KEY_Alt_R + | keysyms::XKB_KEY_Super_R + | keysyms::XKB_KEY_Hyper_R => KeyLocation::Right, + keysyms::XKB_KEY_KP_0 + | keysyms::XKB_KEY_KP_1 + | keysyms::XKB_KEY_KP_2 + | keysyms::XKB_KEY_KP_3 + | keysyms::XKB_KEY_KP_4 + | keysyms::XKB_KEY_KP_5 + | keysyms::XKB_KEY_KP_6 + | keysyms::XKB_KEY_KP_7 + | keysyms::XKB_KEY_KP_8 + | keysyms::XKB_KEY_KP_9 + | keysyms::XKB_KEY_KP_Space + | keysyms::XKB_KEY_KP_Tab + | keysyms::XKB_KEY_KP_Enter + | keysyms::XKB_KEY_KP_F1 + | keysyms::XKB_KEY_KP_F2 + | keysyms::XKB_KEY_KP_F3 + | keysyms::XKB_KEY_KP_F4 + | keysyms::XKB_KEY_KP_Home + | keysyms::XKB_KEY_KP_Left + | keysyms::XKB_KEY_KP_Up + | keysyms::XKB_KEY_KP_Right + | keysyms::XKB_KEY_KP_Down + | keysyms::XKB_KEY_KP_Page_Up + | keysyms::XKB_KEY_KP_Page_Down + | keysyms::XKB_KEY_KP_End + | keysyms::XKB_KEY_KP_Begin + | keysyms::XKB_KEY_KP_Insert + | keysyms::XKB_KEY_KP_Delete + | keysyms::XKB_KEY_KP_Equal + | keysyms::XKB_KEY_KP_Multiply + | keysyms::XKB_KEY_KP_Add + | keysyms::XKB_KEY_KP_Separator + | keysyms::XKB_KEY_KP_Subtract + | keysyms::XKB_KEY_KP_Decimal + | keysyms::XKB_KEY_KP_Divide => KeyLocation::Numpad, + _ => KeyLocation::Standard, + } +} diff --git a/src/platform_impl/linux/common/mod.rs b/src/platform_impl/linux/common/mod.rs new file mode 100644 index 0000000000..fa23919676 --- /dev/null +++ b/src/platform_impl/linux/common/mod.rs @@ -0,0 +1,2 @@ +pub mod keymap; +pub mod xkb_state; diff --git a/src/platform_impl/linux/common/xkb_state.rs b/src/platform_impl/linux/common/xkb_state.rs new file mode 100644 index 0000000000..3660d1a599 --- /dev/null +++ b/src/platform_impl/linux/common/xkb_state.rs @@ -0,0 +1,658 @@ +use std::ffi::CString; +use std::os::raw::c_char; +use std::os::unix::ffi::OsStringExt; +use std::{char, env, ptr, slice, str}; + +#[cfg(feature = "wayland")] +use memmap2::MmapOptions; +#[cfg(feature = "wayland")] +pub use sctk::seat::keyboard::RMLVO; + +#[cfg(feature = "x11")] +use xcb_dl::ffi::xcb_connection_t; +#[cfg(feature = "x11")] +use xkbcommon_dl::XKBCOMMON_X11_HANDLE as XKBXH; + +use xkbcommon_dl::{ + self as ffi, xkb_compose_status, xkb_state_component, XKBCOMMON_COMPOSE_HANDLE as XKBCH, + XKBCOMMON_HANDLE as XKBH, +}; + +use crate::{ + event::ElementState, + keyboard::{Key, KeyCode, KeyLocation, ModifiersState}, +}; + +pub(crate) struct KbState { + #[cfg(feature = "x11")] + xcb_connection: *mut xcb_connection_t, + xkb_context: *mut ffi::xkb_context, + xkb_keymap: *mut ffi::xkb_keymap, + xkb_state: *mut ffi::xkb_state, + xkb_compose_table: *mut ffi::xkb_compose_table, + xkb_compose_state: *mut ffi::xkb_compose_state, + mod_indices: ModIndices, + mods_state: ModifiersState, + #[cfg(feature = "wayland")] + locked: bool, + scratch_buffer: Vec, +} + +#[derive(Default)] +struct ModIndices { + ctrl: u32, + alt: u32, + shift: u32, + logo: u32, +} + +impl ModIndices { + unsafe fn from_keymap(xkb_keymap: *mut ffi::xkb_keymap) -> Self { + let ctrl = (XKBH.xkb_keymap_mod_get_index)( + xkb_keymap, + ffi::XKB_MOD_NAME_CTRL.as_ptr() as *const c_char, + ); + let alt = (XKBH.xkb_keymap_mod_get_index)( + xkb_keymap, + ffi::XKB_MOD_NAME_ALT.as_ptr() as *const c_char, + ); + let shift = (XKBH.xkb_keymap_mod_get_index)( + xkb_keymap, + ffi::XKB_MOD_NAME_SHIFT.as_ptr() as *const c_char, + ); + let logo = (XKBH.xkb_keymap_mod_get_index)( + xkb_keymap, + ffi::XKB_MOD_NAME_LOGO.as_ptr() as *const c_char, + ); + Self { + ctrl, + alt, + shift, + logo, + } + } +} + +unsafe fn xkb_state_to_modifiers( + state: *mut ffi::xkb_state, + indices: &ModIndices, +) -> ModifiersState { + let ctrl = (XKBH.xkb_state_mod_index_is_active)( + state, + indices.ctrl, + xkb_state_component::XKB_STATE_MODS_EFFECTIVE, + ) > 0; + let alt = (XKBH.xkb_state_mod_index_is_active)( + state, + indices.alt, + xkb_state_component::XKB_STATE_MODS_EFFECTIVE, + ) > 0; + let shift = (XKBH.xkb_state_mod_index_is_active)( + state, + indices.shift, + xkb_state_component::XKB_STATE_MODS_EFFECTIVE, + ) > 0; + let logo = (XKBH.xkb_state_mod_index_is_active)( + state, + indices.logo, + xkb_state_component::XKB_STATE_MODS_EFFECTIVE, + ) > 0; + + let mut mods = ModifiersState::empty(); + mods.set(ModifiersState::SHIFT, shift); + mods.set(ModifiersState::CONTROL, ctrl); + mods.set(ModifiersState::ALT, alt); + mods.set(ModifiersState::SUPER, logo); + mods +} + +impl KbState { + pub(crate) fn update_state( + &mut self, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + depressed_group: u32, + latched_group: u32, + locked_group: u32, + ) { + if !self.ready() { + return; + } + let mask = unsafe { + (XKBH.xkb_state_update_mask)( + self.xkb_state, + mods_depressed, + mods_latched, + mods_locked, + depressed_group, + latched_group, + locked_group, + ) + }; + if mask.contains(xkb_state_component::XKB_STATE_MODS_EFFECTIVE) { + // effective value of mods have changed, we need to update our state + self.mods_state = unsafe { xkb_state_to_modifiers(self.xkb_state, &self.mod_indices) }; + } + } + + pub(crate) fn get_one_sym_raw(&mut self, keycode: u32) -> u32 { + if !self.ready() { + return 0; + } + unsafe { (XKBH.xkb_state_key_get_one_sym)(self.xkb_state, keycode) } + } + + pub(crate) fn get_utf8_raw(&mut self, keycode: u32) -> Option<&'static str> { + if !self.ready() { + return None; + } + let utf32 = unsafe { (XKBH.xkb_state_key_get_utf32)(self.xkb_state, keycode) }; + char_to_str(utf32) + } + + fn compose_feed(&mut self, keysym: u32) -> Option { + if !self.ready() || self.xkb_compose_state.is_null() { + return None; + } + Some(unsafe { (XKBCH.xkb_compose_state_feed)(self.xkb_compose_state, keysym) }) + } + + pub fn reset_dead_keys(&mut self) { + unsafe { + (XKBCH.xkb_compose_state_reset)(self.xkb_compose_state); + } + } + + fn compose_status(&mut self) -> Option { + if !self.ready() || self.xkb_compose_state.is_null() { + return None; + } + Some(unsafe { (XKBCH.xkb_compose_state_get_status)(self.xkb_compose_state) }) + } + + fn compose_get_utf8(&mut self) -> Option<&'static str> { + if !self.ready() || self.xkb_compose_state.is_null() { + return None; + } + self.scratch_buffer.truncate(0); + loop { + unsafe { + let size = (XKBCH.xkb_compose_state_get_utf8)( + self.xkb_compose_state, + self.scratch_buffer.as_mut_ptr() as *mut _, + self.scratch_buffer.capacity(), + ); + if size < 0 { + return None; + } + let size = size as usize; + if size >= self.scratch_buffer.capacity() { + self.scratch_buffer.reserve(size + 1); + continue; + } + self.scratch_buffer.set_len(size); + return Some(byte_slice_to_cached_string(&self.scratch_buffer)); + } + } + } + + pub(crate) fn new() -> Result { + if ffi::XKBCOMMON_OPTION.as_ref().is_none() { + return Err(Error::XKBNotFound); + } + + let context = + unsafe { (XKBH.xkb_context_new)(ffi::xkb_context_flags::XKB_CONTEXT_NO_FLAGS) }; + if context.is_null() { + return Err(Error::XKBNotFound); + } + + // let level = if log::log_enabled!(log::Level::Debug) { + // ffi::xkb_log_level::XKB_LOG_LEVEL_DEBUG + // } else if log::log_enabled!(log::Level::Info) { + // ffi::xkb_log_level::XKB_LOG_LEVEL_INFO + // } else if log::log_enabled!(log::Level::Warn) { + // ffi::xkb_log_level::XKB_LOG_LEVEL_WARNING + // } else if log::log_enabled!(log::Level::Error) { + // ffi::xkb_log_level::XKB_LOG_LEVEL_ERROR + // } else { + // ffi::xkb_log_level::XKB_LOG_LEVEL_CRITICAL + // }; + // unsafe { + // (XKBH.xkb_context_set_log_level)(context, level); + // } + + let mut me = Self { + #[cfg(feature = "x11")] + xcb_connection: ptr::null_mut(), + xkb_context: context, + xkb_keymap: ptr::null_mut(), + xkb_state: ptr::null_mut(), + xkb_compose_table: ptr::null_mut(), + xkb_compose_state: ptr::null_mut(), + mod_indices: Default::default(), + mods_state: ModifiersState::empty(), + #[cfg(feature = "wayland")] + locked: false, + scratch_buffer: Vec::with_capacity(5), + }; + + unsafe { me.init_compose() }; + + Ok(me) + } +} + +impl KbState { + #[cfg(feature = "x11")] + pub(crate) fn from_x11_xkb( + connection: *mut xcb_connection_t, + device_id: xcb_dl::ffi::xcb_input_device_id_t, + ) -> Result { + let mut me = Self::new()?; + me.xcb_connection = connection; + + unsafe { me.init_with_x11_keymap(device_id) }; + + Ok(me) + } + + #[cfg(feature = "wayland")] + pub(crate) fn from_rmlvo(rmlvo: RMLVO) -> Result { + fn to_cstring(s: Option) -> Result, Error> { + s.map_or(Ok(None), |s| CString::new(s).map(Option::Some)) + .map_err(|_| Error::BadNames) + } + + let mut state = Self::new()?; + + let rules = to_cstring(rmlvo.rules)?; + let model = to_cstring(rmlvo.model)?; + let layout = to_cstring(rmlvo.layout)?; + let variant = to_cstring(rmlvo.variant)?; + let options = to_cstring(rmlvo.options)?; + + let xkb_names = ffi::xkb_rule_names { + rules: rules.map_or(ptr::null(), |s| s.as_ptr()), + model: model.map_or(ptr::null(), |s| s.as_ptr()), + layout: layout.map_or(ptr::null(), |s| s.as_ptr()), + variant: variant.map_or(ptr::null(), |s| s.as_ptr()), + options: options.map_or(ptr::null(), |s| s.as_ptr()), + }; + + unsafe { + state.init_with_rmlvo(xkb_names)?; + } + + state.locked = true; + Ok(state) + } + + unsafe fn init_compose(&mut self) { + let locale = env::var_os("LC_ALL") + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .or_else(|| env::var_os("LC_CTYPE")) + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .or_else(|| env::var_os("LANG")) + .and_then(|v| if v.is_empty() { None } else { Some(v) }) + .unwrap_or_else(|| "C".into()); + let locale = CString::new(locale.into_vec()).unwrap(); + + let compose_table = (XKBCH.xkb_compose_table_new_from_locale)( + self.xkb_context, + locale.as_ptr(), + ffi::xkb_compose_compile_flags::XKB_COMPOSE_COMPILE_NO_FLAGS, + ); + + if compose_table.is_null() { + // init of compose table failed, continue without compose + return; + } + + let compose_state = (XKBCH.xkb_compose_state_new)( + compose_table, + ffi::xkb_compose_state_flags::XKB_COMPOSE_STATE_NO_FLAGS, + ); + + if compose_state.is_null() { + // init of compose state failed, continue without compose + (XKBCH.xkb_compose_table_unref)(compose_table); + return; + } + + self.xkb_compose_table = compose_table; + self.xkb_compose_state = compose_state; + } + + unsafe fn post_init(&mut self, state: *mut ffi::xkb_state, keymap: *mut ffi::xkb_keymap) { + self.xkb_keymap = keymap; + self.mod_indices = ModIndices::from_keymap(keymap); + self.xkb_state = state; + self.mods_state = xkb_state_to_modifiers(state, &self.mod_indices); + } + + unsafe fn de_init(&mut self) { + (XKBH.xkb_state_unref)(self.xkb_state); + self.xkb_state = ptr::null_mut(); + (XKBH.xkb_keymap_unref)(self.xkb_keymap); + self.xkb_keymap = ptr::null_mut(); + } + + #[cfg(feature = "x11")] + pub(crate) unsafe fn init_with_x11_keymap( + &mut self, + device_id: xcb_dl::ffi::xcb_input_device_id_t, + ) { + if !self.xkb_keymap.is_null() { + self.de_init(); + } + + let keymap = (XKBXH.xkb_x11_keymap_new_from_device)( + self.xkb_context, + self.xcb_connection as _, + device_id as _, + xkbcommon_dl::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, + ); + assert_ne!(keymap, ptr::null_mut()); + + let state = + (XKBXH.xkb_x11_state_new_from_device)(keymap, self.xcb_connection as _, device_id as _); + self.post_init(state, keymap); + } + + #[cfg(feature = "wayland")] + pub(crate) unsafe fn init_with_fd(&mut self, fd: std::fs::File, size: usize) { + if !self.xkb_keymap.is_null() { + self.de_init(); + } + + let map = MmapOptions::new().len(size).map(&fd).unwrap(); + + let keymap = (XKBH.xkb_keymap_new_from_string)( + self.xkb_context, + map.as_ptr() as *const _, + ffi::xkb_keymap_format::XKB_KEYMAP_FORMAT_TEXT_V1, + ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, + ); + + if keymap.is_null() { + panic!("Received invalid keymap from compositor."); + } + + let state = (XKBH.xkb_state_new)(keymap); + self.post_init(state, keymap); + } + + #[cfg(feature = "wayland")] + pub(crate) unsafe fn init_with_rmlvo( + &mut self, + names: ffi::xkb_rule_names, + ) -> Result<(), Error> { + if !self.xkb_keymap.is_null() { + self.de_init(); + } + + let keymap = (XKBH.xkb_keymap_new_from_names)( + self.xkb_context, + &names, + ffi::xkb_keymap_compile_flags::XKB_KEYMAP_COMPILE_NO_FLAGS, + ); + + if keymap.is_null() { + return Err(Error::BadNames); + } + + let state = (XKBH.xkb_state_new)(keymap); + self.post_init(state, keymap); + + Ok(()) + } +} + +impl KbState { + #[cfg(feature = "wayland")] + pub(crate) unsafe fn key_repeats(&mut self, keycode: ffi::xkb_keycode_t) -> bool { + (XKBH.xkb_keymap_key_repeats)(self.xkb_keymap, keycode) == 1 + } + + #[inline] + pub(crate) fn ready(&self) -> bool { + !self.xkb_state.is_null() + } + + #[inline] + #[cfg(feature = "wayland")] + pub(crate) fn locked(&self) -> bool { + self.locked + } + + #[inline] + pub(crate) fn mods_state(&self) -> ModifiersState { + self.mods_state + } +} + +impl Drop for KbState { + fn drop(&mut self) { + unsafe { + // TODO: Simplify this. We can currently only safely assume that the `xkb_context` + // is always valid. If we can somehow guarantee the same for `xkb_state` and + // `xkb_keymap`, then we could omit their null-checks. + if !self.xkb_compose_state.is_null() { + (XKBCH.xkb_compose_state_unref)(self.xkb_compose_state); + } + if !self.xkb_compose_table.is_null() { + (XKBCH.xkb_compose_table_unref)(self.xkb_compose_table); + } + if !self.xkb_state.is_null() { + (XKBH.xkb_state_unref)(self.xkb_state); + } + if !self.xkb_keymap.is_null() { + (XKBH.xkb_keymap_unref)(self.xkb_keymap); + } + (XKBH.xkb_context_unref)(self.xkb_context); + } + } +} + +#[derive(Debug)] +pub enum Error { + /// libxkbcommon is not available + XKBNotFound, + /// Provided RMLVO specified a keymap that would not be loaded + #[cfg(feature = "wayland")] + BadNames, +} + +impl KbState { + pub fn process_key_event( + &mut self, + keycode: u32, + group: u32, + state: ElementState, + ) -> KeyEventResults { + let keysym = self.get_one_sym_raw(keycode); + + let (text, text_with_all_modifiers); + + if state == ElementState::Pressed { + // This is a press or repeat event. Feed the keysym to the compose engine. + match self.compose_feed(keysym) { + Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_ACCEPTED) => { + // The keysym potentially affected the compose state. Check it. + match self.compose_status().unwrap() { + xkb_compose_status::XKB_COMPOSE_NOTHING => { + // There is no ongoing composing. Use the keysym on its own. + text = keysym_to_utf8_raw(keysym); + text_with_all_modifiers = self.get_utf8_raw(keycode); + } + xkb_compose_status::XKB_COMPOSE_COMPOSING => { + // Composing is ongoing and not yet completed. No text is produced. + text = None; + text_with_all_modifiers = None; + } + xkb_compose_status::XKB_COMPOSE_COMPOSED => { + // This keysym completed the sequence. The text is the result. + text = self.compose_get_utf8(); + // The current behaviour makes it so composing a character overrides attempts to input a + // control character with the `Ctrl` key. We can potentially add a configuration option + // if someone specifically wants the opposite behaviour. + text_with_all_modifiers = text; + } + xkb_compose_status::XKB_COMPOSE_CANCELLED => { + // Before this keysym, composing was ongoing. This keysym was not a possible + // continuation of the sequence and thus aborted composing. The standard + // behavior on linux in this case is to ignore both the sequence and this keysym. + text = None; + text_with_all_modifiers = None; + } + } + } + Some(ffi::xkb_compose_feed_result::XKB_COMPOSE_FEED_IGNORED) => { + // This keysym is a modifier and thus has no effect on the engine. Nor does it produce + // text. + text = None; + text_with_all_modifiers = None; + } + _ => { + // The compose engine is disabled. Use the keysym on its own. + text = keysym_to_utf8_raw(keysym); + text_with_all_modifiers = self.get_utf8_raw(keycode); + } + } + } else { + // This is a key release. No text is produced. + text = None; + text_with_all_modifiers = None; + } + + let key_without_modifiers = { + // This will become a pointer to an array which libxkbcommon owns, so we don't need to deallocate it. + let mut keysyms = ptr::null(); + let keysym_count = unsafe { + (XKBH.xkb_keymap_key_get_syms_by_level)( + self.xkb_keymap, + keycode, + group, + 0, + &mut keysyms, + ) + }; + let keysym = if keysym_count == 1 { + unsafe { *keysyms } + } else { + 0 + }; + keysym_to_key(keysym) + }; + + let res = KeyEventResults { + keycode: super::keymap::raw_keycode_to_keycode(keycode), + location: super::keymap::keysym_location(keysym), + key: keysym_to_key(keysym), + key_without_modifiers, + text, + text_with_all_modifiers, + }; + + // log::trace!("{:?}", res); + + res + } +} + +#[derive(Debug)] +pub(crate) struct KeyEventResults { + pub keycode: KeyCode, + pub location: KeyLocation, + pub key: Key<'static>, + pub key_without_modifiers: Key<'static>, + pub text: Option<&'static str>, + pub text_with_all_modifiers: Option<&'static str>, +} + +fn keysym_to_key(keysym: u32) -> Key<'static> { + let key = super::keymap::keysym_to_key(keysym); + if let Key::Unidentified(_) = key { + keysym_to_utf8_raw(keysym) + .map(Key::Character) + .unwrap_or(key) + } else { + key + } +} + +fn keysym_to_utf8_raw(keysym: u32) -> Option<&'static str> { + let utf32 = unsafe { (XKBH.xkb_keysym_to_utf32)(keysym) }; + char_to_str(utf32) +} + +fn char_to_str(utf32: u32) -> Option<&'static str> { + use std::cell::RefCell; + use std::collections::HashMap; + + if utf32 == 0 { + return None; + } + + if utf32 < 128 { + static ASCII: [u8; 128] = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, + 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, + 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, + 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, + 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, + 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, + 126, 127, + ]; + unsafe { + debug_assert_eq!(ASCII[utf32 as usize], utf32 as u8); + return Some(str::from_utf8_unchecked(slice::from_raw_parts( + &ASCII[utf32 as usize], + 1, + ))); + } + } + + thread_local! { + static STRING_CACHE: RefCell> = RefCell::new(HashMap::new()); + } + + return STRING_CACHE.with(|cache| { + let mut cache = cache.borrow_mut(); + if let Some(string) = cache.get(&utf32) { + Some(*string) + } else { + let mut buf = [0; 4]; + let char = char::from_u32(utf32).unwrap(); + let string: &'static str = + Box::leak(char.encode_utf8(&mut buf).to_string().into_boxed_str()); + cache.insert(utf32, string); + Some(string) + } + }); +} + +fn byte_slice_to_cached_string(bytes: &[u8]) -> &'static str { + use std::cell::RefCell; + use std::collections::HashSet; + + thread_local! { + static STRING_CACHE: RefCell> = RefCell::new(HashSet::new()); + } + + let string = str::from_utf8(bytes).unwrap(); + + STRING_CACHE.with(|cache| { + let mut cache = cache.borrow_mut(); + if let Some(string) = cache.get(string) { + *string + } else { + // borrowck couldn't quite figure out this one on its own + let string: &'static str = Box::leak(String::from(string).into_boxed_str()); + cache.insert(string); + string + } + }) +} diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 3bf2b04744..fb7162fb7d 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -11,24 +11,23 @@ compile_error!("Please select a feature to build for unix: `x11`, `wayland`"); #[cfg(feature = "wayland")] use std::error::Error; -use std::{collections::VecDeque, env, fmt}; #[cfg(feature = "x11")] -use std::{ffi::CStr, mem::MaybeUninit, os::raw::*, sync::Arc}; +use std::sync::Arc; +use std::{collections::VecDeque, env, fmt}; -#[cfg(feature = "x11")] -use parking_lot::Mutex; use raw_window_handle::RawWindowHandle; #[cfg(feature = "x11")] pub use self::x11::XNotSupported; #[cfg(feature = "x11")] -use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError}; +use self::x11::{util::WindowType as XWindowType, XConnection, XError}; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, event::Event, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, icon::Icon, + keyboard::Key, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, window::{CursorIcon, Fullscreen, UserAttentionType, WindowAttributes}, }; @@ -40,6 +39,15 @@ pub mod wayland; #[cfg(feature = "x11")] pub mod x11; +#[cfg(any(feature = "x11", feature = "wayland"))] +pub mod common; + +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEventExtra { + pub key_without_modifiers: Key<'static>, + pub text_with_all_modifiers: Option<&'static str>, +} + /// Environment variable specifying which backend should be used on unix platform. /// /// Legal values are x11 and wayland. If this variable is set only the named backend @@ -52,9 +60,9 @@ const BACKEND_PREFERENCE_ENV_VAR: &str = "WINIT_UNIX_BACKEND"; #[derive(Clone)] pub struct PlatformSpecificWindowBuilderAttributes { #[cfg(feature = "x11")] - pub visual_infos: Option, + pub visual_infos: crate::platform::unix::XVisualInfos, #[cfg(feature = "x11")] - pub screen_id: Option, + pub screen_id: Option, #[cfg(feature = "x11")] pub resize_increments: Option, #[cfg(feature = "x11")] @@ -75,7 +83,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes { fn default() -> Self { Self { #[cfg(feature = "x11")] - visual_infos: None, + visual_infos: Default::default(), #[cfg(feature = "x11")] screen_id: None, #[cfg(feature = "x11")] @@ -96,12 +104,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes { } } -#[cfg(feature = "x11")] -lazy_static! { - pub static ref X11_BACKEND: Mutex, XNotSupported>> = - Mutex::new(XConnection::new(Some(x_error_callback)).map(Arc::new)); -} - #[derive(Debug, Clone)] pub enum OsError { #[cfg(feature = "x11")] @@ -429,6 +431,11 @@ impl Window { x11_or_wayland!(match self; Window(w) => w.set_ime_position(position)) } + #[inline] + pub fn reset_dead_keys(&self) { + x11_or_wayland!(match self; Window(w) => w.reset_dead_keys()) + } + #[inline] pub fn request_user_attention(&self, _request_type: Option) { match self { @@ -500,46 +507,20 @@ impl Window { pub fn raw_window_handle(&self) -> RawWindowHandle { match self { #[cfg(feature = "x11")] - &Window::X(ref window) => RawWindowHandle::Xlib(window.raw_window_handle()), + &Window::X(ref window) => match &window.xconn.xlib { + Some(xlib) => RawWindowHandle::Xlib(raw_window_handle::unix::XlibHandle { + window: window.xwindow as _, + display: xlib.dpy as _, + ..raw_window_handle::unix::XlibHandle::empty() + }), + _ => RawWindowHandle::Xcb(window.raw_window_handle()), + }, #[cfg(feature = "wayland")] &Window::Wayland(ref window) => RawWindowHandle::Wayland(window.raw_window_handle()), } } } -#[cfg(feature = "x11")] -unsafe extern "C" fn x_error_callback( - display: *mut x11::ffi::Display, - event: *mut x11::ffi::XErrorEvent, -) -> c_int { - let xconn_lock = X11_BACKEND.lock(); - if let Ok(ref xconn) = *xconn_lock { - // `assume_init` is safe here because the array consists of `MaybeUninit` values, - // which do not require initialization. - let mut buf: [MaybeUninit; 1024] = MaybeUninit::uninit().assume_init(); - (xconn.xlib.XGetErrorText)( - display, - (*event).error_code as c_int, - buf.as_mut_ptr() as *mut c_char, - buf.len() as c_int, - ); - let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy(); - - let error = XError { - description: description.into_owned(), - error_code: (*event).error_code, - request_code: (*event).request_code, - minor_code: (*event).minor_code, - }; - - error!("X11 error: {:#?}", error); - - *xconn.latest_error.lock() = Some(error); - } - // Fun fact: this return value is completely ignored. - 0 -} - pub enum EventLoop { #[cfg(feature = "wayland")] Wayland(wayland::EventLoop), @@ -636,12 +617,9 @@ impl EventLoop { #[cfg(feature = "x11")] pub fn new_x11_any_thread() -> Result, XNotSupported> { - let xconn = match X11_BACKEND.lock().as_ref() { - Ok(xconn) => xconn.clone(), - Err(err) => return Err(err.clone()), - }; - - Ok(EventLoop::X(x11::EventLoop::new(xconn))) + Ok(EventLoop::X(x11::EventLoop::new(Arc::new( + XConnection::new()?, + )))) } pub fn create_proxy(&self) -> EventLoopProxy { diff --git a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs index 7c320973b8..e06e0c82cb 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/handlers.rs @@ -1,25 +1,29 @@ //! Handling of various keyboard events. -use sctk::reexports::client::protocol::wl_keyboard::KeyState; +use sctk::reexports::calloop; +use sctk::reexports::client; -use sctk::seat::keyboard::Event as KeyboardEvent; - -use crate::event::{ElementState, KeyboardInput, ModifiersState, WindowEvent}; +use crate::keyboard::{Key, KeyLocation, ModifiersState}; +use crate::platform_impl::platform::common::xkb_state::{self, RMLVO}; use crate::platform_impl::wayland::event_loop::WinitState; use crate::platform_impl::wayland::{self, DeviceId}; +use crate::platform_impl::KeyEventExtra; +use crate::{ + event::{ElementState, KeyEvent, WindowEvent}, + keyboard::KeyCode, +}; -use super::keymap; use super::KeyboardInner; #[inline] pub(super) fn handle_keyboard( - event: KeyboardEvent<'_>, + event: Event<'_>, inner: &mut KeyboardInner, winit_state: &mut WinitState, ) { let event_sink = &mut winit_state.event_sink; match event { - KeyboardEvent::Enter { surface, .. } => { + Event::Enter { surface, .. } => { let window_id = wayland::make_wid(&surface); // Window gained focus. @@ -33,7 +37,7 @@ pub(super) fn handle_keyboard( inner.target_window_id = Some(window_id); } - KeyboardEvent::Leave { surface, .. } => { + Event::Leave { surface, .. } => { let window_id = wayland::make_wid(&surface); // Notify that no modifiers are being pressed. @@ -50,11 +54,14 @@ pub(super) fn handle_keyboard( // Reset the id. inner.target_window_id = None; } - KeyboardEvent::Key { - rawkey, - keysym, + Event::Key { + physical_key, + logical_key, + text, + location, state, - utf8, + key_without_modifiers, + text_with_all_modifiers, .. } => { let window_id = match inner.target_window_id { @@ -62,46 +69,35 @@ pub(super) fn handle_keyboard( None => return, }; - let state = match state { - KeyState::Pressed => ElementState::Pressed, - KeyState::Released => ElementState::Released, - _ => unreachable!(), - }; - - let virtual_keycode = keymap::keysym_to_vkey(keysym); - event_sink.push_window_event( - #[allow(deprecated)] WindowEvent::KeyboardInput { device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( DeviceId, )), - input: KeyboardInput { + event: KeyEvent { + physical_key, + logical_key, + text, + location, state, - scancode: rawkey, - virtual_keycode, - modifiers: *inner.modifiers_state.borrow(), + repeat: false, + platform_specific: KeyEventExtra { + key_without_modifiers, + text_with_all_modifiers, + }, }, is_synthetic: false, }, window_id, ); - - // Send ReceivedCharacter event only on ElementState::Pressed. - if ElementState::Released == state { - return; - } - - if let Some(txt) = utf8 { - for ch in txt.chars() { - event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); - } - } } - KeyboardEvent::Repeat { - rawkey, - keysym, - utf8, + Event::Repeat { + physical_key, + logical_key, + text, + location, + key_without_modifiers, + text_with_all_modifiers, .. } => { let window_id = match inner.target_window_id { @@ -109,32 +105,29 @@ pub(super) fn handle_keyboard( None => return, }; - let virtual_keycode = keymap::keysym_to_vkey(keysym); - event_sink.push_window_event( - #[allow(deprecated)] WindowEvent::KeyboardInput { device_id: crate::event::DeviceId(crate::platform_impl::DeviceId::Wayland( DeviceId, )), - input: KeyboardInput { + event: KeyEvent { + physical_key, + logical_key, + text, + location, state: ElementState::Pressed, - scancode: rawkey, - virtual_keycode, - modifiers: *inner.modifiers_state.borrow(), + repeat: true, + platform_specific: KeyEventExtra { + key_without_modifiers, + text_with_all_modifiers, + }, }, is_synthetic: false, }, window_id, ); - - if let Some(txt) = utf8 { - for ch in txt.chars() { - event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); - } - } } - KeyboardEvent::Modifiers { modifiers } => { + Event::Modifiers { modifiers } => { let modifiers = ModifiersState::from(modifiers); if let Some(window_id) = inner.target_window_id { *inner.modifiers_state.borrow_mut() = modifiers; @@ -149,3 +142,586 @@ pub(super) fn handle_keyboard( } } } + +// ! ====================================================================================================== ! +// ! "INSPIRED" BY SCTK ! +// ! ====================================================================================================== ! + +use std::num::NonZeroU32; +use std::time::Duration; +use std::{ + cell::RefCell, + convert::TryInto, + fs::File, + os::unix::io::{FromRawFd, RawFd}, + rc::Rc, +}; + +use sctk::reexports::client::{ + protocol::{wl_keyboard, wl_seat, wl_surface}, + Attached, +}; + +use super::super::super::super::common::xkb_state::KbState; + +const MICROS_IN_SECOND: u32 = 1000000; + +/// Possible kinds of key repetition +pub enum RepeatKind { + /// keys will be repeated at a set rate and delay + #[allow(dead_code)] + Fixed { + /// The number of repetitions per second that should occur. + rate: u32, + /// delay (in milliseconds) between a key press and the start of repetition + delay: u32, + }, + /// keys will be repeated at a rate and delay set by the wayland server + System, +} + +#[derive(Debug)] +/// An error that occurred while trying to initialize a mapped keyboard +pub enum Error { + XkbState(xkb_state::Error), + /// The provided seat does not have the keyboard capability + NoKeyboard, + /// Failed to init timers for repetition + TimerError(std::io::Error), +} + +impl From for Error { + fn from(err: xkb_state::Error) -> Self { + Self::XkbState(err) + } +} + +/// Events received from a mapped keyboard +pub enum Event<'a> { + /// The keyboard focus has entered a surface + Enter { + /// serial number of the event + serial: u32, + /// surface that was entered + surface: wl_surface::WlSurface, + /// raw values of the currently pressed keys + rawkeys: &'a [u32], + /// interpreted symbols of the currently pressed keys + keysyms: &'a [u32], + }, + /// The keyboard focus has left a surface + Leave { + /// serial number of the event + serial: u32, + /// surface that was left + surface: wl_surface::WlSurface, + }, + /// The key modifiers have changed state + Modifiers { + /// current state of the modifiers + modifiers: ModifiersState, + }, + /// A key event occurred + Key { + /// serial number of the event + serial: u32, + /// time at which the keypress occurred + time: u32, + physical_key: KeyCode, + logical_key: Key<'static>, + text: Option<&'static str>, + location: KeyLocation, + state: ElementState, + key_without_modifiers: Key<'static>, + text_with_all_modifiers: Option<&'static str>, + }, + /// A key repetition event + Repeat { + /// time at which the repetition occured + time: u32, + physical_key: KeyCode, + logical_key: Key<'static>, + text: Option<&'static str>, + location: KeyLocation, + key_without_modifiers: Key<'static>, + text_with_all_modifiers: Option<&'static str>, + }, +} + +/// Implement a keyboard for keymap translation with key repetition +/// +/// This requires you to provide a callback to receive the events after they +/// have been interpreted with the keymap. +/// +/// The keymap will be loaded from the provided RMLVO rules, or from the compositor +/// provided keymap if `None`. +/// +/// Returns an error if xkbcommon could not be initialized, the RMLVO specification +/// contained invalid values, or if the provided seat does not have keyboard capability. +pub fn map_keyboard_repeat( + loop_handle: calloop::LoopHandle, + seat: &Attached, + rmlvo: Option, + repeatkind: RepeatKind, + callback: F, +) -> Result<(wl_keyboard::WlKeyboard, calloop::Source), Error> +where + F: FnMut(Event<'_>, wl_keyboard::WlKeyboard, wayland_client::DispatchData<'_>) + 'static, +{ + let has_kbd = sctk::seat::with_seat_data(seat, |data| data.has_keyboard).unwrap_or(false); + let keyboard = if has_kbd { + seat.get_keyboard() + } else { + return Err(Error::NoKeyboard); + }; + + let state = Rc::new(RefCell::new( + rmlvo + .map(KbState::from_rmlvo) + .unwrap_or_else(KbState::new)?, + )); + + let callback = Rc::new(RefCell::new(callback)); + + let repeat = match repeatkind { + RepeatKind::System => RepeatDetails { + locked: false, + gap: None, + delay: 200, + }, + RepeatKind::Fixed { rate, delay } => { + let gap = rate_to_gap(rate as i32); + RepeatDetails { + locked: true, + gap, + delay, + } + } + }; + + // Prepare the repetition handling. + let (mut kbd_handler, source) = { + let current_repeat = Rc::new(RefCell::new(None)); + + let source = RepeatSource { + timer: calloop::timer::Timer::new().map_err(Error::TimerError)?, + state: state.clone(), + current_repeat: current_repeat.clone(), + }; + + let timer_handle = source.timer.handle(); + + let handler = KbdHandler { + callback: callback.clone(), + state, + repeat: Some(KbdRepeat { + timer_handle, + current_repeat, + details: repeat, + }), + group: 0, + }; + (handler, source) + }; + + let source = loop_handle + .insert_source(source, move |event, kbd, ddata| { + (&mut *callback.borrow_mut())( + event, + kbd.clone(), + wayland_client::DispatchData::wrap(ddata), + ) + }) + .map_err(|e| Error::TimerError(e.error))?; + + keyboard.quick_assign(move |keyboard, event, data| { + kbd_handler.event(keyboard.detach(), event, data) + }); + + Ok((keyboard.detach(), source)) +} + +fn rate_to_gap(rate: i32) -> Option { + if rate <= 0 { + None + } else if MICROS_IN_SECOND < rate as u32 { + NonZeroU32::new(1) + } else { + NonZeroU32::new(MICROS_IN_SECOND / rate as u32) + } +} + +/* + * Classic handling + */ + +type KbdCallback = dyn FnMut(Event<'_>, wl_keyboard::WlKeyboard, wayland_client::DispatchData<'_>); + +struct RepeatDetails { + locked: bool, + /// Gap between key presses in microseconds. + /// + /// If the `gap` is `None`, it means that repeat is disabled. + gap: Option, + /// Delay before starting key repeat in milliseconds. + delay: u32, +} + +struct KbdHandler { + state: Rc>, + group: u32, + callback: Rc>, + repeat: Option, +} + +struct KbdRepeat { + timer_handle: calloop::timer::TimerHandle<()>, + current_repeat: Rc>>, + details: RepeatDetails, +} + +impl KbdRepeat { + fn start_repeat(&self, key: u32, group: u32, keyboard: wl_keyboard::WlKeyboard, time: u32) { + // Start a new repetition, overwriting the previous ones + self.timer_handle.cancel_all_timeouts(); + + // Handle disabled repeat rate. + let gap = match self.details.gap { + Some(gap) => gap.get() as u64, + None => return, + }; + + *self.current_repeat.borrow_mut() = Some(RepeatData { + keyboard, + group, + keycode: key, + gap, + time: (time + self.details.delay) as u64 * 1000, + }); + self.timer_handle + .add_timeout(Duration::from_micros(self.details.delay as u64 * 1000), ()); + } + + fn stop_repeat(&self, key: u32) { + // only cancel if the released key is the currently repeating key + let mut guard = self.current_repeat.borrow_mut(); + let stop = (*guard).as_ref().map(|d| d.keycode == key).unwrap_or(false); + if stop { + self.timer_handle.cancel_all_timeouts(); + *guard = None; + } + } + + fn stop_all_repeat(&self) { + self.timer_handle.cancel_all_timeouts(); + *self.current_repeat.borrow_mut() = None; + } +} + +impl KbdHandler { + fn event( + &mut self, + kbd: wl_keyboard::WlKeyboard, + event: wl_keyboard::Event, + dispatch_data: client::DispatchData<'_>, + ) { + use wl_keyboard::Event; + + match event { + Event::Keymap { format, fd, size } => self.keymap(kbd, format, fd, size), + Event::Enter { + serial, + surface, + keys, + } => self.enter(kbd, serial, surface, keys, dispatch_data), + Event::Leave { serial, surface } => self.leave(kbd, serial, surface, dispatch_data), + Event::Key { + serial, + time, + key, + state, + } => self.key(kbd, serial, time, key, state, dispatch_data), + Event::Modifiers { + mods_depressed, + mods_latched, + mods_locked, + group, + .. + } => self.modifiers( + kbd, + mods_depressed, + mods_latched, + mods_locked, + group, + dispatch_data, + ), + Event::RepeatInfo { rate, delay } => self.repeat_info(kbd, rate, delay), + _ => {} + } + } + + fn keymap( + &mut self, + _: wl_keyboard::WlKeyboard, + format: wl_keyboard::KeymapFormat, + fd: RawFd, + size: u32, + ) { + let fd = unsafe { File::from_raw_fd(fd) }; + let mut state = self.state.borrow_mut(); + if state.locked() { + // state is locked, ignore keymap updates + return; + } + match format { + wl_keyboard::KeymapFormat::XkbV1 => unsafe { + state.init_with_fd(fd, size as usize); + }, + wl_keyboard::KeymapFormat::NoKeymap => { + warn!("The Wayland server did not send a keymap!"); + } + _ => unreachable!(), + } + } + + fn enter( + &mut self, + object: wl_keyboard::WlKeyboard, + serial: u32, + surface: wl_surface::WlSurface, + keys: Vec, + dispatch_data: client::DispatchData<'_>, + ) { + let mut state = self.state.borrow_mut(); + let rawkeys = keys + .chunks_exact(4) + .map(|c| u32::from_ne_bytes(c.try_into().unwrap())) + .collect::>(); + let keys: Vec = rawkeys.iter().map(|k| state.get_one_sym_raw(*k)).collect(); + (&mut *self.callback.borrow_mut())( + Event::Enter { + serial, + surface, + rawkeys: &rawkeys, + keysyms: &keys, + }, + object, + dispatch_data, + ); + } + + fn leave( + &mut self, + object: wl_keyboard::WlKeyboard, + serial: u32, + surface: wl_surface::WlSurface, + dispatch_data: client::DispatchData<'_>, + ) { + { + if let Some(ref mut repeat) = self.repeat { + repeat.stop_all_repeat(); + } + } + (&mut *self.callback.borrow_mut())(Event::Leave { serial, surface }, object, dispatch_data); + } + + fn key( + &mut self, + object: wl_keyboard::WlKeyboard, + serial: u32, + time: u32, + key: u32, + key_state: wl_keyboard::KeyState, + dispatch_data: client::DispatchData<'_>, + ) { + let ( + physical_key, + logical_key, + text, + location, + state, + key_without_modifiers, + text_with_all_modifiers, + repeats, + ) = { + let mut state = self.state.borrow_mut(); + let key_state = match key_state { + wl_keyboard::KeyState::Pressed => ElementState::Pressed, + wl_keyboard::KeyState::Released => ElementState::Released, + _ => unreachable!(), + }; + + let ker = state.process_key_event(key + 8, self.group, key_state); + let repeats = unsafe { state.key_repeats(key) }; + + ( + ker.keycode, + ker.key, + ker.text, + ker.location, + key_state, + ker.key_without_modifiers, + ker.text_with_all_modifiers, + repeats, + ) + }; + + { + if let Some(ref mut repeat_handle) = self.repeat { + if repeats { + if state == ElementState::Pressed { + repeat_handle.start_repeat(key, self.group, object.clone(), time); + } else { + repeat_handle.stop_repeat(key); + } + } + } + } + + (&mut *self.callback.borrow_mut())( + Event::Key { + serial, + time, + physical_key, + logical_key, + text, + location, + state, + key_without_modifiers, + text_with_all_modifiers, + }, + object, + dispatch_data, + ); + } + + fn modifiers( + &mut self, + object: wl_keyboard::WlKeyboard, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + group: u32, + dispatch_data: client::DispatchData<'_>, + ) { + { + self.group = group; + let mut state = self.state.borrow_mut(); + state.update_state(mods_depressed, mods_latched, mods_locked, 0, 0, group); + (&mut *self.callback.borrow_mut())( + Event::Modifiers { + modifiers: state.mods_state(), + }, + object, + dispatch_data, + ); + } + } + + fn repeat_info(&mut self, _: wl_keyboard::WlKeyboard, rate: i32, delay: i32) { + { + if let Some(ref mut repeat_handle) = self.repeat { + if !repeat_handle.details.locked { + repeat_handle.details.gap = rate_to_gap(rate); + repeat_handle.details.delay = delay as u32; + } + } + } + } +} + +/* + * Repeat handling + */ + +struct RepeatData { + keyboard: wl_keyboard::WlKeyboard, + group: u32, + keycode: u32, + /// Gap between key presses in microseconds. + gap: u64, + /// Time of the last event in microseconds. + time: u64, +} + +/// An event source managing the key repetition of a keyboard +/// +/// It is given to you from [`map_keyboard`](fn.map_keyboard.html), and you need to +/// insert it in your calloop event loop if you want to have functionning key repetition. +/// +/// If don't want key repetition you can just drop it. +/// +/// This source will not directly generate calloop events, and the callback provided to +/// `EventLoopHandle::insert_source()` will be ignored. Instead it triggers the +/// callback you provided to [`map_keyboard`](fn.map_keyboard.html). +pub struct RepeatSource { + timer: calloop::timer::Timer<()>, + state: Rc>, + current_repeat: Rc>>, +} + +impl calloop::EventSource for RepeatSource { + type Event = Event<'static>; + type Metadata = wl_keyboard::WlKeyboard; + type Ret = (); + + fn process_events( + &mut self, + readiness: calloop::Readiness, + token: calloop::Token, + mut callback: F, + ) -> std::io::Result<()> + where + F: FnMut(Event<'static>, &mut wl_keyboard::WlKeyboard), + { + let current_repeat = &self.current_repeat; + let state = &self.state; + self.timer + .process_events(readiness, token, |(), timer_handle| { + if let Some(ref mut data) = *current_repeat.borrow_mut() { + // there is something to repeat + let mut state = state.borrow_mut(); + let ker = state.process_key_event( + data.keycode + 8, + data.group, + ElementState::Pressed, + ); + + let new_time = data.gap + data.time; + // Notify the callback. + callback( + Event::Repeat { + time: (new_time / 1000) as u32, + physical_key: ker.keycode, + logical_key: ker.key, + text: ker.text, + location: ker.location, + key_without_modifiers: ker.key_without_modifiers, + text_with_all_modifiers: ker.text_with_all_modifiers, + }, + &mut data.keyboard, + ); + // Update the time of last event. + data.time = new_time; + // Schedule the next timeout. + timer_handle.add_timeout(Duration::from_micros(data.gap), ()); + } + }) + } + + fn register(&mut self, poll: &mut calloop::Poll, token: calloop::Token) -> std::io::Result<()> { + self.timer.register(poll, token) + } + + fn reregister( + &mut self, + poll: &mut calloop::Poll, + token: calloop::Token, + ) -> std::io::Result<()> { + self.timer.reregister(poll, token) + } + + fn unregister(&mut self, poll: &mut calloop::Poll) -> std::io::Result<()> { + self.timer.unregister(poll) + } +} diff --git a/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs b/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs deleted file mode 100644 index 991afff2c9..0000000000 --- a/src/platform_impl/linux/wayland/seat/keyboard/keymap.rs +++ /dev/null @@ -1,192 +0,0 @@ -//! Convert Wayland keys to winit keys. - -use crate::event::VirtualKeyCode; - -pub fn keysym_to_vkey(keysym: u32) -> Option { - use sctk::seat::keyboard::keysyms; - match keysym { - // Numbers. - keysyms::XKB_KEY_1 => Some(VirtualKeyCode::Key1), - keysyms::XKB_KEY_2 => Some(VirtualKeyCode::Key2), - keysyms::XKB_KEY_3 => Some(VirtualKeyCode::Key3), - keysyms::XKB_KEY_4 => Some(VirtualKeyCode::Key4), - keysyms::XKB_KEY_5 => Some(VirtualKeyCode::Key5), - keysyms::XKB_KEY_6 => Some(VirtualKeyCode::Key6), - keysyms::XKB_KEY_7 => Some(VirtualKeyCode::Key7), - keysyms::XKB_KEY_8 => Some(VirtualKeyCode::Key8), - keysyms::XKB_KEY_9 => Some(VirtualKeyCode::Key9), - keysyms::XKB_KEY_0 => Some(VirtualKeyCode::Key0), - // Letters. - keysyms::XKB_KEY_A | keysyms::XKB_KEY_a => Some(VirtualKeyCode::A), - keysyms::XKB_KEY_B | keysyms::XKB_KEY_b => Some(VirtualKeyCode::B), - keysyms::XKB_KEY_C | keysyms::XKB_KEY_c => Some(VirtualKeyCode::C), - keysyms::XKB_KEY_D | keysyms::XKB_KEY_d => Some(VirtualKeyCode::D), - keysyms::XKB_KEY_E | keysyms::XKB_KEY_e => Some(VirtualKeyCode::E), - keysyms::XKB_KEY_F | keysyms::XKB_KEY_f => Some(VirtualKeyCode::F), - keysyms::XKB_KEY_G | keysyms::XKB_KEY_g => Some(VirtualKeyCode::G), - keysyms::XKB_KEY_H | keysyms::XKB_KEY_h => Some(VirtualKeyCode::H), - keysyms::XKB_KEY_I | keysyms::XKB_KEY_i => Some(VirtualKeyCode::I), - keysyms::XKB_KEY_J | keysyms::XKB_KEY_j => Some(VirtualKeyCode::J), - keysyms::XKB_KEY_K | keysyms::XKB_KEY_k => Some(VirtualKeyCode::K), - keysyms::XKB_KEY_L | keysyms::XKB_KEY_l => Some(VirtualKeyCode::L), - keysyms::XKB_KEY_M | keysyms::XKB_KEY_m => Some(VirtualKeyCode::M), - keysyms::XKB_KEY_N | keysyms::XKB_KEY_n => Some(VirtualKeyCode::N), - keysyms::XKB_KEY_O | keysyms::XKB_KEY_o => Some(VirtualKeyCode::O), - keysyms::XKB_KEY_P | keysyms::XKB_KEY_p => Some(VirtualKeyCode::P), - keysyms::XKB_KEY_Q | keysyms::XKB_KEY_q => Some(VirtualKeyCode::Q), - keysyms::XKB_KEY_R | keysyms::XKB_KEY_r => Some(VirtualKeyCode::R), - keysyms::XKB_KEY_S | keysyms::XKB_KEY_s => Some(VirtualKeyCode::S), - keysyms::XKB_KEY_T | keysyms::XKB_KEY_t => Some(VirtualKeyCode::T), - keysyms::XKB_KEY_U | keysyms::XKB_KEY_u => Some(VirtualKeyCode::U), - keysyms::XKB_KEY_V | keysyms::XKB_KEY_v => Some(VirtualKeyCode::V), - keysyms::XKB_KEY_W | keysyms::XKB_KEY_w => Some(VirtualKeyCode::W), - keysyms::XKB_KEY_X | keysyms::XKB_KEY_x => Some(VirtualKeyCode::X), - keysyms::XKB_KEY_Y | keysyms::XKB_KEY_y => Some(VirtualKeyCode::Y), - keysyms::XKB_KEY_Z | keysyms::XKB_KEY_z => Some(VirtualKeyCode::Z), - // Escape. - keysyms::XKB_KEY_Escape => Some(VirtualKeyCode::Escape), - // Function keys. - keysyms::XKB_KEY_F1 => Some(VirtualKeyCode::F1), - keysyms::XKB_KEY_F2 => Some(VirtualKeyCode::F2), - keysyms::XKB_KEY_F3 => Some(VirtualKeyCode::F3), - keysyms::XKB_KEY_F4 => Some(VirtualKeyCode::F4), - keysyms::XKB_KEY_F5 => Some(VirtualKeyCode::F5), - keysyms::XKB_KEY_F6 => Some(VirtualKeyCode::F6), - keysyms::XKB_KEY_F7 => Some(VirtualKeyCode::F7), - keysyms::XKB_KEY_F8 => Some(VirtualKeyCode::F8), - keysyms::XKB_KEY_F9 => Some(VirtualKeyCode::F9), - keysyms::XKB_KEY_F10 => Some(VirtualKeyCode::F10), - keysyms::XKB_KEY_F11 => Some(VirtualKeyCode::F11), - keysyms::XKB_KEY_F12 => Some(VirtualKeyCode::F12), - keysyms::XKB_KEY_F13 => Some(VirtualKeyCode::F13), - keysyms::XKB_KEY_F14 => Some(VirtualKeyCode::F14), - keysyms::XKB_KEY_F15 => Some(VirtualKeyCode::F15), - keysyms::XKB_KEY_F16 => Some(VirtualKeyCode::F16), - keysyms::XKB_KEY_F17 => Some(VirtualKeyCode::F17), - keysyms::XKB_KEY_F18 => Some(VirtualKeyCode::F18), - keysyms::XKB_KEY_F19 => Some(VirtualKeyCode::F19), - keysyms::XKB_KEY_F20 => Some(VirtualKeyCode::F20), - keysyms::XKB_KEY_F21 => Some(VirtualKeyCode::F21), - keysyms::XKB_KEY_F22 => Some(VirtualKeyCode::F22), - keysyms::XKB_KEY_F23 => Some(VirtualKeyCode::F23), - keysyms::XKB_KEY_F24 => Some(VirtualKeyCode::F24), - // Flow control. - keysyms::XKB_KEY_Print => Some(VirtualKeyCode::Snapshot), - keysyms::XKB_KEY_Scroll_Lock => Some(VirtualKeyCode::Scroll), - keysyms::XKB_KEY_Pause => Some(VirtualKeyCode::Pause), - keysyms::XKB_KEY_Insert => Some(VirtualKeyCode::Insert), - keysyms::XKB_KEY_Home => Some(VirtualKeyCode::Home), - keysyms::XKB_KEY_Delete => Some(VirtualKeyCode::Delete), - keysyms::XKB_KEY_End => Some(VirtualKeyCode::End), - keysyms::XKB_KEY_Page_Down => Some(VirtualKeyCode::PageDown), - keysyms::XKB_KEY_Page_Up => Some(VirtualKeyCode::PageUp), - // Arrows. - keysyms::XKB_KEY_Left => Some(VirtualKeyCode::Left), - keysyms::XKB_KEY_Up => Some(VirtualKeyCode::Up), - keysyms::XKB_KEY_Right => Some(VirtualKeyCode::Right), - keysyms::XKB_KEY_Down => Some(VirtualKeyCode::Down), - - keysyms::XKB_KEY_BackSpace => Some(VirtualKeyCode::Back), - keysyms::XKB_KEY_Return => Some(VirtualKeyCode::Return), - keysyms::XKB_KEY_space => Some(VirtualKeyCode::Space), - - keysyms::XKB_KEY_Multi_key => Some(VirtualKeyCode::Compose), - keysyms::XKB_KEY_caret => Some(VirtualKeyCode::Caret), - - // Keypad. - keysyms::XKB_KEY_Num_Lock => Some(VirtualKeyCode::Numlock), - keysyms::XKB_KEY_KP_0 => Some(VirtualKeyCode::Numpad0), - keysyms::XKB_KEY_KP_1 => Some(VirtualKeyCode::Numpad1), - keysyms::XKB_KEY_KP_2 => Some(VirtualKeyCode::Numpad2), - keysyms::XKB_KEY_KP_3 => Some(VirtualKeyCode::Numpad3), - keysyms::XKB_KEY_KP_4 => Some(VirtualKeyCode::Numpad4), - keysyms::XKB_KEY_KP_5 => Some(VirtualKeyCode::Numpad5), - keysyms::XKB_KEY_KP_6 => Some(VirtualKeyCode::Numpad6), - keysyms::XKB_KEY_KP_7 => Some(VirtualKeyCode::Numpad7), - keysyms::XKB_KEY_KP_8 => Some(VirtualKeyCode::Numpad8), - keysyms::XKB_KEY_KP_9 => Some(VirtualKeyCode::Numpad9), - // Misc. - // => Some(VirtualKeyCode::AbntC1), - // => Some(VirtualKeyCode::AbntC2), - keysyms::XKB_KEY_plus => Some(VirtualKeyCode::Plus), - keysyms::XKB_KEY_apostrophe => Some(VirtualKeyCode::Apostrophe), - // => Some(VirtualKeyCode::Apps), - keysyms::XKB_KEY_at => Some(VirtualKeyCode::At), - // => Some(VirtualKeyCode::Ax), - keysyms::XKB_KEY_backslash => Some(VirtualKeyCode::Backslash), - keysyms::XKB_KEY_XF86Calculator => Some(VirtualKeyCode::Calculator), - // => Some(VirtualKeyCode::Capital), - keysyms::XKB_KEY_colon => Some(VirtualKeyCode::Colon), - keysyms::XKB_KEY_comma => Some(VirtualKeyCode::Comma), - // => Some(VirtualKeyCode::Convert), - keysyms::XKB_KEY_equal => Some(VirtualKeyCode::Equals), - keysyms::XKB_KEY_grave => Some(VirtualKeyCode::Grave), - // => Some(VirtualKeyCode::Kana), - keysyms::XKB_KEY_Kanji => Some(VirtualKeyCode::Kanji), - keysyms::XKB_KEY_Alt_L => Some(VirtualKeyCode::LAlt), - keysyms::XKB_KEY_bracketleft => Some(VirtualKeyCode::LBracket), - keysyms::XKB_KEY_Control_L => Some(VirtualKeyCode::LControl), - keysyms::XKB_KEY_Shift_L => Some(VirtualKeyCode::LShift), - keysyms::XKB_KEY_Super_L => Some(VirtualKeyCode::LWin), - keysyms::XKB_KEY_XF86Mail => Some(VirtualKeyCode::Mail), - // => Some(VirtualKeyCode::MediaSelect), - // => Some(VirtualKeyCode::MediaStop), - keysyms::XKB_KEY_minus => Some(VirtualKeyCode::Minus), - keysyms::XKB_KEY_asterisk => Some(VirtualKeyCode::Asterisk), - keysyms::XKB_KEY_XF86AudioMute => Some(VirtualKeyCode::Mute), - // => Some(VirtualKeyCode::MyComputer), - keysyms::XKB_KEY_XF86AudioNext => Some(VirtualKeyCode::NextTrack), - // => Some(VirtualKeyCode::NoConvert), - keysyms::XKB_KEY_KP_Separator => Some(VirtualKeyCode::NumpadComma), - keysyms::XKB_KEY_KP_Enter => Some(VirtualKeyCode::NumpadEnter), - keysyms::XKB_KEY_KP_Equal => Some(VirtualKeyCode::NumpadEquals), - keysyms::XKB_KEY_KP_Add => Some(VirtualKeyCode::NumpadAdd), - keysyms::XKB_KEY_KP_Subtract => Some(VirtualKeyCode::NumpadSubtract), - keysyms::XKB_KEY_KP_Multiply => Some(VirtualKeyCode::NumpadMultiply), - keysyms::XKB_KEY_KP_Divide => Some(VirtualKeyCode::NumpadDivide), - keysyms::XKB_KEY_KP_Decimal => Some(VirtualKeyCode::NumpadDecimal), - keysyms::XKB_KEY_KP_Page_Up => Some(VirtualKeyCode::PageUp), - keysyms::XKB_KEY_KP_Page_Down => Some(VirtualKeyCode::PageDown), - keysyms::XKB_KEY_KP_Home => Some(VirtualKeyCode::Home), - keysyms::XKB_KEY_KP_End => Some(VirtualKeyCode::End), - keysyms::XKB_KEY_KP_Left => Some(VirtualKeyCode::Left), - keysyms::XKB_KEY_KP_Up => Some(VirtualKeyCode::Up), - keysyms::XKB_KEY_KP_Right => Some(VirtualKeyCode::Right), - keysyms::XKB_KEY_KP_Down => Some(VirtualKeyCode::Down), - // => Some(VirtualKeyCode::OEM102), - keysyms::XKB_KEY_period => Some(VirtualKeyCode::Period), - // => Some(VirtualKeyCode::Playpause), - keysyms::XKB_KEY_XF86PowerOff => Some(VirtualKeyCode::Power), - keysyms::XKB_KEY_XF86AudioPrev => Some(VirtualKeyCode::PrevTrack), - keysyms::XKB_KEY_Alt_R => Some(VirtualKeyCode::RAlt), - keysyms::XKB_KEY_bracketright => Some(VirtualKeyCode::RBracket), - keysyms::XKB_KEY_Control_R => Some(VirtualKeyCode::RControl), - keysyms::XKB_KEY_Shift_R => Some(VirtualKeyCode::RShift), - keysyms::XKB_KEY_Super_R => Some(VirtualKeyCode::RWin), - keysyms::XKB_KEY_semicolon => Some(VirtualKeyCode::Semicolon), - keysyms::XKB_KEY_slash => Some(VirtualKeyCode::Slash), - keysyms::XKB_KEY_XF86Sleep => Some(VirtualKeyCode::Sleep), - // => Some(VirtualKeyCode::Stop), - // => Some(VirtualKeyCode::Sysrq), - keysyms::XKB_KEY_Tab => Some(VirtualKeyCode::Tab), - keysyms::XKB_KEY_ISO_Left_Tab => Some(VirtualKeyCode::Tab), - keysyms::XKB_KEY_underscore => Some(VirtualKeyCode::Underline), - // => Some(VirtualKeyCode::Unlabeled), - keysyms::XKB_KEY_XF86AudioLowerVolume => Some(VirtualKeyCode::VolumeDown), - keysyms::XKB_KEY_XF86AudioRaiseVolume => Some(VirtualKeyCode::VolumeUp), - // => Some(VirtualKeyCode::Wake), - // => Some(VirtualKeyCode::Webback), - // => Some(VirtualKeyCode::WebFavorites), - // => Some(VirtualKeyCode::WebForward), - // => Some(VirtualKeyCode::WebHome), - // => Some(VirtualKeyCode::WebRefresh), - // => Some(VirtualKeyCode::WebSearch), - // => Some(VirtualKeyCode::WebStop), - keysyms::XKB_KEY_yen => Some(VirtualKeyCode::Yen), - keysyms::XKB_KEY_XF86Copy => Some(VirtualKeyCode::Copy), - keysyms::XKB_KEY_XF86Paste => Some(VirtualKeyCode::Paste), - keysyms::XKB_KEY_XF86Cut => Some(VirtualKeyCode::Cut), - // Fallback. - _ => None, - } -} diff --git a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs index 1362dcf797..d2fd21daf7 100644 --- a/src/platform_impl/linux/wayland/seat/keyboard/mod.rs +++ b/src/platform_impl/linux/wayland/seat/keyboard/mod.rs @@ -3,26 +3,22 @@ use std::cell::RefCell; use std::rc::Rc; +use sctk::reexports::calloop::{LoopHandle, Source}; use sctk::reexports::client::protocol::wl_keyboard::WlKeyboard; use sctk::reexports::client::protocol::wl_seat::WlSeat; use sctk::reexports::client::Attached; -use sctk::reexports::calloop::{LoopHandle, Source}; - -use sctk::seat::keyboard::{self, RepeatSource}; - -use crate::event::ModifiersState; +use crate::keyboard::ModifiersState; use crate::platform_impl::wayland::event_loop::WinitState; use crate::platform_impl::wayland::WindowId; mod handlers; -mod keymap; pub(crate) struct Keyboard { pub keyboard: WlKeyboard, /// The source for repeat keys. - pub repeat_source: Option>, + pub repeat_source: Option>, /// LoopHandle to drop `RepeatSource`, when dropping the keyboard. pub loop_handle: LoopHandle, @@ -35,11 +31,11 @@ impl Keyboard { modifiers_state: Rc>, ) -> Option { let mut inner = KeyboardInner::new(modifiers_state); - let keyboard_data = keyboard::map_keyboard_repeat( + let keyboard_data = handlers::map_keyboard_repeat( loop_handle.clone(), &seat, None, - keyboard::RepeatKind::System, + handlers::RepeatKind::System, move |event, _, mut dispatch_data| { let winit_state = dispatch_data.get::().unwrap(); handlers::handle_keyboard(event, &mut inner, winit_state); @@ -92,14 +88,3 @@ impl KeyboardInner { } } } - -impl From for ModifiersState { - fn from(mods: keyboard::ModifiersState) -> ModifiersState { - let mut wl_mods = ModifiersState::empty(); - wl_mods.set(ModifiersState::SHIFT, mods.shift); - wl_mods.set(ModifiersState::CTRL, mods.ctrl); - wl_mods.set(ModifiersState::ALT, mods.alt); - wl_mods.set(ModifiersState::LOGO, mods.logo); - wl_mods - } -} diff --git a/src/platform_impl/linux/wayland/seat/mod.rs b/src/platform_impl/linux/wayland/seat/mod.rs index 23098d08c8..f4271b2b10 100644 --- a/src/platform_impl/linux/wayland/seat/mod.rs +++ b/src/platform_impl/linux/wayland/seat/mod.rs @@ -17,7 +17,7 @@ use sctk::seat::{SeatData, SeatListener}; use super::env::WinitEnv; use super::event_loop::WinitState; -use crate::event::ModifiersState; +use crate::keyboard::ModifiersState; mod keyboard; pub mod pointer; diff --git a/src/platform_impl/linux/wayland/seat/pointer/data.rs b/src/platform_impl/linux/wayland/seat/pointer/data.rs index 1da60d3526..a502a86e4c 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/data.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/data.rs @@ -8,7 +8,8 @@ use sctk::reexports::client::Attached; use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_pointer_constraints_v1::{ZwpPointerConstraintsV1}; use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_confined_pointer_v1::ZwpConfinedPointerV1; -use crate::event::{ModifiersState, TouchPhase}; +use crate::event::TouchPhase; +use crate::keyboard::ModifiersState; /// A data being used by pointer handlers. pub(super) struct PointerData { diff --git a/src/platform_impl/linux/wayland/seat/pointer/mod.rs b/src/platform_impl/linux/wayland/seat/pointer/mod.rs index 7ae1f251b3..be2d9a2d08 100644 --- a/src/platform_impl/linux/wayland/seat/pointer/mod.rs +++ b/src/platform_impl/linux/wayland/seat/pointer/mod.rs @@ -15,7 +15,7 @@ use sctk::reexports::protocols::unstable::pointer_constraints::v1::client::zwp_c use sctk::seat::pointer::{ThemeManager, ThemedPointer}; use sctk::window::{ConceptFrame, Window}; -use crate::event::ModifiersState; +use crate::keyboard::ModifiersState; use crate::platform_impl::wayland::event_loop::WinitState; use crate::window::CursorIcon; diff --git a/src/platform_impl/linux/wayland/seat/text_input/handlers.rs b/src/platform_impl/linux/wayland/seat/text_input/handlers.rs index 4ba13d6715..b57b5f58a3 100644 --- a/src/platform_impl/linux/wayland/seat/text_input/handlers.rs +++ b/src/platform_impl/linux/wayland/seat/text_input/handlers.rs @@ -69,9 +69,7 @@ pub(super) fn handle_text_input( _ => return, }; - for ch in text.chars() { - event_sink.push_window_event(WindowEvent::ReceivedCharacter(ch), window_id); - } + event_sink.push_window_event(WindowEvent::ReceivedImeText(text), window_id); } _ => (), } diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index 5f59651edb..fc6784c7c8 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -610,6 +610,11 @@ impl Window { self.event_loop_awakener.ping(); } + #[inline] + pub fn reset_dead_keys(&self) { + // not implemented + } + #[inline] pub fn display(&self) -> &Display { &self.display diff --git a/src/platform_impl/linux/x11/dnd.rs b/src/platform_impl/linux/x11/dnd.rs index 997228cd59..6d5dc4d0b6 100644 --- a/src/platform_impl/linux/x11/dnd.rs +++ b/src/platform_impl/linux/x11/dnd.rs @@ -8,55 +8,42 @@ use std::{ use percent_encoding::percent_decode; -use super::{ffi, util, XConnection, XError}; +use super::{ffi, XConnection}; +use xcb_dl_util::error::XcbError; +use xcb_dl_util::property::XcbGetPropertyError; #[derive(Debug)] pub struct DndAtoms { - pub aware: ffi::Atom, - pub enter: ffi::Atom, - pub leave: ffi::Atom, - pub drop: ffi::Atom, - pub position: ffi::Atom, - pub status: ffi::Atom, - pub action_private: ffi::Atom, - pub selection: ffi::Atom, - pub finished: ffi::Atom, - pub type_list: ffi::Atom, - pub uri_list: ffi::Atom, - pub none: ffi::Atom, + pub aware: ffi::xcb_atom_t, + pub enter: ffi::xcb_atom_t, + pub leave: ffi::xcb_atom_t, + pub drop: ffi::xcb_atom_t, + pub position: ffi::xcb_atom_t, + pub status: ffi::xcb_atom_t, + pub action_private: ffi::xcb_atom_t, + pub selection: ffi::xcb_atom_t, + pub finished: ffi::xcb_atom_t, + pub type_list: ffi::xcb_atom_t, + pub uri_list: ffi::xcb_atom_t, + pub none: ffi::xcb_atom_t, } impl DndAtoms { - pub fn new(xconn: &Arc) -> Result { - let names = [ - b"XdndAware\0".as_ptr() as *mut c_char, - b"XdndEnter\0".as_ptr() as *mut c_char, - b"XdndLeave\0".as_ptr() as *mut c_char, - b"XdndDrop\0".as_ptr() as *mut c_char, - b"XdndPosition\0".as_ptr() as *mut c_char, - b"XdndStatus\0".as_ptr() as *mut c_char, - b"XdndActionPrivate\0".as_ptr() as *mut c_char, - b"XdndSelection\0".as_ptr() as *mut c_char, - b"XdndFinished\0".as_ptr() as *mut c_char, - b"XdndTypeList\0".as_ptr() as *mut c_char, - b"text/uri-list\0".as_ptr() as *mut c_char, - b"None\0".as_ptr() as *mut c_char, - ]; - let atoms = unsafe { xconn.get_atoms(&names) }?; - Ok(DndAtoms { - aware: atoms[0], - enter: atoms[1], - leave: atoms[2], - drop: atoms[3], - position: atoms[4], - status: atoms[5], - action_private: atoms[6], - selection: atoms[7], - finished: atoms[8], - type_list: atoms[9], - uri_list: atoms[10], - none: atoms[11], - }) + pub fn new(xconn: &Arc) -> Self { + DndAtoms { + aware: xconn.get_atom("XdndAware"), + enter: xconn.get_atom("XdndEnter"), + leave: xconn.get_atom("XdndLeave"), + drop: xconn.get_atom("XdndDrop"), + position: xconn.get_atom("XdndPosition"), + status: xconn.get_atom("XdndStatus"), + action_private: xconn.get_atom("XdndActionPrivate"), + selection: xconn.get_atom("XdndSelection"), + finished: xconn.get_atom("XdndFinished"), + type_list: xconn.get_atom("XdndTypeList"), + uri_list: xconn.get_atom("text/uri-list"), + none: xconn.get_atom("None"), + } } } @@ -91,25 +78,25 @@ pub struct Dnd { xconn: Arc, pub atoms: DndAtoms, // Populated by XdndEnter event handler - pub version: Option, - pub type_list: Option>, + pub version: Option, + pub type_list: Option>, // Populated by XdndPosition event handler - pub source_window: Option, + pub source_window: Option, // Populated by SelectionNotify event handler (triggered by XdndPosition event handler) pub result: Option, DndDataParseError>>, } impl Dnd { - pub fn new(xconn: Arc) -> Result { - let atoms = DndAtoms::new(&xconn)?; - Ok(Dnd { + pub fn new(xconn: Arc) -> Self { + let atoms = DndAtoms::new(&xconn); + Dnd { xconn, atoms, version: None, type_list: None, source_window: None, result: None, - }) + } } pub fn reset(&mut self) { @@ -121,69 +108,67 @@ impl Dnd { pub unsafe fn send_status( &self, - this_window: c_ulong, - target_window: c_ulong, + this_window: ffi::xcb_window_t, + target_window: ffi::xcb_window_t, state: DndState, - ) -> Result<(), XError> { + ) { let (accepted, action) = match state { - DndState::Accepted => (1, self.atoms.action_private as c_long), - DndState::Rejected => (0, self.atoms.none as c_long), + DndState::Accepted => (1, self.atoms.action_private), + DndState::Rejected => (0, self.atoms.none), }; - self.xconn - .send_client_msg( - target_window, - target_window, - self.atoms.status, - None, - [this_window as c_long, accepted, 0, 0, action], - ) - .flush() + let pending = self.xconn.send_client_msg( + target_window, + target_window, + self.atoms.status, + None, + [this_window, accepted, 0, 0, action], + ); + self.xconn.discard(pending); } pub unsafe fn send_finished( &self, - this_window: c_ulong, - target_window: c_ulong, + this_window: ffi::xcb_window_t, + target_window: ffi::xcb_window_t, state: DndState, - ) -> Result<(), XError> { + ) -> Result<(), XcbError> { let (accepted, action) = match state { - DndState::Accepted => (1, self.atoms.action_private as c_long), - DndState::Rejected => (0, self.atoms.none as c_long), + DndState::Accepted => (1, self.atoms.action_private), + DndState::Rejected => (0, self.atoms.none), }; - self.xconn - .send_client_msg( - target_window, - target_window, - self.atoms.finished, - None, - [this_window as c_long, accepted, action, 0, 0], - ) - .flush() + let pending = self.xconn.send_client_msg( + target_window, + target_window, + self.atoms.finished, + None, + [this_window, accepted, action, 0, 0], + ); + self.xconn.check_pending1(pending) } pub unsafe fn get_type_list( &self, - source_window: c_ulong, - ) -> Result, util::GetPropertyError> { + source_window: ffi::xcb_window_t, + ) -> Result, XcbGetPropertyError> { self.xconn - .get_property(source_window, self.atoms.type_list, ffi::XA_ATOM) + .get_property(source_window, self.atoms.type_list, ffi::XCB_ATOM_ATOM) } - pub unsafe fn convert_selection(&self, window: c_ulong, time: c_ulong) { - (self.xconn.xlib.XConvertSelection)( - self.xconn.display, + pub unsafe fn convert_selection(&self, window: ffi::xcb_window_t, time: ffi::xcb_time_t) { + self.xconn.xcb.xcb_convert_selection( + self.xconn.c, + window, self.atoms.selection, self.atoms.uri_list, self.atoms.selection, - window, time, ); } pub unsafe fn read_data( &self, - window: c_ulong, - ) -> Result, util::GetPropertyError> { + window: ffi::xcb_window_t, + ) -> Result, XcbGetPropertyError> { self.xconn .get_property(window, self.atoms.selection, self.atoms.uri_list) } diff --git a/src/platform_impl/linux/x11/event_processor.rs b/src/platform_impl/linux/x11/event_processor.rs index 65ed4d9ff1..8dcc045b91 100644 --- a/src/platform_impl/linux/x11/event_processor.rs +++ b/src/platform_impl/linux/x11/event_processor.rs @@ -1,62 +1,111 @@ -use std::{cell::RefCell, collections::HashMap, rc::Rc, slice, sync::Arc}; - -use libc::{c_char, c_int, c_long, c_uint, c_ulong}; +use std::sync::atomic::Ordering::Relaxed; +use std::{collections::HashMap, rc::Rc, slice, sync::Arc}; use parking_lot::MutexGuard; +use SeatFocus::{KbFocus, PtrFocus}; use super::{ - events, ffi, get_xtarget, mkdid, mkwid, monitor, util, Device, DeviceId, DeviceInfo, Dnd, - DndState, GenericEventCookie, ImeReceiver, ScrollOrientation, UnownedWindow, WindowId, - XExtension, + ffi, get_xtarget, mkdid, mkwid, util, Device, DeviceId, DeviceInfo, Dnd, DndState, + ScrollOrientation, UnownedWindow, WindowId, }; -use util::modifiers::{ModifierKeyState, ModifierKeymap}; - use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - event::{ - DeviceEvent, ElementState, Event, KeyboardInput, ModifiersState, TouchPhase, WindowEvent, - }, + event::{DeviceEvent, Event, KeyEvent, RawKeyEvent, TouchPhase, WindowEvent}, event_loop::EventLoopWindowTarget as RootELW, + keyboard::ModifiersState, + platform_impl::platform::{ + common::{keymap, xkb_state::KbState}, + KeyEventExtra, + }, + platform_impl::x11::EventLoopWindowTarget, }; /// The X11 documentation states: "Keycodes lie in the inclusive range [8,255]". const KEYCODE_OFFSET: u8 = 8; +pub(super) struct Seat { + kb_state: KbState, + /// The master keyboard of this seat + keyboard: ffi::xcb_input_device_id_t, + /// The master pointer of this seat + pointer: ffi::xcb_input_device_id_t, + /// The window that has this seats keyboard focus + kb_focus: Option, + /// The window that has this seats pointer focus + ptr_focus: Option, + /// The latest modifiers state + current_modifiers: ModifiersState, + + num_errors: usize, +} + +enum SeatFocus { + KbFocus, + PtrFocus, +} + 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) devices: HashMap, pub(super) target: Rc>, - pub(super) mod_keymap: ModifierKeymap, - pub(super) device_mod_state: ModifierKeyState, + pub(super) seats: Vec, // Number of touch events currently in progress pub(super) num_touch: u32, pub(super) first_touch: Option, - // Currently focused window belonging to this process - pub(super) active_window: Option, } impl EventProcessor { - pub(super) fn init_device(&self, device: c_int) { - let wt = get_xtarget(&self.target); - let mut devices = self.devices.borrow_mut(); + pub(super) fn init_device( + target: &RootELW, + devices: &mut HashMap, + seats: &mut Vec, + device: ffi::xcb_input_device_id_t, + ) { + let wt = get_xtarget(target); if let Some(info) = DeviceInfo::get(&wt.xconn, device) { - for info in info.iter() { - devices.insert(DeviceId(info.deviceid), Device::new(&self, info)); + for info in info { + let info = unsafe { &*info }; + let device_id = DeviceId(info.deviceid); + if info.type_ == ffi::XCB_INPUT_DEVICE_TYPE_MASTER_KEYBOARD as _ { + if devices.contains_key(&device_id) { + seats.retain(|s| s.keyboard != info.deviceid); + } + match wt.xconn.make_auto_repeat_detectable(device_id.0) { + Ok(true) => {} + Ok(false) => log::warn!("X server does not support detectable auto-repeat"), + Err(e) => log::error!( + "Could not enable detectable auto repeat for device {}: {}", + device_id.0, + e + ), + } + let kb_state = KbState::from_x11_xkb(wt.xconn.c, info.deviceid).unwrap(); + seats.push(Seat { + kb_state, + keyboard: info.deviceid, + pointer: info.attachment, + kb_focus: None, + ptr_focus: None, + current_modifiers: ModifiersState::empty(), + num_errors: 0, + }); + } + devices.insert(device_id, Device::new(wt, info)); } } } - fn with_window(&self, window_id: ffi::Window, callback: F) -> Option + fn with_window( + wt: &EventLoopWindowTarget, + window_id: ffi::xcb_window_t, + callback: F, + ) -> Option where F: Fn(&Arc) -> Ret, { let mut deleted = false; let window_id = WindowId(window_id); - let wt = get_xtarget(&self.target); let result = wt .windows .borrow() @@ -74,144 +123,76 @@ impl EventProcessor { result } - fn window_exists(&self, window_id: ffi::Window) -> bool { - self.with_window(window_id, |_| ()).is_some() - } - - pub(super) fn poll(&self) -> bool { - let wt = get_xtarget(&self.target); - let result = unsafe { (wt.xconn.xlib.XPending)(wt.xconn.display) }; - - result != 0 + fn window_exists(wt: &EventLoopWindowTarget, window_id: ffi::xcb_window_t) -> bool { + Self::with_window(wt, 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) + pub(super) fn process_event(&mut self, xev: &mut ffi::xcb_generic_event_t, mut callback: F) where F: FnMut(Event<'_, T>), { let wt = get_xtarget(&self.target); - // XFilterEvent tells us when an event has been discarded by the input method. - // Specifically, this involves all of the KeyPress events in compose/pre-edit sequences, - // along with an extra copy of the KeyRelease events. This also prevents backspace and - // arrow keys from being detected twice. - if ffi::True - == unsafe { - (wt.xconn.xlib.XFilterEvent)(xev, { - let xev: &ffi::XAnyEvent = xev.as_ref(); - xev.window - }) - } { - return; - } - - // We can't call a `&mut self` method because of the above borrow, - // so we use this macro for repeated modifier state updates. - macro_rules! update_modifiers { - ( $state:expr , $modifier:expr ) => {{ - match ($state, $modifier) { - (state, modifier) => { - if let Some(modifiers) = - self.device_mod_state.update_state(&state, modifier) - { - if let Some(window_id) = self.active_window { - callback(Event::WindowEvent { - window_id: mkwid(window_id), - event: WindowEvent::ModifiersChanged(modifiers), - }); - } - } - } + let reset_dead_keys = wt.reset_dead_keys.load(Relaxed); + if reset_dead_keys != 0 { + for seat in &mut self.seats { + seat.kb_state.reset_dead_keys(); } - }}; + wt.reset_dead_keys.fetch_sub(reset_dead_keys, Relaxed); + } } - let event_type = xev.get_type(); - match event_type { - ffi::MappingNotify => { - let mapping: &ffi::XMappingEvent = xev.as_ref(); - - if mapping.request == ffi::MappingModifier - || mapping.request == ffi::MappingKeyboard - { - unsafe { - (wt.xconn.xlib.XRefreshKeyboardMapping)(xev.as_mut()); - } - wt.xconn - .check_errors() - .expect("Failed to call XRefreshKeyboardMapping"); - - self.mod_keymap.reset_from_x_connection(&wt.xconn); - self.device_mod_state.update_keymap(&self.mod_keymap); - } - } + let response_type = xev.response_type & 0x7f; + match response_type { + 0 => unsafe { + let error = &*(xev as *const _ as *const ffi::xcb_generic_error_t); + let error = wt.xconn.errors.parse(error); + log::error!("An unchecked error occurred: {}", error); + }, - ffi::ClientMessage => { - let client_msg: &ffi::XClientMessageEvent = xev.as_ref(); + ffi::XCB_CLIENT_MESSAGE => { + let client_msg = + unsafe { &*(xev as *const _ as *const ffi::xcb_client_message_event_t) }; + let data32 = unsafe { client_msg.data.data32 }; let window = client_msg.window; let window_id = mkwid(window); - if client_msg.data.get_long(0) as ffi::Atom == wt.wm_delete_window { + if data32[0] == wt.wm_delete_window { callback(Event::WindowEvent { window_id, event: WindowEvent::CloseRequested, }); - } else if client_msg.data.get_long(0) as ffi::Atom == wt.net_wm_ping { - let response_msg: &mut ffi::XClientMessageEvent = xev.as_mut(); - response_msg.window = wt.root; - wt.xconn - .send_event( - wt.root, - Some(ffi::SubstructureNotifyMask | ffi::SubstructureRedirectMask), - *response_msg, - ) - .queue(); - } else if client_msg.message_type == self.dnd.atoms.enter { - let source_window = client_msg.data.get_long(0) as c_ulong; - let flags = client_msg.data.get_long(1); + } else if data32[0] == wt.net_wm_ping { + Self::with_window(wt, window, |w| { + let response_msg = ffi::xcb_client_message_event_t { + window: w.screen.root, + ..*client_msg + }; + let pending = wt.xconn.send_event( + w.screen.root, + Some( + ffi::XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY + | ffi::XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, + ), + &response_msg, + ); + wt.xconn.discard(pending); + }); + } else if client_msg.type_ == self.dnd.atoms.enter { + let source_window = data32[0]; + let flags = data32[1]; let version = flags >> 24; self.dnd.version = Some(version); - let has_more_types = flags - (flags & (c_long::max_value() - 1)) == 1; + let has_more_types = flags - (flags & (u32::MAX - 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, - ]; + let type_list = vec![data32[2], data32[3], data32[4]]; 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 { + } else if client_msg.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 @@ -220,7 +201,7 @@ impl EventProcessor { // 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; + let source_window = data32[0]; // Equivalent to `(x << shift) | y` // where `shift = mem::size_of::() * 8` @@ -248,27 +229,25 @@ impl EventProcessor { unsafe { if self.dnd.result.is_none() { let time = if version >= 1 { - client_msg.data.get_long(3) as c_ulong + data32[3] } else { // In version 0, time isn't specified - ffi::CurrentTime + ffi::XCB_TIME_CURRENT_TIME }; // 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."); + .send_status(window, source_window, DndState::Accepted); } } else { unsafe { self.dnd - .send_status(window, source_window, DndState::Rejected) - .expect("Failed to send `XdndStatus` message."); + .send_status(window, source_window, DndState::Rejected); } self.dnd.reset(); } - } else if client_msg.message_type == self.dnd.atoms.drop { + } else if client_msg.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 { @@ -283,7 +262,7 @@ impl EventProcessor { } 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; + let source_window = data32[0]; (source_window, DndState::Rejected) }; unsafe { @@ -292,7 +271,7 @@ impl EventProcessor { .expect("Failed to send `XdndFinished` message."); } self.dnd.reset(); - } else if client_msg.message_type == self.dnd.atoms.leave { + } else if client_msg.type_ == self.dnd.atoms.leave { self.dnd.reset(); callback(Event::WindowEvent { window_id, @@ -301,8 +280,9 @@ impl EventProcessor { } } - ffi::SelectionNotify => { - let xsel: &ffi::XSelectionEvent = xev.as_ref(); + ffi::XCB_SELECTION_NOTIFY => { + let xsel = + unsafe { &*(xev as *const _ as *const ffi::xcb_selection_notify_event_t) }; let window = xsel.requestor; let window_id = mkwid(window); @@ -328,12 +308,13 @@ impl EventProcessor { } } - ffi::ConfigureNotify => { - let xev: &ffi::XConfigureEvent = xev.as_ref(); + ffi::XCB_CONFIGURE_NOTIFY => { + let xev = + unsafe { &*(xev as *const _ as *const ffi::xcb_configure_notify_event_t) }; let xwindow = xev.window; let window_id = mkwid(xwindow); - if let Some(window) = self.with_window(xwindow, Arc::clone) { + if let Some(window) = Self::with_window(wt, xwindow, Arc::clone) { // So apparently... // `XSendEvent` (synthetic `ConfigureNotify`) -> position relative to root // `XConfigureNotify` (real `ConfigureNotify`) -> position relative to parent @@ -341,7 +322,7 @@ impl EventProcessor { // 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; + let is_synthetic = xev.response_type & 0x80 != 0; // These are both in physical space. let new_inner_size = (xev.width as u32, xev.height as u32); @@ -383,8 +364,7 @@ impl EventProcessor { .as_ref() .cloned() .unwrap_or_else(|| { - let frame_extents = - wt.xconn.get_frame_extents_heuristic(xwindow, wt.root); + let frame_extents = wt.xconn.get_frame_extents_heuristic(&window); shared_state_lock.frame_extents = Some(frame_extents.clone()); frame_extents }); @@ -466,7 +446,9 @@ impl EventProcessor { // 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 || !util::wm_name_is_one_of(&["Xfwm4"]) { + if new_inner_size == adjusted_size + || !window.screen.wm_name_is_one_of(&["Xfwm4"]) + { // When this finally happens, the event will not be synthetic. shared_state_lock.dpi_adjusted = None; } else { @@ -486,23 +468,23 @@ impl EventProcessor { } } - ffi::ReparentNotify => { - let xev: &ffi::XReparentEvent = xev.as_ref(); + ffi::XCB_REPARENT_NOTIFY => { + let xev = unsafe { &*(xev as *const _ as *const ffi::xcb_reparent_notify_event_t) }; // 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); + wt.xconn.update_cached_wm_info(); - self.with_window(xev.window, |window| { + Self::with_window(wt, xev.window, |window| { window.invalidate_cached_frame_extents(); }); } - ffi::DestroyNotify => { - let xev: &ffi::XDestroyWindowEvent = xev.as_ref(); + ffi::XCB_DESTROY_NOTIFY => { + let xev = unsafe { &*(xev as *const _ as *const ffi::xcb_destroy_notify_event_t) }; let window = xev.window; let window_id = mkwid(window); @@ -511,28 +493,22 @@ impl EventProcessor { // cleanup again here. wt.windows.borrow_mut().remove(&WindowId(window)); - // Since all XIM stuff needs to happen from the same thread, we destroy the input - // context here instead of when dropping the window. - wt.ime - .borrow_mut() - .remove_context(window) - .expect("Failed to destroy input context"); - callback(Event::WindowEvent { window_id, event: WindowEvent::Destroyed, }); } - ffi::VisibilityNotify => { - let xev: &ffi::XVisibilityEvent = xev.as_ref(); + ffi::XCB_VISIBILITY_NOTIFY => { + let xev = + unsafe { &*(xev as *const _ as *const ffi::xcb_visibility_notify_event_t) }; let xwindow = xev.window; - self.with_window(xwindow, |window| window.visibility_notify()); + Self::with_window(wt, xwindow, |window| window.visibility_notify()); } - ffi::Expose => { - let xev: &ffi::XExposeEvent = xev.as_ref(); + ffi::XCB_EXPOSE => { + let xev = unsafe { &*(xev as *const _ as *const ffi::xcb_expose_event_t) }; // Multiple Expose events may be received for subareas of a window. // We issue `RedrawRequested` only for the last event of such a series. @@ -544,82 +520,10 @@ impl EventProcessor { } } - ffi::KeyPress | ffi::KeyRelease => { - use crate::event::ElementState::{Pressed, Released}; - - // Note that in compose/pre-edit sequences, this will always be Released. - let state = if xev.get_type() == ffi::KeyPress { - Pressed - } else { - Released - }; - - let xkev: &mut ffi::XKeyEvent = xev.as_mut(); - - let window = xkev.window; - let window_id = mkwid(window); - - // Standard virtual core keyboard ID. XInput2 needs to be used to get a reliable - // value, though this should only be an issue under multiseat configurations. - let device = util::VIRTUAL_CORE_KEYBOARD; - let device_id = mkdid(device); - let keycode = xkev.keycode; - - // When a compose sequence or IME pre-edit is finished, it ends in a KeyPress with - // a keycode of 0. - if keycode != 0 { - let scancode = keycode - KEYCODE_OFFSET as u32; - let keysym = wt.xconn.lookup_keysym(xkev); - let virtual_keycode = events::keysym_to_element(keysym as c_uint); + ffi::XCB_GE_GENERIC => { + let xev = unsafe { &*(xev as *const _ as *const ffi::xcb_ge_generic_event_t) }; - update_modifiers!( - ModifiersState::from_x11_mask(xkev.state), - self.mod_keymap.get_modifier(xkev.keycode as ffi::KeyCode) - ); - - let modifiers = self.device_mod_state.modifiers(); - - #[allow(deprecated)] - callback(Event::WindowEvent { - window_id, - event: WindowEvent::KeyboardInput { - device_id, - input: KeyboardInput { - state, - scancode, - virtual_keycode, - modifiers, - }, - is_synthetic: false, - }, - }); - } - - if state == Pressed { - let written = if let Some(ic) = wt.ime.borrow().get_context(window) { - wt.xconn.lookup_utf8(ic, xkev) - } else { - return; - }; - - for chr in written.chars() { - let event = Event::WindowEvent { - window_id, - event: WindowEvent::ReceivedCharacter(chr), - }; - callback(event); - } - } - } - - ffi::GenericEvent => { - let guard = if let Some(e) = GenericEventCookie::from_event(&wt.xconn, *xev) { - e - } else { - return; - }; - let xev = &guard.cookie; - if self.xi2ext.opcode != xev.extension { + if wt.xconn.xinput_extension != xev.extension { return; } @@ -628,64 +532,62 @@ impl EventProcessor { MouseButton::{Left, Middle, Other, Right}, MouseScrollDelta::LineDelta, Touch, - WindowEvent::{ - AxisMotion, CursorEntered, CursorLeft, CursorMoved, Focused, MouseInput, - MouseWheel, - }, + WindowEvent::{AxisMotion, CursorMoved, MouseInput, MouseWheel}, }; - match xev.evtype { - ffi::XI_ButtonPress | ffi::XI_ButtonRelease => { - let xev: &ffi::XIDeviceEvent = unsafe { &*(xev.data as *const _) }; - let window_id = mkwid(xev.event); - let device_id = mkdid(xev.deviceid); - if (xev.flags & ffi::XIPointerEmulated) != 0 { + match xev.event_type { + ffi::XCB_INPUT_BUTTON_PRESS | ffi::XCB_INPUT_BUTTON_RELEASE => { + let xev = unsafe { + &*(xev as *const _ as *const ffi::xcb_input_button_press_event_t) + }; + if (xev.flags & ffi::XCB_INPUT_POINTER_EVENT_FLAGS_POINTER_EMULATED) != 0 { // Deliver multi-touch events instead of emulated mouse events. return; } - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); + let seat = match find_seat_by_pointer(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); + Self::update_seat_focus(seat, PtrFocus, wt, Some(xev.event), &mut callback); - let state = if xev.evtype == ffi::XI_ButtonPress { + let window_id = mkwid(xev.event); + let device_id = mkdid(seat.keyboard); + + let state = if xev.event_type == ffi::XCB_INPUT_BUTTON_PRESS { 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, - }, - }), + ffi::XCB_BUTTON_INDEX_1 + | ffi::XCB_BUTTON_INDEX_2 + | ffi::XCB_BUTTON_INDEX_3 => { + let button = match xev.detail as u32 { + ffi::XCB_BUTTON_INDEX_1 => Left, + ffi::XCB_BUTTON_INDEX_2 => Middle, + ffi::XCB_BUTTON_INDEX_3 => Right, + _ => unreachable!(), + }; + callback(Event::WindowEvent { + window_id, + event: MouseInput { + device_id, + state, + button, + modifiers: seat.current_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 { + if xev.flags & ffi::XCB_INPUT_POINTER_EVENT_FLAGS_POINTER_EMULATED + == 0 + { callback(Event::WindowEvent { window_id, event: MouseWheel { @@ -698,7 +600,7 @@ impl EventProcessor { _ => unreachable!(), }, phase: TouchPhase::Moved, - modifiers, + modifiers: seat.current_modifiers, }, }); } @@ -710,33 +612,41 @@ impl EventProcessor { device_id, state, button: Other(x as u16), - modifiers, + modifiers: seat.current_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); + ffi::XCB_INPUT_MOTION => { + let xev = + unsafe { &*(xev as *const _ as *const ffi::xcb_input_motion_event_t) }; - let modifiers = ModifiersState::from_x11(&xev.mods); - update_modifiers!(modifiers, None); + let seat = match find_seat_by_pointer(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); + Self::update_seat_focus(seat, PtrFocus, wt, Some(xev.event), &mut callback); + + let device_id = mkdid(seat.keyboard); + let window_id = mkwid(xev.event); + let event_x = util::fp1616_to_f64(xev.event_x); + let event_y = util::fp1616_to_f64(xev.event_y); + let new_cursor_pos = (event_x, event_y); - let cursor_moved = self.with_window(xev.event, |window| { + let cursor_moved = Self::with_window(wt, 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, xev.event_y); + let position = PhysicalPosition::new(event_x, event_y); callback(Event::WindowEvent { window_id, event: CursorMoved { device_id, position, - modifiers, + modifiers: seat.current_modifiers, }, }); } else if cursor_moved.is_none() { @@ -748,55 +658,58 @@ impl EventProcessor { { let mask = unsafe { slice::from_raw_parts( - xev.valuators.mask, - xev.valuators.mask_len as usize, + wt.xconn.xinput.xcb_input_button_press_valuator_mask(xev), + xev.valuators_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 axis_ids = mask_to_axis_ids(mask); + let axes = unsafe { + slice::from_raw_parts( + wt.xconn.xinput.xcb_input_button_press_axisvalues(xev), + axis_ids.len(), + ) }; + let physical_device = + match self.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 }, + for (&i, &x) in axis_ids.iter().zip(axes.iter()) { + let x = util::fp3232_to_f64(x); + 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) + } }, - }); - } - value = unsafe { value.offset(1) }; + phase: TouchPhase::Moved, + modifiers: seat.current_modifiers, + }, + }); + } else { + events.push(Event::WindowEvent { + window_id, + event: AxisMotion { + device_id, + axis: i as u32, + value: x, + }, + }); } } } @@ -805,15 +718,24 @@ impl EventProcessor { } } - ffi::XI_Enter => { - let xev: &ffi::XIEnterEvent = unsafe { &*(xev.data as *const _) }; + ffi::XCB_INPUT_ENTER => { + let xev = + unsafe { &*(xev as *const _ as *const ffi::xcb_input_enter_event_t) }; - let window_id = mkwid(xev.event); - let device_id = mkdid(xev.deviceid); + let seat = match find_seat_by_pointer(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); + Self::update_seat_focus(seat, PtrFocus, wt, Some(xev.event), &mut callback); + + let device_id = mkdid(seat.keyboard); - 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 let Some(all_info) = + DeviceInfo::get(&wt.xconn, ffi::XCB_INPUT_DEVICE_ALL as _) + { + for device_info in all_info { + let device_info = unsafe { &*device_info }; 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 @@ -822,167 +744,89 @@ impl EventProcessor { || device_info.attachment == xev.sourceid { let device_id = DeviceId(device_info.deviceid); - if let Some(device) = devices.get_mut(&device_id) { - device.reset_scroll_position(device_info); + if let Some(device) = self.devices.get_mut(&device_id) { + device.reset_scroll_position(wt, device_info); } } } } - if self.window_exists(xev.event) { - callback(Event::WindowEvent { - window_id, - event: CursorEntered { device_id }, - }); - - let position = PhysicalPosition::new(xev.event_x, 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 - .query_pointer(xev.event, xev.deviceid) - .expect("Failed to query pointer device") - .get_modifier_state(); + if let Some(window) = seat.ptr_focus { + let event_x = util::fp1616_to_f64(xev.event_x); + let event_y = util::fp1616_to_f64(xev.event_y); + let position = PhysicalPosition::new(event_x, event_y); callback(Event::WindowEvent { - window_id, + window_id: mkwid(window), event: CursorMoved { device_id, position, - modifiers, + modifiers: seat.current_modifiers, }, }); } } - ffi::XI_Leave => { - let xev: &ffi::XILeaveEvent = unsafe { &*(xev.data as *const _) }; + ffi::XCB_INPUT_LEAVE => { + let xev = + unsafe { &*(xev as *const _ as *const ffi::xcb_input_leave_event_t) }; - // 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), - }, - }); - } + let seat = match find_seat_by_pointer(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); + Self::update_seat_focus(seat, PtrFocus, wt, None, &mut callback); } - ffi::XI_FocusIn => { - let xev: &ffi::XIFocusInEvent = unsafe { &*(xev.data as *const _) }; - - wt.ime - .borrow_mut() - .focus(xev.event) - .expect("Failed to focus input context"); - - let modifiers = ModifiersState::from_x11(&xev.mods); - - self.device_mod_state.update_state(&modifiers, None); - - if self.active_window != Some(xev.event) { - self.active_window = Some(xev.event); - - let window_id = mkwid(xev.event); - let position = PhysicalPosition::new(xev.event_x, xev.event_y); - - callback(Event::WindowEvent { - window_id, - event: Focused(true), - }); - - if !modifiers.is_empty() { - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ModifiersChanged(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); - - callback(Event::WindowEvent { - window_id, - event: CursorMoved { - device_id: mkdid(pointer_id), - position, - modifiers, - }, - }); + ffi::XCB_INPUT_FOCUS_IN => { + let xev = unsafe { + &*(xev as *const _ as *const ffi::xcb_input_focus_in_event_t) + }; - // 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, - ); - } + let seat = match find_seat(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); + Self::update_seat_focus(seat, KbFocus, wt, Some(xev.event), &mut callback); } - ffi::XI_FocusOut => { - let xev: &ffi::XIFocusOutEvent = unsafe { &*(xev.data as *const _) }; - if !self.window_exists(xev.event) { - return; - } - wt.ime - .borrow_mut() - .unfocus(xev.event) - .expect("Failed to unfocus input context"); - - 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, - ); - - callback(Event::WindowEvent { - window_id, - event: WindowEvent::ModifiersChanged(ModifiersState::empty()), - }); + ffi::XCB_INPUT_FOCUS_OUT => { + let xev = unsafe { + &*(xev as *const _ as *const ffi::xcb_input_focus_out_event_t) + }; - callback(Event::WindowEvent { - window_id, - event: Focused(false), - }) - } + let seat = match find_seat(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); + Self::update_seat_focus(seat, KbFocus, wt, None, &mut callback); } - 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, + ffi::XCB_INPUT_TOUCH_BEGIN + | ffi::XCB_INPUT_TOUCH_UPDATE + | ffi::XCB_INPUT_TOUCH_END => { + let xev = unsafe { + &*(xev as *const _ as *const ffi::xcb_input_touch_begin_event_t) + }; + let phase = match xev.event_type { + ffi::XCB_INPUT_TOUCH_BEGIN => TouchPhase::Started, + ffi::XCB_INPUT_TOUCH_UPDATE => TouchPhase::Moved, + ffi::XCB_INPUT_TOUCH_END => TouchPhase::Ended, _ => unreachable!(), }; - if self.window_exists(xev.event) { + let seat = match find_seat_by_pointer(&mut self.seats, xev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xev.mods, &xev.group, &mut callback); + Self::update_seat_focus(seat, PtrFocus, wt, Some(xev.event), &mut callback); + + if let Some(window) = seat.ptr_focus { + let window_id = mkwid(window); let id = xev.detail as u64; - let modifiers = self.device_mod_state.modifiers(); - let location = - PhysicalPosition::new(xev.event_x as f64, xev.event_y as f64); + let event_x = util::fp1616_to_f64(xev.event_x); + let event_y = util::fp1616_to_f64(xev.event_y); + let location = PhysicalPosition::new(event_x, event_y); // Mouse cursor position changes when touch events are received. // Only the first concurrently active touch ID moves the mouse cursor. @@ -991,9 +835,9 @@ impl EventProcessor { callback(Event::WindowEvent { window_id, event: WindowEvent::CursorMoved { - device_id: mkdid(util::VIRTUAL_CORE_POINTER), + device_id: mkdid(seat.keyboard), position: location.cast(), - modifiers, + modifiers: seat.current_modifiers, }, }); } @@ -1001,7 +845,7 @@ impl EventProcessor { callback(Event::WindowEvent { window_id, event: WindowEvent::Touch(Touch { - device_id: mkdid(xev.deviceid), + device_id: mkdid(seat.keyboard), phase, location, force: None, // TODO @@ -1011,16 +855,18 @@ impl EventProcessor { } } - ffi::XI_RawButtonPress | ffi::XI_RawButtonRelease => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; - if xev.flags & ffi::XIPointerEmulated == 0 { + ffi::XCB_INPUT_RAW_BUTTON_PRESS | ffi::XCB_INPUT_RAW_BUTTON_RELEASE => { + let xev = unsafe { + &*(xev as *const _ as *const ffi::xcb_input_raw_button_press_event_t) + }; + if xev.flags & ffi::XCB_INPUT_POINTER_EVENT_FLAGS_POINTER_EMULATED == 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, + state: match xev.event_type { + ffi::XCB_INPUT_RAW_BUTTON_PRESS => Pressed, + ffi::XCB_INPUT_RAW_BUTTON_RELEASE => Released, _ => unreachable!(), }, }, @@ -1028,40 +874,47 @@ impl EventProcessor { } } - ffi::XI_RawMotion => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; + ffi::XCB_INPUT_RAW_MOTION => { + let xev = unsafe { + &*(xev as *const _ as *const ffi::xcb_input_raw_motion_event_t) + }; let did = mkdid(xev.deviceid); let mask = unsafe { slice::from_raw_parts( - xev.valuators.mask, - xev.valuators.mask_len as usize, + wt.xconn + .xinput + .xcb_input_raw_button_press_valuator_mask(xev), + xev.valuators_len as usize, + ) + }; + let axis_ids = mask_to_axis_ids(mask); + let axes = unsafe { + slice::from_raw_parts( + wt.xconn.xinput.xcb_input_raw_button_press_axisvalues(xev), + axis_ids.len(), ) }; - 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) }; + for (&i, &x) in axis_ids.iter().zip(axes.iter()) { + let x = util::fp3232_to_f64(x); + // 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 { @@ -1079,77 +932,125 @@ impl EventProcessor { } } - ffi::XI_RawKeyPress | ffi::XI_RawKeyRelease => { - let xev: &ffi::XIRawEvent = unsafe { &*(xev.data as *const _) }; + // The regular KeyPress event has a problem where if you press a dead key, a KeyPress + // event won't be emitted. XInput 2 does not have this problem. + ffi::XCB_INPUT_KEY_PRESS | ffi::XCB_INPUT_KEY_RELEASE => { + let xkev = unsafe { + &*(xev as *const _ as *const ffi::xcb_input_key_press_event_t) + }; + + let seat = match find_seat(&mut self.seats, xkev.deviceid) { + Some(seat) => seat, + _ => return, + }; + Self::update_seat_kb_xi(seat, &xkev.mods, &xkev.group, &mut callback); + Self::update_seat_focus(seat, KbFocus, wt, Some(xkev.event), &mut callback); + + if let Some(focus) = seat.kb_focus { + let state = if xev.event_type == ffi::XCB_INPUT_KEY_PRESS { + Pressed + } else { + Released + }; + + let device_id = mkdid(seat.keyboard); + let keycode = xkev.detail as u32; + + let ker = seat.kb_state.process_key_event( + keycode, + xkev.group.effective as u32, + state, + ); + let repeat = + xkev.flags & ffi::XCB_INPUT_KEY_EVENT_FLAGS_KEY_REPEAT != 0; - let state = match xev.evtype { - ffi::XI_RawKeyPress => Pressed, - ffi::XI_RawKeyRelease => Released, + callback(Event::WindowEvent { + window_id: mkwid(focus), + event: WindowEvent::KeyboardInput { + device_id, + event: KeyEvent { + physical_key: ker.keycode, + logical_key: ker.key, + text: ker.text, + location: ker.location, + state, + repeat, + platform_specific: KeyEventExtra { + key_without_modifiers: ker.key_without_modifiers, + text_with_all_modifiers: ker.text_with_all_modifiers, + }, + }, + is_synthetic: false, + }, + }); + } + } + + ffi::XCB_INPUT_RAW_KEY_PRESS | ffi::XCB_INPUT_RAW_KEY_RELEASE => { + let xev = unsafe { + &*(xev as *const _ as *const ffi::xcb_input_raw_key_press_event_t) + }; + + let state = match xev.event_type { + ffi::XCB_INPUT_RAW_KEY_PRESS => Pressed, + ffi::XCB_INPUT_RAW_KEY_RELEASE => Released, _ => unreachable!(), }; let device_id = mkdid(xev.sourceid); let keycode = xev.detail; - let scancode = keycode - KEYCODE_OFFSET as i32; - if scancode < 0 { + if keycode < KEYCODE_OFFSET as u32 { 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(); + let physical_key = keymap::raw_keycode_to_keycode(keycode); - #[allow(deprecated)] callback(Event::DeviceEvent { device_id, - event: DeviceEvent::Key(KeyboardInput { - scancode: scancode as u32, - virtual_keycode, + event: DeviceEvent::Key(RawKeyEvent { + physical_key, state, - modifiers, }), }); - - if let Some(modifier) = - self.mod_keymap.get_modifier(keycode as ffi::KeyCode) - { - self.device_mod_state.key_event( - state, - keycode as ffi::KeyCode, - modifier, - ); - - let new_modifiers = self.device_mod_state.modifiers(); - - if modifiers != new_modifiers { - if let Some(window_id) = self.active_window { - callback(Event::WindowEvent { - window_id: mkwid(window_id), - event: WindowEvent::ModifiersChanged(new_modifiers), - }); - } - } - } } - ffi::XI_HierarchyChanged => { - let xev: &ffi::XIHierarchyEvent = unsafe { &*(xev.data as *const _) }; - for info in - unsafe { slice::from_raw_parts(xev.info, xev.num_info as usize) } - { - if 0 != info.flags & (ffi::XISlaveAdded | ffi::XIMasterAdded) { - self.init_device(info.deviceid); + ffi::XCB_INPUT_HIERARCHY => { + let xev = unsafe { + &*(xev as *const _ as *const ffi::xcb_input_hierarchy_event_t) + }; + let infos = unsafe { + slice::from_raw_parts( + wt.xconn.xinput.xcb_input_hierarchy_infos(xev), + xev.num_infos as _, + ) + }; + for info in infos { + if 0 != info.flags + & (ffi::XCB_INPUT_HIERARCHY_MASK_SLAVE_ADDED + | ffi::XCB_INPUT_HIERARCHY_MASK_MASTER_ADDED) + { + Self::init_device( + &self.target, + &mut self.devices, + &mut self.seats, + info.deviceid, + ); callback(Event::DeviceEvent { device_id: mkdid(info.deviceid), event: DeviceEvent::Added, }); - } else if 0 != info.flags & (ffi::XISlaveRemoved | ffi::XIMasterRemoved) + } else if 0 + != info.flags + & (ffi::XCB_INPUT_HIERARCHY_MASK_SLAVE_REMOVED + | ffi::XCB_INPUT_HIERARCHY_MASK_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)); + self.devices.remove(&DeviceId(info.deviceid)); + if info.type_ == ffi::XCB_INPUT_DEVICE_TYPE_MASTER_KEYBOARD as _ { + self.seats.retain(|s| s.keyboard != info.deviceid); + } } } } @@ -1157,125 +1058,259 @@ impl EventProcessor { _ => {} } } - _ => { - if event_type == self.randr_event_offset { - // In the future, it would be quite easy to emit monitor hotplug events. - let prev_list = monitor::invalidate_cached_monitor_list(); - if let Some(prev_list) = prev_list { - let new_list = wt.xconn.available_monitors(); - for new_monitor in new_list { - prev_list - .iter() - .find(|prev_monitor| prev_monitor.name == new_monitor.name) - .map(|prev_monitor| { - if new_monitor.scale_factor != prev_monitor.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( - prev_monitor.scale_factor, - new_monitor.scale_factor, - width, - height, - &*window.shared_state.lock(), - ); - - let window_id = crate::window::WindowId( - crate::platform_impl::platform::WindowId::X( - *window_id, - ), + _ if response_type == wt.xconn.randr_first_event => { + // In the future, it would be quite easy to emit monitor hotplug events. + let prev_list = wt.xconn.invalidate_cached_monitor_list(); + if let Some(prev_list) = prev_list { + let new_list = wt.xconn.available_monitors(); + for new_monitor in new_list { + prev_list + .iter() + .find(|prev_monitor| prev_monitor.name == new_monitor.name) + .map(|prev_monitor| { + if new_monitor.scale_factor != prev_monitor.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( + prev_monitor.scale_factor, + new_monitor.scale_factor, + width, + height, + &*window.shared_state.lock(), + ); + + let window_id = crate::window::WindowId( + crate::platform_impl::platform::WindowId::X( + *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, ); - 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, - ); - } } } } } - }); - } + } + }); } } } - } - - match self.ime_receiver.try_recv() { - Ok((window_id, x, y)) => { - wt.ime.borrow_mut().send_xim_spot(window_id, x, y); + _ if response_type == wt.xconn.xkb_first_event => { + let xev = unsafe { &*(xev as *const _ as *const ffi::xcb_xkb_map_notify_event_t) }; + let seat = match find_seat(&mut self.seats, xev.device_id as _) { + Some(state) => state, + _ => return, + }; + match xev.xkb_type { + ffi::XCB_XKB_MAP_NOTIFY | ffi::XCB_XKB_NEW_KEYBOARD_NOTIFY => { + unsafe { + seat.kb_state.init_with_x11_keymap(xev.device_id as _); + } + Self::modifiers_changed(seat, &mut callback); + } + ffi::XCB_XKB_STATE_NOTIFY => { + let xev = unsafe { + &*(xev as *const _ as *const ffi::xcb_xkb_state_notify_event_t) + }; + Self::update_seat_kb( + seat, + xev.base_mods as u32, + xev.latched_mods as u32, + xev.locked_mods as u32, + xev.base_group as u32, + xev.latched_group as u32, + xev.locked_group as u32, + &mut callback, + ); + } + _ => {} + } } - Err(_) => (), + _ => {} } } - fn handle_pressed_keys( - wt: &super::EventLoopWindowTarget, - window_id: crate::window::WindowId, - state: ElementState, - mod_keymap: &ModifierKeymap, - device_mod_state: &mut ModifierKeyState, + /// Updates the window that has this seat's keyboard/pointer focus. + /// + /// This function must be called whenever an event occurs that could potentially imply a + /// change of the focus. Some non-multi-seat-aware window managers treat all focus changes + /// as changes of the core-seat focus. In these cases the X server will not send FocusIn/Out + /// events for additional seats. By calling this function on every keyboard/pointer input, + /// we update the focus as necessary. + fn update_seat_focus( + seat: &mut Seat, + component: SeatFocus, + wt: &EventLoopWindowTarget, + mut focus: Option, callback: &mut F, ) where F: FnMut(Event<'_, T>), { - let device_id = mkdid(util::VIRTUAL_CORE_KEYBOARD); - let modifiers = device_mod_state.modifiers(); - - // Update modifiers state and emit key events based on which keys are currently pressed. - for keycode in wt - .xconn - .query_keymap() - .into_iter() - .filter(|k| *k >= KEYCODE_OFFSET) - { - let scancode = (keycode - KEYCODE_OFFSET) as u32; - let keysym = wt.xconn.keycode_to_keysym(keycode); - let virtual_keycode = events::keysym_to_element(keysym as c_uint); - - if let Some(modifier) = mod_keymap.get_modifier(keycode as ffi::KeyCode) { - device_mod_state.key_event( - ElementState::Pressed, - keycode as ffi::KeyCode, - modifier, + let old_focus_count = seat.kb_focus.is_some() as u8 + seat.ptr_focus.is_some() as u8; + let seat_focus = match component { + KbFocus => &mut seat.kb_focus, + PtrFocus => &mut seat.ptr_focus, + }; + if *seat_focus == focus { + return; + } + if let Some(new) = focus { + if !Self::window_exists(wt, new) { + if seat_focus.is_none() { + return; + } + focus = None; + } + } + if seat_focus.is_some() != focus.is_some() { + let new_focus_count = if focus.is_some() { + old_focus_count + 1 + } else { + old_focus_count - 1 + }; + if (new_focus_count == 0) != (old_focus_count == 0) { + let state_details = if new_focus_count == 0 { + 0 + } else { + ffi::XCB_XKB_STATE_PART_MODIFIER_STATE as _ + }; + let details = ffi::xcb_xkb_select_events_details_t { + affect_state: ffi::XCB_XKB_STATE_PART_MODIFIER_STATE as _, + state_details, + ..Default::default() + }; + let pending = wt.xconn.select_xkb_event_details( + seat.keyboard, + ffi::XCB_XKB_EVENT_TYPE_STATE_NOTIFY, + &details, ); + if let Err(e) = wt.xconn.check_pending1(pending) { + const MAX_ERRORS: usize = 5; + if seat.num_errors < MAX_ERRORS { + seat.num_errors += 1; + log::warn!("Could not change XKB selected events: {}", e); + if seat.num_errors == MAX_ERRORS { + log::warn!("Future warnings of this kind will be suppressed"); + } + } + } } - - #[allow(deprecated)] + } + let device_id = mkdid(seat.keyboard); + if let Some(focus) = *seat_focus { + let event = match component { + KbFocus => WindowEvent::Focused(false), + PtrFocus => WindowEvent::CursorLeft { device_id }, + }; callback(Event::WindowEvent { - window_id, - event: WindowEvent::KeyboardInput { - device_id, - input: KeyboardInput { - scancode, - state, - virtual_keycode, - modifiers, - }, - is_synthetic: true, - }, + window_id: mkwid(focus), + event, }); } + *seat_focus = focus; + if let Some(focus) = *seat_focus { + let event = match component { + KbFocus => WindowEvent::Focused(true), + PtrFocus => WindowEvent::CursorEntered { device_id }, + }; + callback(Event::WindowEvent { + window_id: mkwid(focus), + event, + }); + callback(Event::WindowEvent { + window_id: mkwid(focus), + event: WindowEvent::ModifiersChanged(seat.current_modifiers), + }); + } + } + + fn update_seat_kb_xi( + seat: &mut Seat, + mods: &ffi::xcb_input_modifier_info_t, + group: &ffi::xcb_input_group_info_t, + callback: &mut F, + ) where + F: FnMut(Event<'_, T>), + { + Self::update_seat_kb( + seat, + mods.base, + mods.latched, + mods.locked, + group.base as u32, + group.latched as u32, + group.locked as u32, + callback, + ); + } + + fn update_seat_kb( + seat: &mut Seat, + mods_depressed: u32, + mods_latched: u32, + mods_locked: u32, + depressed_group: u32, + latched_group: u32, + locked_group: u32, + callback: &mut F, + ) where + F: FnMut(Event<'_, T>), + { + seat.kb_state.update_state( + mods_depressed, + mods_latched, + mods_locked, + depressed_group, + latched_group, + locked_group, + ); + Self::modifiers_changed(seat, callback); + } + + fn modifiers_changed(seat: &mut Seat, callback: &mut F) + where + F: FnMut(Event<'_, T>), + { + let new = seat.kb_state.mods_state(); + if seat.current_modifiers != new { + seat.current_modifiers = new; + let targets = [seat.kb_focus, seat.ptr_focus]; + let targets = if seat.kb_focus == seat.ptr_focus { + &targets[..1] + } else { + &targets[..] + }; + for target in targets { + if let Some(target) = target { + callback(Event::WindowEvent { + window_id: mkwid(*target), + event: WindowEvent::ModifiersChanged(new), + }); + } + } + } } } @@ -1298,3 +1333,26 @@ fn is_first_touch(first: &mut Option, num: &mut u32, id: u64, phase: TouchP *first == Some(id) } + +fn find_seat(seats: &mut [Seat], kb: ffi::xcb_input_device_id_t) -> Option<&mut Seat> { + seats.iter_mut().find(|s| s.keyboard == kb) +} + +fn find_seat_by_pointer( + seats: &mut [Seat], + pointer: ffi::xcb_input_device_id_t, +) -> Option<&mut Seat> { + seats.iter_mut().find(|s| s.pointer == pointer) +} + +fn mask_to_axis_ids(mask: &[u32]) -> Vec { + let mut axis_ids = vec![]; + for i in 0..mask.len() { + for j in 0..32 { + if (mask[i] >> j) & 1 == 1 { + axis_ids.push((i * 32 + j) as u16); + } + } + } + axis_ids +} diff --git a/src/platform_impl/linux/x11/events.rs b/src/platform_impl/linux/x11/events.rs deleted file mode 100644 index 0c02ca0c82..0000000000 --- a/src/platform_impl/linux/x11/events.rs +++ /dev/null @@ -1,1008 +0,0 @@ -use super::ffi; -use crate::event::VirtualKeyCode; -use libc; - -pub fn keysym_to_element(keysym: libc::c_uint) -> Option { - Some(match keysym { - ffi::XK_BackSpace => VirtualKeyCode::Back, - ffi::XK_Tab => VirtualKeyCode::Tab, - //ffi::XK_Linefeed => VirtualKeyCode::Linefeed, - //ffi::XK_Clear => VirtualKeyCode::Clear, - ffi::XK_Return => VirtualKeyCode::Return, - //ffi::XK_Pause => VirtualKeyCode::Pause, - //ffi::XK_Scroll_Lock => VirtualKeyCode::Scroll_lock, - //ffi::XK_Sys_Req => VirtualKeyCode::Sys_req, - ffi::XK_Escape => VirtualKeyCode::Escape, - ffi::XK_Delete => VirtualKeyCode::Delete, - ffi::XK_Multi_key => VirtualKeyCode::Compose, - //ffi::XK_Kanji => VirtualKeyCode::Kanji, - //ffi::XK_Muhenkan => VirtualKeyCode::Muhenkan, - //ffi::XK_Henkan_Mode => VirtualKeyCode::Henkan_mode, - //ffi::XK_Henkan => VirtualKeyCode::Henkan, - //ffi::XK_Romaji => VirtualKeyCode::Romaji, - //ffi::XK_Hiragana => VirtualKeyCode::Hiragana, - //ffi::XK_Katakana => VirtualKeyCode::Katakana, - //ffi::XK_Hiragana_Katakana => VirtualKeyCode::Hiragana_katakana, - //ffi::XK_Zenkaku => VirtualKeyCode::Zenkaku, - //ffi::XK_Hankaku => VirtualKeyCode::Hankaku, - //ffi::XK_Zenkaku_Hankaku => VirtualKeyCode::Zenkaku_hankaku, - //ffi::XK_Touroku => VirtualKeyCode::Touroku, - //ffi::XK_Massyo => VirtualKeyCode::Massyo, - //ffi::XK_Kana_Lock => VirtualKeyCode::Kana_lock, - //ffi::XK_Kana_Shift => VirtualKeyCode::Kana_shift, - //ffi::XK_Eisu_Shift => VirtualKeyCode::Eisu_shift, - //ffi::XK_Eisu_toggle => VirtualKeyCode::Eisu_toggle, - ffi::XK_Home => VirtualKeyCode::Home, - ffi::XK_Left => VirtualKeyCode::Left, - ffi::XK_Up => VirtualKeyCode::Up, - ffi::XK_Right => VirtualKeyCode::Right, - ffi::XK_Down => VirtualKeyCode::Down, - //ffi::XK_Prior => VirtualKeyCode::Prior, - ffi::XK_Page_Up => VirtualKeyCode::PageUp, - //ffi::XK_Next => VirtualKeyCode::Next, - ffi::XK_Page_Down => VirtualKeyCode::PageDown, - ffi::XK_End => VirtualKeyCode::End, - //ffi::XK_Begin => VirtualKeyCode::Begin, - //ffi::XK_Win_L => VirtualKeyCode::Win_l, - //ffi::XK_Win_R => VirtualKeyCode::Win_r, - //ffi::XK_App => VirtualKeyCode::App, - //ffi::XK_Select => VirtualKeyCode::Select, - //ffi::XK_Print => VirtualKeyCode::Print, - //ffi::XK_Execute => VirtualKeyCode::Execute, - ffi::XK_Insert => VirtualKeyCode::Insert, - //ffi::XK_Undo => VirtualKeyCode::Undo, - //ffi::XK_Redo => VirtualKeyCode::Redo, - //ffi::XK_Menu => VirtualKeyCode::Menu, - //ffi::XK_Find => VirtualKeyCode::Find, - //ffi::XK_Cancel => VirtualKeyCode::Cancel, - //ffi::XK_Help => VirtualKeyCode::Help, - //ffi::XK_Break => VirtualKeyCode::Break, - //ffi::XK_Mode_switch => VirtualKeyCode::Mode_switch, - //ffi::XK_script_switch => VirtualKeyCode::Script_switch, - //ffi::XK_Num_Lock => VirtualKeyCode::Num_lock, - //ffi::XK_KP_Space => VirtualKeyCode::Kp_space, - //ffi::XK_KP_Tab => VirtualKeyCode::Kp_tab, - //ffi::XK_KP_Enter => VirtualKeyCode::Kp_enter, - //ffi::XK_KP_F1 => VirtualKeyCode::Kp_f1, - //ffi::XK_KP_F2 => VirtualKeyCode::Kp_f2, - //ffi::XK_KP_F3 => VirtualKeyCode::Kp_f3, - //ffi::XK_KP_F4 => VirtualKeyCode::Kp_f4, - ffi::XK_KP_Home => VirtualKeyCode::Home, - ffi::XK_KP_Left => VirtualKeyCode::Left, - ffi::XK_KP_Up => VirtualKeyCode::Up, - ffi::XK_KP_Right => VirtualKeyCode::Right, - ffi::XK_KP_Down => VirtualKeyCode::Down, - //ffi::XK_KP_Prior => VirtualKeyCode::Kp_prior, - ffi::XK_KP_Page_Up => VirtualKeyCode::PageUp, - //ffi::XK_KP_Next => VirtualKeyCode::Kp_next, - ffi::XK_KP_Page_Down => VirtualKeyCode::PageDown, - ffi::XK_KP_End => VirtualKeyCode::End, - //ffi::XK_KP_Begin => VirtualKeyCode::Kp_begin, - ffi::XK_KP_Insert => VirtualKeyCode::Insert, - ffi::XK_KP_Delete => VirtualKeyCode::Delete, - ffi::XK_KP_Equal => VirtualKeyCode::NumpadEquals, - ffi::XK_KP_Multiply => VirtualKeyCode::NumpadMultiply, - ffi::XK_KP_Add => VirtualKeyCode::NumpadAdd, - //ffi::XK_KP_Separator => VirtualKeyCode::Kp_separator, - ffi::XK_KP_Subtract => VirtualKeyCode::NumpadSubtract, - ffi::XK_KP_Decimal => VirtualKeyCode::NumpadDecimal, - ffi::XK_KP_Divide => VirtualKeyCode::NumpadDivide, - ffi::XK_KP_0 => VirtualKeyCode::Numpad0, - ffi::XK_KP_1 => VirtualKeyCode::Numpad1, - ffi::XK_KP_2 => VirtualKeyCode::Numpad2, - ffi::XK_KP_3 => VirtualKeyCode::Numpad3, - ffi::XK_KP_4 => VirtualKeyCode::Numpad4, - ffi::XK_KP_5 => VirtualKeyCode::Numpad5, - ffi::XK_KP_6 => VirtualKeyCode::Numpad6, - ffi::XK_KP_7 => VirtualKeyCode::Numpad7, - ffi::XK_KP_8 => VirtualKeyCode::Numpad8, - ffi::XK_KP_9 => VirtualKeyCode::Numpad9, - ffi::XK_F1 => VirtualKeyCode::F1, - ffi::XK_F2 => VirtualKeyCode::F2, - ffi::XK_F3 => VirtualKeyCode::F3, - ffi::XK_F4 => VirtualKeyCode::F4, - ffi::XK_F5 => VirtualKeyCode::F5, - ffi::XK_F6 => VirtualKeyCode::F6, - ffi::XK_F7 => VirtualKeyCode::F7, - ffi::XK_F8 => VirtualKeyCode::F8, - ffi::XK_F9 => VirtualKeyCode::F9, - ffi::XK_F10 => VirtualKeyCode::F10, - ffi::XK_F11 => VirtualKeyCode::F11, - //ffi::XK_L1 => VirtualKeyCode::L1, - ffi::XK_F12 => VirtualKeyCode::F12, - //ffi::XK_L2 => VirtualKeyCode::L2, - ffi::XK_F13 => VirtualKeyCode::F13, - //ffi::XK_L3 => VirtualKeyCode::L3, - ffi::XK_F14 => VirtualKeyCode::F14, - //ffi::XK_L4 => VirtualKeyCode::L4, - ffi::XK_F15 => VirtualKeyCode::F15, - //ffi::XK_L5 => VirtualKeyCode::L5, - ffi::XK_F16 => VirtualKeyCode::F16, - //ffi::XK_L6 => VirtualKeyCode::L6, - ffi::XK_F17 => VirtualKeyCode::F17, - //ffi::XK_L7 => VirtualKeyCode::L7, - ffi::XK_F18 => VirtualKeyCode::F18, - //ffi::XK_L8 => VirtualKeyCode::L8, - ffi::XK_F19 => VirtualKeyCode::F19, - //ffi::XK_L9 => VirtualKeyCode::L9, - ffi::XK_F20 => VirtualKeyCode::F20, - //ffi::XK_L10 => VirtualKeyCode::L10, - ffi::XK_F21 => VirtualKeyCode::F21, - //ffi::XK_R1 => VirtualKeyCode::R1, - ffi::XK_F22 => VirtualKeyCode::F22, - //ffi::XK_R2 => VirtualKeyCode::R2, - ffi::XK_F23 => VirtualKeyCode::F23, - //ffi::XK_R3 => VirtualKeyCode::R3, - ffi::XK_F24 => VirtualKeyCode::F24, - //ffi::XK_R4 => VirtualKeyCode::R4, - //ffi::XK_F25 => VirtualKeyCode::F25, - //ffi::XK_R5 => VirtualKeyCode::R5, - //ffi::XK_F26 => VirtualKeyCode::F26, - //ffi::XK_R6 => VirtualKeyCode::R6, - //ffi::XK_F27 => VirtualKeyCode::F27, - //ffi::XK_R7 => VirtualKeyCode::R7, - //ffi::XK_F28 => VirtualKeyCode::F28, - //ffi::XK_R8 => VirtualKeyCode::R8, - //ffi::XK_F29 => VirtualKeyCode::F29, - //ffi::XK_R9 => VirtualKeyCode::R9, - //ffi::XK_F30 => VirtualKeyCode::F30, - //ffi::XK_R10 => VirtualKeyCode::R10, - //ffi::XK_F31 => VirtualKeyCode::F31, - //ffi::XK_R11 => VirtualKeyCode::R11, - //ffi::XK_F32 => VirtualKeyCode::F32, - //ffi::XK_R12 => VirtualKeyCode::R12, - //ffi::XK_F33 => VirtualKeyCode::F33, - //ffi::XK_R13 => VirtualKeyCode::R13, - //ffi::XK_F34 => VirtualKeyCode::F34, - //ffi::XK_R14 => VirtualKeyCode::R14, - //ffi::XK_F35 => VirtualKeyCode::F35, - //ffi::XK_R15 => VirtualKeyCode::R15, - ffi::XK_Shift_L => VirtualKeyCode::LShift, - ffi::XK_Shift_R => VirtualKeyCode::RShift, - ffi::XK_Control_L => VirtualKeyCode::LControl, - ffi::XK_Control_R => VirtualKeyCode::RControl, - //ffi::XK_Caps_Lock => VirtualKeyCode::Caps_lock, - //ffi::XK_Shift_Lock => VirtualKeyCode::Shift_lock, - //ffi::XK_Meta_L => VirtualKeyCode::Meta_l, - //ffi::XK_Meta_R => VirtualKeyCode::Meta_r, - ffi::XK_Alt_L => VirtualKeyCode::LAlt, - ffi::XK_Alt_R => VirtualKeyCode::RAlt, - //ffi::XK_Super_L => VirtualKeyCode::Super_l, - //ffi::XK_Super_R => VirtualKeyCode::Super_r, - //ffi::XK_Hyper_L => VirtualKeyCode::Hyper_l, - //ffi::XK_Hyper_R => VirtualKeyCode::Hyper_r, - ffi::XK_ISO_Left_Tab => VirtualKeyCode::Tab, - ffi::XK_space => VirtualKeyCode::Space, - //ffi::XK_exclam => VirtualKeyCode::Exclam, - //ffi::XK_quotedbl => VirtualKeyCode::Quotedbl, - //ffi::XK_numbersign => VirtualKeyCode::Numbersign, - //ffi::XK_dollar => VirtualKeyCode::Dollar, - //ffi::XK_percent => VirtualKeyCode::Percent, - //ffi::XK_ampersand => VirtualKeyCode::Ampersand, - ffi::XK_apostrophe => VirtualKeyCode::Apostrophe, - //ffi::XK_quoteright => VirtualKeyCode::Quoteright, - //ffi::XK_parenleft => VirtualKeyCode::Parenleft, - //ffi::XK_parenright => VirtualKeyCode::Parenright, - ffi::XK_asterisk => VirtualKeyCode::Asterisk, - ffi::XK_plus => VirtualKeyCode::Plus, - ffi::XK_comma => VirtualKeyCode::Comma, - ffi::XK_minus => VirtualKeyCode::Minus, - ffi::XK_period => VirtualKeyCode::Period, - ffi::XK_slash => VirtualKeyCode::Slash, - ffi::XK_0 => VirtualKeyCode::Key0, - ffi::XK_1 => VirtualKeyCode::Key1, - ffi::XK_2 => VirtualKeyCode::Key2, - ffi::XK_3 => VirtualKeyCode::Key3, - ffi::XK_4 => VirtualKeyCode::Key4, - ffi::XK_5 => VirtualKeyCode::Key5, - ffi::XK_6 => VirtualKeyCode::Key6, - ffi::XK_7 => VirtualKeyCode::Key7, - ffi::XK_8 => VirtualKeyCode::Key8, - ffi::XK_9 => VirtualKeyCode::Key9, - ffi::XK_colon => VirtualKeyCode::Colon, - ffi::XK_semicolon => VirtualKeyCode::Semicolon, - //ffi::XK_less => VirtualKeyCode::Less, - ffi::XK_equal => VirtualKeyCode::Equals, - //ffi::XK_greater => VirtualKeyCode::Greater, - //ffi::XK_question => VirtualKeyCode::Question, - ffi::XK_at => VirtualKeyCode::At, - ffi::XK_A => VirtualKeyCode::A, - ffi::XK_B => VirtualKeyCode::B, - ffi::XK_C => VirtualKeyCode::C, - ffi::XK_D => VirtualKeyCode::D, - ffi::XK_E => VirtualKeyCode::E, - ffi::XK_F => VirtualKeyCode::F, - ffi::XK_G => VirtualKeyCode::G, - ffi::XK_H => VirtualKeyCode::H, - ffi::XK_I => VirtualKeyCode::I, - ffi::XK_J => VirtualKeyCode::J, - ffi::XK_K => VirtualKeyCode::K, - ffi::XK_L => VirtualKeyCode::L, - ffi::XK_M => VirtualKeyCode::M, - ffi::XK_N => VirtualKeyCode::N, - ffi::XK_O => VirtualKeyCode::O, - ffi::XK_P => VirtualKeyCode::P, - ffi::XK_Q => VirtualKeyCode::Q, - ffi::XK_R => VirtualKeyCode::R, - ffi::XK_S => VirtualKeyCode::S, - ffi::XK_T => VirtualKeyCode::T, - ffi::XK_U => VirtualKeyCode::U, - ffi::XK_V => VirtualKeyCode::V, - ffi::XK_W => VirtualKeyCode::W, - ffi::XK_X => VirtualKeyCode::X, - ffi::XK_Y => VirtualKeyCode::Y, - ffi::XK_Z => VirtualKeyCode::Z, - ffi::XK_bracketleft => VirtualKeyCode::LBracket, - ffi::XK_backslash => VirtualKeyCode::Backslash, - ffi::XK_bracketright => VirtualKeyCode::RBracket, - //ffi::XK_asciicircum => VirtualKeyCode::Asciicircum, - //ffi::XK_underscore => VirtualKeyCode::Underscore, - ffi::XK_grave => VirtualKeyCode::Grave, - //ffi::XK_quoteleft => VirtualKeyCode::Quoteleft, - ffi::XK_a => VirtualKeyCode::A, - ffi::XK_b => VirtualKeyCode::B, - ffi::XK_c => VirtualKeyCode::C, - ffi::XK_d => VirtualKeyCode::D, - ffi::XK_e => VirtualKeyCode::E, - ffi::XK_f => VirtualKeyCode::F, - ffi::XK_g => VirtualKeyCode::G, - ffi::XK_h => VirtualKeyCode::H, - ffi::XK_i => VirtualKeyCode::I, - ffi::XK_j => VirtualKeyCode::J, - ffi::XK_k => VirtualKeyCode::K, - ffi::XK_l => VirtualKeyCode::L, - ffi::XK_m => VirtualKeyCode::M, - ffi::XK_n => VirtualKeyCode::N, - ffi::XK_o => VirtualKeyCode::O, - ffi::XK_p => VirtualKeyCode::P, - ffi::XK_q => VirtualKeyCode::Q, - ffi::XK_r => VirtualKeyCode::R, - ffi::XK_s => VirtualKeyCode::S, - ffi::XK_t => VirtualKeyCode::T, - ffi::XK_u => VirtualKeyCode::U, - ffi::XK_v => VirtualKeyCode::V, - ffi::XK_w => VirtualKeyCode::W, - ffi::XK_x => VirtualKeyCode::X, - ffi::XK_y => VirtualKeyCode::Y, - ffi::XK_z => VirtualKeyCode::Z, - //ffi::XK_braceleft => VirtualKeyCode::Braceleft, - //ffi::XK_bar => VirtualKeyCode::Bar, - //ffi::XK_braceright => VirtualKeyCode::Braceright, - //ffi::XK_asciitilde => VirtualKeyCode::Asciitilde, - //ffi::XK_nobreakspace => VirtualKeyCode::Nobreakspace, - //ffi::XK_exclamdown => VirtualKeyCode::Exclamdown, - //ffi::XK_cent => VirtualKeyCode::Cent, - //ffi::XK_sterling => VirtualKeyCode::Sterling, - //ffi::XK_currency => VirtualKeyCode::Currency, - //ffi::XK_yen => VirtualKeyCode::Yen, - //ffi::XK_brokenbar => VirtualKeyCode::Brokenbar, - //ffi::XK_section => VirtualKeyCode::Section, - //ffi::XK_diaeresis => VirtualKeyCode::Diaeresis, - //ffi::XK_copyright => VirtualKeyCode::Copyright, - //ffi::XK_ordfeminine => VirtualKeyCode::Ordfeminine, - //ffi::XK_guillemotleft => VirtualKeyCode::Guillemotleft, - //ffi::XK_notsign => VirtualKeyCode::Notsign, - //ffi::XK_hyphen => VirtualKeyCode::Hyphen, - //ffi::XK_registered => VirtualKeyCode::Registered, - //ffi::XK_macron => VirtualKeyCode::Macron, - //ffi::XK_degree => VirtualKeyCode::Degree, - //ffi::XK_plusminus => VirtualKeyCode::Plusminus, - //ffi::XK_twosuperior => VirtualKeyCode::Twosuperior, - //ffi::XK_threesuperior => VirtualKeyCode::Threesuperior, - //ffi::XK_acute => VirtualKeyCode::Acute, - //ffi::XK_mu => VirtualKeyCode::Mu, - //ffi::XK_paragraph => VirtualKeyCode::Paragraph, - //ffi::XK_periodcentered => VirtualKeyCode::Periodcentered, - //ffi::XK_cedilla => VirtualKeyCode::Cedilla, - //ffi::XK_onesuperior => VirtualKeyCode::Onesuperior, - //ffi::XK_masculine => VirtualKeyCode::Masculine, - //ffi::XK_guillemotright => VirtualKeyCode::Guillemotright, - //ffi::XK_onequarter => VirtualKeyCode::Onequarter, - //ffi::XK_onehalf => VirtualKeyCode::Onehalf, - //ffi::XK_threequarters => VirtualKeyCode::Threequarters, - //ffi::XK_questiondown => VirtualKeyCode::Questiondown, - //ffi::XK_Agrave => VirtualKeyCode::Agrave, - //ffi::XK_Aacute => VirtualKeyCode::Aacute, - //ffi::XK_Acircumflex => VirtualKeyCode::Acircumflex, - //ffi::XK_Atilde => VirtualKeyCode::Atilde, - //ffi::XK_Adiaeresis => VirtualKeyCode::Adiaeresis, - //ffi::XK_Aring => VirtualKeyCode::Aring, - //ffi::XK_AE => VirtualKeyCode::Ae, - //ffi::XK_Ccedilla => VirtualKeyCode::Ccedilla, - //ffi::XK_Egrave => VirtualKeyCode::Egrave, - //ffi::XK_Eacute => VirtualKeyCode::Eacute, - //ffi::XK_Ecircumflex => VirtualKeyCode::Ecircumflex, - //ffi::XK_Ediaeresis => VirtualKeyCode::Ediaeresis, - //ffi::XK_Igrave => VirtualKeyCode::Igrave, - //ffi::XK_Iacute => VirtualKeyCode::Iacute, - //ffi::XK_Icircumflex => VirtualKeyCode::Icircumflex, - //ffi::XK_Idiaeresis => VirtualKeyCode::Idiaeresis, - //ffi::XK_ETH => VirtualKeyCode::Eth, - //ffi::XK_Eth => VirtualKeyCode::Eth, - //ffi::XK_Ntilde => VirtualKeyCode::Ntilde, - //ffi::XK_Ograve => VirtualKeyCode::Ograve, - //ffi::XK_Oacute => VirtualKeyCode::Oacute, - //ffi::XK_Ocircumflex => VirtualKeyCode::Ocircumflex, - //ffi::XK_Otilde => VirtualKeyCode::Otilde, - //ffi::XK_Odiaeresis => VirtualKeyCode::Odiaeresis, - //ffi::XK_multiply => VirtualKeyCode::Multiply, - //ffi::XK_Ooblique => VirtualKeyCode::Ooblique, - //ffi::XK_Ugrave => VirtualKeyCode::Ugrave, - //ffi::XK_Uacute => VirtualKeyCode::Uacute, - //ffi::XK_Ucircumflex => VirtualKeyCode::Ucircumflex, - //ffi::XK_Udiaeresis => VirtualKeyCode::Udiaeresis, - //ffi::XK_Yacute => VirtualKeyCode::Yacute, - //ffi::XK_THORN => VirtualKeyCode::Thorn, - //ffi::XK_Thorn => VirtualKeyCode::Thorn, - //ffi::XK_ssharp => VirtualKeyCode::Ssharp, - //ffi::XK_agrave => VirtualKeyCode::Agrave, - //ffi::XK_aacute => VirtualKeyCode::Aacute, - //ffi::XK_acircumflex => VirtualKeyCode::Acircumflex, - //ffi::XK_atilde => VirtualKeyCode::Atilde, - //ffi::XK_adiaeresis => VirtualKeyCode::Adiaeresis, - //ffi::XK_aring => VirtualKeyCode::Aring, - //ffi::XK_ae => VirtualKeyCode::Ae, - //ffi::XK_ccedilla => VirtualKeyCode::Ccedilla, - //ffi::XK_egrave => VirtualKeyCode::Egrave, - //ffi::XK_eacute => VirtualKeyCode::Eacute, - //ffi::XK_ecircumflex => VirtualKeyCode::Ecircumflex, - //ffi::XK_ediaeresis => VirtualKeyCode::Ediaeresis, - //ffi::XK_igrave => VirtualKeyCode::Igrave, - //ffi::XK_iacute => VirtualKeyCode::Iacute, - //ffi::XK_icircumflex => VirtualKeyCode::Icircumflex, - //ffi::XK_idiaeresis => VirtualKeyCode::Idiaeresis, - //ffi::XK_eth => VirtualKeyCode::Eth, - //ffi::XK_ntilde => VirtualKeyCode::Ntilde, - //ffi::XK_ograve => VirtualKeyCode::Ograve, - //ffi::XK_oacute => VirtualKeyCode::Oacute, - //ffi::XK_ocircumflex => VirtualKeyCode::Ocircumflex, - //ffi::XK_otilde => VirtualKeyCode::Otilde, - //ffi::XK_odiaeresis => VirtualKeyCode::Odiaeresis, - //ffi::XK_division => VirtualKeyCode::Division, - //ffi::XK_oslash => VirtualKeyCode::Oslash, - //ffi::XK_ugrave => VirtualKeyCode::Ugrave, - //ffi::XK_uacute => VirtualKeyCode::Uacute, - //ffi::XK_ucircumflex => VirtualKeyCode::Ucircumflex, - //ffi::XK_udiaeresis => VirtualKeyCode::Udiaeresis, - //ffi::XK_yacute => VirtualKeyCode::Yacute, - //ffi::XK_thorn => VirtualKeyCode::Thorn, - //ffi::XK_ydiaeresis => VirtualKeyCode::Ydiaeresis, - //ffi::XK_Aogonek => VirtualKeyCode::Aogonek, - //ffi::XK_breve => VirtualKeyCode::Breve, - //ffi::XK_Lstroke => VirtualKeyCode::Lstroke, - //ffi::XK_Lcaron => VirtualKeyCode::Lcaron, - //ffi::XK_Sacute => VirtualKeyCode::Sacute, - //ffi::XK_Scaron => VirtualKeyCode::Scaron, - //ffi::XK_Scedilla => VirtualKeyCode::Scedilla, - //ffi::XK_Tcaron => VirtualKeyCode::Tcaron, - //ffi::XK_Zacute => VirtualKeyCode::Zacute, - //ffi::XK_Zcaron => VirtualKeyCode::Zcaron, - //ffi::XK_Zabovedot => VirtualKeyCode::Zabovedot, - //ffi::XK_aogonek => VirtualKeyCode::Aogonek, - //ffi::XK_ogonek => VirtualKeyCode::Ogonek, - //ffi::XK_lstroke => VirtualKeyCode::Lstroke, - //ffi::XK_lcaron => VirtualKeyCode::Lcaron, - //ffi::XK_sacute => VirtualKeyCode::Sacute, - //ffi::XK_caron => VirtualKeyCode::Caron, - //ffi::XK_scaron => VirtualKeyCode::Scaron, - //ffi::XK_scedilla => VirtualKeyCode::Scedilla, - //ffi::XK_tcaron => VirtualKeyCode::Tcaron, - //ffi::XK_zacute => VirtualKeyCode::Zacute, - //ffi::XK_doubleacute => VirtualKeyCode::Doubleacute, - //ffi::XK_zcaron => VirtualKeyCode::Zcaron, - //ffi::XK_zabovedot => VirtualKeyCode::Zabovedot, - //ffi::XK_Racute => VirtualKeyCode::Racute, - //ffi::XK_Abreve => VirtualKeyCode::Abreve, - //ffi::XK_Lacute => VirtualKeyCode::Lacute, - //ffi::XK_Cacute => VirtualKeyCode::Cacute, - //ffi::XK_Ccaron => VirtualKeyCode::Ccaron, - //ffi::XK_Eogonek => VirtualKeyCode::Eogonek, - //ffi::XK_Ecaron => VirtualKeyCode::Ecaron, - //ffi::XK_Dcaron => VirtualKeyCode::Dcaron, - //ffi::XK_Dstroke => VirtualKeyCode::Dstroke, - //ffi::XK_Nacute => VirtualKeyCode::Nacute, - //ffi::XK_Ncaron => VirtualKeyCode::Ncaron, - //ffi::XK_Odoubleacute => VirtualKeyCode::Odoubleacute, - //ffi::XK_Rcaron => VirtualKeyCode::Rcaron, - //ffi::XK_Uring => VirtualKeyCode::Uring, - //ffi::XK_Udoubleacute => VirtualKeyCode::Udoubleacute, - //ffi::XK_Tcedilla => VirtualKeyCode::Tcedilla, - //ffi::XK_racute => VirtualKeyCode::Racute, - //ffi::XK_abreve => VirtualKeyCode::Abreve, - //ffi::XK_lacute => VirtualKeyCode::Lacute, - //ffi::XK_cacute => VirtualKeyCode::Cacute, - //ffi::XK_ccaron => VirtualKeyCode::Ccaron, - //ffi::XK_eogonek => VirtualKeyCode::Eogonek, - //ffi::XK_ecaron => VirtualKeyCode::Ecaron, - //ffi::XK_dcaron => VirtualKeyCode::Dcaron, - //ffi::XK_dstroke => VirtualKeyCode::Dstroke, - //ffi::XK_nacute => VirtualKeyCode::Nacute, - //ffi::XK_ncaron => VirtualKeyCode::Ncaron, - //ffi::XK_odoubleacute => VirtualKeyCode::Odoubleacute, - //ffi::XK_udoubleacute => VirtualKeyCode::Udoubleacute, - //ffi::XK_rcaron => VirtualKeyCode::Rcaron, - //ffi::XK_uring => VirtualKeyCode::Uring, - //ffi::XK_tcedilla => VirtualKeyCode::Tcedilla, - //ffi::XK_abovedot => VirtualKeyCode::Abovedot, - //ffi::XK_Hstroke => VirtualKeyCode::Hstroke, - //ffi::XK_Hcircumflex => VirtualKeyCode::Hcircumflex, - //ffi::XK_Iabovedot => VirtualKeyCode::Iabovedot, - //ffi::XK_Gbreve => VirtualKeyCode::Gbreve, - //ffi::XK_Jcircumflex => VirtualKeyCode::Jcircumflex, - //ffi::XK_hstroke => VirtualKeyCode::Hstroke, - //ffi::XK_hcircumflex => VirtualKeyCode::Hcircumflex, - //ffi::XK_idotless => VirtualKeyCode::Idotless, - //ffi::XK_gbreve => VirtualKeyCode::Gbreve, - //ffi::XK_jcircumflex => VirtualKeyCode::Jcircumflex, - //ffi::XK_Cabovedot => VirtualKeyCode::Cabovedot, - //ffi::XK_Ccircumflex => VirtualKeyCode::Ccircumflex, - //ffi::XK_Gabovedot => VirtualKeyCode::Gabovedot, - //ffi::XK_Gcircumflex => VirtualKeyCode::Gcircumflex, - //ffi::XK_Ubreve => VirtualKeyCode::Ubreve, - //ffi::XK_Scircumflex => VirtualKeyCode::Scircumflex, - //ffi::XK_cabovedot => VirtualKeyCode::Cabovedot, - //ffi::XK_ccircumflex => VirtualKeyCode::Ccircumflex, - //ffi::XK_gabovedot => VirtualKeyCode::Gabovedot, - //ffi::XK_gcircumflex => VirtualKeyCode::Gcircumflex, - //ffi::XK_ubreve => VirtualKeyCode::Ubreve, - //ffi::XK_scircumflex => VirtualKeyCode::Scircumflex, - //ffi::XK_kra => VirtualKeyCode::Kra, - //ffi::XK_kappa => VirtualKeyCode::Kappa, - //ffi::XK_Rcedilla => VirtualKeyCode::Rcedilla, - //ffi::XK_Itilde => VirtualKeyCode::Itilde, - //ffi::XK_Lcedilla => VirtualKeyCode::Lcedilla, - //ffi::XK_Emacron => VirtualKeyCode::Emacron, - //ffi::XK_Gcedilla => VirtualKeyCode::Gcedilla, - //ffi::XK_Tslash => VirtualKeyCode::Tslash, - //ffi::XK_rcedilla => VirtualKeyCode::Rcedilla, - //ffi::XK_itilde => VirtualKeyCode::Itilde, - //ffi::XK_lcedilla => VirtualKeyCode::Lcedilla, - //ffi::XK_emacron => VirtualKeyCode::Emacron, - //ffi::XK_gcedilla => VirtualKeyCode::Gcedilla, - //ffi::XK_tslash => VirtualKeyCode::Tslash, - //ffi::XK_ENG => VirtualKeyCode::Eng, - //ffi::XK_eng => VirtualKeyCode::Eng, - //ffi::XK_Amacron => VirtualKeyCode::Amacron, - //ffi::XK_Iogonek => VirtualKeyCode::Iogonek, - //ffi::XK_Eabovedot => VirtualKeyCode::Eabovedot, - //ffi::XK_Imacron => VirtualKeyCode::Imacron, - //ffi::XK_Ncedilla => VirtualKeyCode::Ncedilla, - //ffi::XK_Omacron => VirtualKeyCode::Omacron, - //ffi::XK_Kcedilla => VirtualKeyCode::Kcedilla, - //ffi::XK_Uogonek => VirtualKeyCode::Uogonek, - //ffi::XK_Utilde => VirtualKeyCode::Utilde, - //ffi::XK_Umacron => VirtualKeyCode::Umacron, - //ffi::XK_amacron => VirtualKeyCode::Amacron, - //ffi::XK_iogonek => VirtualKeyCode::Iogonek, - //ffi::XK_eabovedot => VirtualKeyCode::Eabovedot, - //ffi::XK_imacron => VirtualKeyCode::Imacron, - //ffi::XK_ncedilla => VirtualKeyCode::Ncedilla, - //ffi::XK_omacron => VirtualKeyCode::Omacron, - //ffi::XK_kcedilla => VirtualKeyCode::Kcedilla, - //ffi::XK_uogonek => VirtualKeyCode::Uogonek, - //ffi::XK_utilde => VirtualKeyCode::Utilde, - //ffi::XK_umacron => VirtualKeyCode::Umacron, - //ffi::XK_overline => VirtualKeyCode::Overline, - //ffi::XK_kana_fullstop => VirtualKeyCode::Kana_fullstop, - //ffi::XK_kana_openingbracket => VirtualKeyCode::Kana_openingbracket, - //ffi::XK_kana_closingbracket => VirtualKeyCode::Kana_closingbracket, - //ffi::XK_kana_comma => VirtualKeyCode::Kana_comma, - //ffi::XK_kana_conjunctive => VirtualKeyCode::Kana_conjunctive, - //ffi::XK_kana_middledot => VirtualKeyCode::Kana_middledot, - //ffi::XK_kana_WO => VirtualKeyCode::Kana_wo, - //ffi::XK_kana_a => VirtualKeyCode::Kana_a, - //ffi::XK_kana_i => VirtualKeyCode::Kana_i, - //ffi::XK_kana_u => VirtualKeyCode::Kana_u, - //ffi::XK_kana_e => VirtualKeyCode::Kana_e, - //ffi::XK_kana_o => VirtualKeyCode::Kana_o, - //ffi::XK_kana_ya => VirtualKeyCode::Kana_ya, - //ffi::XK_kana_yu => VirtualKeyCode::Kana_yu, - //ffi::XK_kana_yo => VirtualKeyCode::Kana_yo, - //ffi::XK_kana_tsu => VirtualKeyCode::Kana_tsu, - //ffi::XK_kana_tu => VirtualKeyCode::Kana_tu, - //ffi::XK_prolongedsound => VirtualKeyCode::Prolongedsound, - //ffi::XK_kana_A => VirtualKeyCode::Kana_a, - //ffi::XK_kana_I => VirtualKeyCode::Kana_i, - //ffi::XK_kana_U => VirtualKeyCode::Kana_u, - //ffi::XK_kana_E => VirtualKeyCode::Kana_e, - //ffi::XK_kana_O => VirtualKeyCode::Kana_o, - //ffi::XK_kana_KA => VirtualKeyCode::Kana_ka, - //ffi::XK_kana_KI => VirtualKeyCode::Kana_ki, - //ffi::XK_kana_KU => VirtualKeyCode::Kana_ku, - //ffi::XK_kana_KE => VirtualKeyCode::Kana_ke, - //ffi::XK_kana_KO => VirtualKeyCode::Kana_ko, - //ffi::XK_kana_SA => VirtualKeyCode::Kana_sa, - //ffi::XK_kana_SHI => VirtualKeyCode::Kana_shi, - //ffi::XK_kana_SU => VirtualKeyCode::Kana_su, - //ffi::XK_kana_SE => VirtualKeyCode::Kana_se, - //ffi::XK_kana_SO => VirtualKeyCode::Kana_so, - //ffi::XK_kana_TA => VirtualKeyCode::Kana_ta, - //ffi::XK_kana_CHI => VirtualKeyCode::Kana_chi, - //ffi::XK_kana_TI => VirtualKeyCode::Kana_ti, - //ffi::XK_kana_TSU => VirtualKeyCode::Kana_tsu, - //ffi::XK_kana_TU => VirtualKeyCode::Kana_tu, - //ffi::XK_kana_TE => VirtualKeyCode::Kana_te, - //ffi::XK_kana_TO => VirtualKeyCode::Kana_to, - //ffi::XK_kana_NA => VirtualKeyCode::Kana_na, - //ffi::XK_kana_NI => VirtualKeyCode::Kana_ni, - //ffi::XK_kana_NU => VirtualKeyCode::Kana_nu, - //ffi::XK_kana_NE => VirtualKeyCode::Kana_ne, - //ffi::XK_kana_NO => VirtualKeyCode::Kana_no, - //ffi::XK_kana_HA => VirtualKeyCode::Kana_ha, - //ffi::XK_kana_HI => VirtualKeyCode::Kana_hi, - //ffi::XK_kana_FU => VirtualKeyCode::Kana_fu, - //ffi::XK_kana_HU => VirtualKeyCode::Kana_hu, - //ffi::XK_kana_HE => VirtualKeyCode::Kana_he, - //ffi::XK_kana_HO => VirtualKeyCode::Kana_ho, - //ffi::XK_kana_MA => VirtualKeyCode::Kana_ma, - //ffi::XK_kana_MI => VirtualKeyCode::Kana_mi, - //ffi::XK_kana_MU => VirtualKeyCode::Kana_mu, - //ffi::XK_kana_ME => VirtualKeyCode::Kana_me, - //ffi::XK_kana_MO => VirtualKeyCode::Kana_mo, - //ffi::XK_kana_YA => VirtualKeyCode::Kana_ya, - //ffi::XK_kana_YU => VirtualKeyCode::Kana_yu, - //ffi::XK_kana_YO => VirtualKeyCode::Kana_yo, - //ffi::XK_kana_RA => VirtualKeyCode::Kana_ra, - //ffi::XK_kana_RI => VirtualKeyCode::Kana_ri, - //ffi::XK_kana_RU => VirtualKeyCode::Kana_ru, - //ffi::XK_kana_RE => VirtualKeyCode::Kana_re, - //ffi::XK_kana_RO => VirtualKeyCode::Kana_ro, - //ffi::XK_kana_WA => VirtualKeyCode::Kana_wa, - //ffi::XK_kana_N => VirtualKeyCode::Kana_n, - //ffi::XK_voicedsound => VirtualKeyCode::Voicedsound, - //ffi::XK_semivoicedsound => VirtualKeyCode::Semivoicedsound, - //ffi::XK_kana_switch => VirtualKeyCode::Kana_switch, - //ffi::XK_Arabic_comma => VirtualKeyCode::Arabic_comma, - //ffi::XK_Arabic_semicolon => VirtualKeyCode::Arabic_semicolon, - //ffi::XK_Arabic_question_mark => VirtualKeyCode::Arabic_question_mark, - //ffi::XK_Arabic_hamza => VirtualKeyCode::Arabic_hamza, - //ffi::XK_Arabic_maddaonalef => VirtualKeyCode::Arabic_maddaonalef, - //ffi::XK_Arabic_hamzaonalef => VirtualKeyCode::Arabic_hamzaonalef, - //ffi::XK_Arabic_hamzaonwaw => VirtualKeyCode::Arabic_hamzaonwaw, - //ffi::XK_Arabic_hamzaunderalef => VirtualKeyCode::Arabic_hamzaunderalef, - //ffi::XK_Arabic_hamzaonyeh => VirtualKeyCode::Arabic_hamzaonyeh, - //ffi::XK_Arabic_alef => VirtualKeyCode::Arabic_alef, - //ffi::XK_Arabic_beh => VirtualKeyCode::Arabic_beh, - //ffi::XK_Arabic_tehmarbuta => VirtualKeyCode::Arabic_tehmarbuta, - //ffi::XK_Arabic_teh => VirtualKeyCode::Arabic_teh, - //ffi::XK_Arabic_theh => VirtualKeyCode::Arabic_theh, - //ffi::XK_Arabic_jeem => VirtualKeyCode::Arabic_jeem, - //ffi::XK_Arabic_hah => VirtualKeyCode::Arabic_hah, - //ffi::XK_Arabic_khah => VirtualKeyCode::Arabic_khah, - //ffi::XK_Arabic_dal => VirtualKeyCode::Arabic_dal, - //ffi::XK_Arabic_thal => VirtualKeyCode::Arabic_thal, - //ffi::XK_Arabic_ra => VirtualKeyCode::Arabic_ra, - //ffi::XK_Arabic_zain => VirtualKeyCode::Arabic_zain, - //ffi::XK_Arabic_seen => VirtualKeyCode::Arabic_seen, - //ffi::XK_Arabic_sheen => VirtualKeyCode::Arabic_sheen, - //ffi::XK_Arabic_sad => VirtualKeyCode::Arabic_sad, - //ffi::XK_Arabic_dad => VirtualKeyCode::Arabic_dad, - //ffi::XK_Arabic_tah => VirtualKeyCode::Arabic_tah, - //ffi::XK_Arabic_zah => VirtualKeyCode::Arabic_zah, - //ffi::XK_Arabic_ain => VirtualKeyCode::Arabic_ain, - //ffi::XK_Arabic_ghain => VirtualKeyCode::Arabic_ghain, - //ffi::XK_Arabic_tatweel => VirtualKeyCode::Arabic_tatweel, - //ffi::XK_Arabic_feh => VirtualKeyCode::Arabic_feh, - //ffi::XK_Arabic_qaf => VirtualKeyCode::Arabic_qaf, - //ffi::XK_Arabic_kaf => VirtualKeyCode::Arabic_kaf, - //ffi::XK_Arabic_lam => VirtualKeyCode::Arabic_lam, - //ffi::XK_Arabic_meem => VirtualKeyCode::Arabic_meem, - //ffi::XK_Arabic_noon => VirtualKeyCode::Arabic_noon, - //ffi::XK_Arabic_ha => VirtualKeyCode::Arabic_ha, - //ffi::XK_Arabic_heh => VirtualKeyCode::Arabic_heh, - //ffi::XK_Arabic_waw => VirtualKeyCode::Arabic_waw, - //ffi::XK_Arabic_alefmaksura => VirtualKeyCode::Arabic_alefmaksura, - //ffi::XK_Arabic_yeh => VirtualKeyCode::Arabic_yeh, - //ffi::XK_Arabic_fathatan => VirtualKeyCode::Arabic_fathatan, - //ffi::XK_Arabic_dammatan => VirtualKeyCode::Arabic_dammatan, - //ffi::XK_Arabic_kasratan => VirtualKeyCode::Arabic_kasratan, - //ffi::XK_Arabic_fatha => VirtualKeyCode::Arabic_fatha, - //ffi::XK_Arabic_damma => VirtualKeyCode::Arabic_damma, - //ffi::XK_Arabic_kasra => VirtualKeyCode::Arabic_kasra, - //ffi::XK_Arabic_shadda => VirtualKeyCode::Arabic_shadda, - //ffi::XK_Arabic_sukun => VirtualKeyCode::Arabic_sukun, - //ffi::XK_Arabic_switch => VirtualKeyCode::Arabic_switch, - //ffi::XK_Serbian_dje => VirtualKeyCode::Serbian_dje, - //ffi::XK_Macedonia_gje => VirtualKeyCode::Macedonia_gje, - //ffi::XK_Cyrillic_io => VirtualKeyCode::Cyrillic_io, - //ffi::XK_Ukrainian_ie => VirtualKeyCode::Ukrainian_ie, - //ffi::XK_Ukranian_je => VirtualKeyCode::Ukranian_je, - //ffi::XK_Macedonia_dse => VirtualKeyCode::Macedonia_dse, - //ffi::XK_Ukrainian_i => VirtualKeyCode::Ukrainian_i, - //ffi::XK_Ukranian_i => VirtualKeyCode::Ukranian_i, - //ffi::XK_Ukrainian_yi => VirtualKeyCode::Ukrainian_yi, - //ffi::XK_Ukranian_yi => VirtualKeyCode::Ukranian_yi, - //ffi::XK_Cyrillic_je => VirtualKeyCode::Cyrillic_je, - //ffi::XK_Serbian_je => VirtualKeyCode::Serbian_je, - //ffi::XK_Cyrillic_lje => VirtualKeyCode::Cyrillic_lje, - //ffi::XK_Serbian_lje => VirtualKeyCode::Serbian_lje, - //ffi::XK_Cyrillic_nje => VirtualKeyCode::Cyrillic_nje, - //ffi::XK_Serbian_nje => VirtualKeyCode::Serbian_nje, - //ffi::XK_Serbian_tshe => VirtualKeyCode::Serbian_tshe, - //ffi::XK_Macedonia_kje => VirtualKeyCode::Macedonia_kje, - //ffi::XK_Byelorussian_shortu => VirtualKeyCode::Byelorussian_shortu, - //ffi::XK_Cyrillic_dzhe => VirtualKeyCode::Cyrillic_dzhe, - //ffi::XK_Serbian_dze => VirtualKeyCode::Serbian_dze, - //ffi::XK_numerosign => VirtualKeyCode::Numerosign, - //ffi::XK_Serbian_DJE => VirtualKeyCode::Serbian_dje, - //ffi::XK_Macedonia_GJE => VirtualKeyCode::Macedonia_gje, - //ffi::XK_Cyrillic_IO => VirtualKeyCode::Cyrillic_io, - //ffi::XK_Ukrainian_IE => VirtualKeyCode::Ukrainian_ie, - //ffi::XK_Ukranian_JE => VirtualKeyCode::Ukranian_je, - //ffi::XK_Macedonia_DSE => VirtualKeyCode::Macedonia_dse, - //ffi::XK_Ukrainian_I => VirtualKeyCode::Ukrainian_i, - //ffi::XK_Ukranian_I => VirtualKeyCode::Ukranian_i, - //ffi::XK_Ukrainian_YI => VirtualKeyCode::Ukrainian_yi, - //ffi::XK_Ukranian_YI => VirtualKeyCode::Ukranian_yi, - //ffi::XK_Cyrillic_JE => VirtualKeyCode::Cyrillic_je, - //ffi::XK_Serbian_JE => VirtualKeyCode::Serbian_je, - //ffi::XK_Cyrillic_LJE => VirtualKeyCode::Cyrillic_lje, - //ffi::XK_Serbian_LJE => VirtualKeyCode::Serbian_lje, - //ffi::XK_Cyrillic_NJE => VirtualKeyCode::Cyrillic_nje, - //ffi::XK_Serbian_NJE => VirtualKeyCode::Serbian_nje, - //ffi::XK_Serbian_TSHE => VirtualKeyCode::Serbian_tshe, - //ffi::XK_Macedonia_KJE => VirtualKeyCode::Macedonia_kje, - //ffi::XK_Byelorussian_SHORTU => VirtualKeyCode::Byelorussian_shortu, - //ffi::XK_Cyrillic_DZHE => VirtualKeyCode::Cyrillic_dzhe, - //ffi::XK_Serbian_DZE => VirtualKeyCode::Serbian_dze, - //ffi::XK_Cyrillic_yu => VirtualKeyCode::Cyrillic_yu, - //ffi::XK_Cyrillic_a => VirtualKeyCode::Cyrillic_a, - //ffi::XK_Cyrillic_be => VirtualKeyCode::Cyrillic_be, - //ffi::XK_Cyrillic_tse => VirtualKeyCode::Cyrillic_tse, - //ffi::XK_Cyrillic_de => VirtualKeyCode::Cyrillic_de, - //ffi::XK_Cyrillic_ie => VirtualKeyCode::Cyrillic_ie, - //ffi::XK_Cyrillic_ef => VirtualKeyCode::Cyrillic_ef, - //ffi::XK_Cyrillic_ghe => VirtualKeyCode::Cyrillic_ghe, - //ffi::XK_Cyrillic_ha => VirtualKeyCode::Cyrillic_ha, - //ffi::XK_Cyrillic_i => VirtualKeyCode::Cyrillic_i, - //ffi::XK_Cyrillic_shorti => VirtualKeyCode::Cyrillic_shorti, - //ffi::XK_Cyrillic_ka => VirtualKeyCode::Cyrillic_ka, - //ffi::XK_Cyrillic_el => VirtualKeyCode::Cyrillic_el, - //ffi::XK_Cyrillic_em => VirtualKeyCode::Cyrillic_em, - //ffi::XK_Cyrillic_en => VirtualKeyCode::Cyrillic_en, - //ffi::XK_Cyrillic_o => VirtualKeyCode::Cyrillic_o, - //ffi::XK_Cyrillic_pe => VirtualKeyCode::Cyrillic_pe, - //ffi::XK_Cyrillic_ya => VirtualKeyCode::Cyrillic_ya, - //ffi::XK_Cyrillic_er => VirtualKeyCode::Cyrillic_er, - //ffi::XK_Cyrillic_es => VirtualKeyCode::Cyrillic_es, - //ffi::XK_Cyrillic_te => VirtualKeyCode::Cyrillic_te, - //ffi::XK_Cyrillic_u => VirtualKeyCode::Cyrillic_u, - //ffi::XK_Cyrillic_zhe => VirtualKeyCode::Cyrillic_zhe, - //ffi::XK_Cyrillic_ve => VirtualKeyCode::Cyrillic_ve, - //ffi::XK_Cyrillic_softsign => VirtualKeyCode::Cyrillic_softsign, - //ffi::XK_Cyrillic_yeru => VirtualKeyCode::Cyrillic_yeru, - //ffi::XK_Cyrillic_ze => VirtualKeyCode::Cyrillic_ze, - //ffi::XK_Cyrillic_sha => VirtualKeyCode::Cyrillic_sha, - //ffi::XK_Cyrillic_e => VirtualKeyCode::Cyrillic_e, - //ffi::XK_Cyrillic_shcha => VirtualKeyCode::Cyrillic_shcha, - //ffi::XK_Cyrillic_che => VirtualKeyCode::Cyrillic_che, - //ffi::XK_Cyrillic_hardsign => VirtualKeyCode::Cyrillic_hardsign, - //ffi::XK_Cyrillic_YU => VirtualKeyCode::Cyrillic_yu, - //ffi::XK_Cyrillic_A => VirtualKeyCode::Cyrillic_a, - //ffi::XK_Cyrillic_BE => VirtualKeyCode::Cyrillic_be, - //ffi::XK_Cyrillic_TSE => VirtualKeyCode::Cyrillic_tse, - //ffi::XK_Cyrillic_DE => VirtualKeyCode::Cyrillic_de, - //ffi::XK_Cyrillic_IE => VirtualKeyCode::Cyrillic_ie, - //ffi::XK_Cyrillic_EF => VirtualKeyCode::Cyrillic_ef, - //ffi::XK_Cyrillic_GHE => VirtualKeyCode::Cyrillic_ghe, - //ffi::XK_Cyrillic_HA => VirtualKeyCode::Cyrillic_ha, - //ffi::XK_Cyrillic_I => VirtualKeyCode::Cyrillic_i, - //ffi::XK_Cyrillic_SHORTI => VirtualKeyCode::Cyrillic_shorti, - //ffi::XK_Cyrillic_KA => VirtualKeyCode::Cyrillic_ka, - //ffi::XK_Cyrillic_EL => VirtualKeyCode::Cyrillic_el, - //ffi::XK_Cyrillic_EM => VirtualKeyCode::Cyrillic_em, - //ffi::XK_Cyrillic_EN => VirtualKeyCode::Cyrillic_en, - //ffi::XK_Cyrillic_O => VirtualKeyCode::Cyrillic_o, - //ffi::XK_Cyrillic_PE => VirtualKeyCode::Cyrillic_pe, - //ffi::XK_Cyrillic_YA => VirtualKeyCode::Cyrillic_ya, - //ffi::XK_Cyrillic_ER => VirtualKeyCode::Cyrillic_er, - //ffi::XK_Cyrillic_ES => VirtualKeyCode::Cyrillic_es, - //ffi::XK_Cyrillic_TE => VirtualKeyCode::Cyrillic_te, - //ffi::XK_Cyrillic_U => VirtualKeyCode::Cyrillic_u, - //ffi::XK_Cyrillic_ZHE => VirtualKeyCode::Cyrillic_zhe, - //ffi::XK_Cyrillic_VE => VirtualKeyCode::Cyrillic_ve, - //ffi::XK_Cyrillic_SOFTSIGN => VirtualKeyCode::Cyrillic_softsign, - //ffi::XK_Cyrillic_YERU => VirtualKeyCode::Cyrillic_yeru, - //ffi::XK_Cyrillic_ZE => VirtualKeyCode::Cyrillic_ze, - //ffi::XK_Cyrillic_SHA => VirtualKeyCode::Cyrillic_sha, - //ffi::XK_Cyrillic_E => VirtualKeyCode::Cyrillic_e, - //ffi::XK_Cyrillic_SHCHA => VirtualKeyCode::Cyrillic_shcha, - //ffi::XK_Cyrillic_CHE => VirtualKeyCode::Cyrillic_che, - //ffi::XK_Cyrillic_HARDSIGN => VirtualKeyCode::Cyrillic_hardsign, - //ffi::XK_Greek_ALPHAaccent => VirtualKeyCode::Greek_alphaaccent, - //ffi::XK_Greek_EPSILONaccent => VirtualKeyCode::Greek_epsilonaccent, - //ffi::XK_Greek_ETAaccent => VirtualKeyCode::Greek_etaaccent, - //ffi::XK_Greek_IOTAaccent => VirtualKeyCode::Greek_iotaaccent, - //ffi::XK_Greek_IOTAdiaeresis => VirtualKeyCode::Greek_iotadiaeresis, - //ffi::XK_Greek_OMICRONaccent => VirtualKeyCode::Greek_omicronaccent, - //ffi::XK_Greek_UPSILONaccent => VirtualKeyCode::Greek_upsilonaccent, - //ffi::XK_Greek_UPSILONdieresis => VirtualKeyCode::Greek_upsilondieresis, - //ffi::XK_Greek_OMEGAaccent => VirtualKeyCode::Greek_omegaaccent, - //ffi::XK_Greek_accentdieresis => VirtualKeyCode::Greek_accentdieresis, - //ffi::XK_Greek_horizbar => VirtualKeyCode::Greek_horizbar, - //ffi::XK_Greek_alphaaccent => VirtualKeyCode::Greek_alphaaccent, - //ffi::XK_Greek_epsilonaccent => VirtualKeyCode::Greek_epsilonaccent, - //ffi::XK_Greek_etaaccent => VirtualKeyCode::Greek_etaaccent, - //ffi::XK_Greek_iotaaccent => VirtualKeyCode::Greek_iotaaccent, - //ffi::XK_Greek_iotadieresis => VirtualKeyCode::Greek_iotadieresis, - //ffi::XK_Greek_iotaaccentdieresis => VirtualKeyCode::Greek_iotaaccentdieresis, - //ffi::XK_Greek_omicronaccent => VirtualKeyCode::Greek_omicronaccent, - //ffi::XK_Greek_upsilonaccent => VirtualKeyCode::Greek_upsilonaccent, - //ffi::XK_Greek_upsilondieresis => VirtualKeyCode::Greek_upsilondieresis, - //ffi::XK_Greek_upsilonaccentdieresis => VirtualKeyCode::Greek_upsilonaccentdieresis, - //ffi::XK_Greek_omegaaccent => VirtualKeyCode::Greek_omegaaccent, - //ffi::XK_Greek_ALPHA => VirtualKeyCode::Greek_alpha, - //ffi::XK_Greek_BETA => VirtualKeyCode::Greek_beta, - //ffi::XK_Greek_GAMMA => VirtualKeyCode::Greek_gamma, - //ffi::XK_Greek_DELTA => VirtualKeyCode::Greek_delta, - //ffi::XK_Greek_EPSILON => VirtualKeyCode::Greek_epsilon, - //ffi::XK_Greek_ZETA => VirtualKeyCode::Greek_zeta, - //ffi::XK_Greek_ETA => VirtualKeyCode::Greek_eta, - //ffi::XK_Greek_THETA => VirtualKeyCode::Greek_theta, - //ffi::XK_Greek_IOTA => VirtualKeyCode::Greek_iota, - //ffi::XK_Greek_KAPPA => VirtualKeyCode::Greek_kappa, - //ffi::XK_Greek_LAMDA => VirtualKeyCode::Greek_lamda, - //ffi::XK_Greek_LAMBDA => VirtualKeyCode::Greek_lambda, - //ffi::XK_Greek_MU => VirtualKeyCode::Greek_mu, - //ffi::XK_Greek_NU => VirtualKeyCode::Greek_nu, - //ffi::XK_Greek_XI => VirtualKeyCode::Greek_xi, - //ffi::XK_Greek_OMICRON => VirtualKeyCode::Greek_omicron, - //ffi::XK_Greek_PI => VirtualKeyCode::Greek_pi, - //ffi::XK_Greek_RHO => VirtualKeyCode::Greek_rho, - //ffi::XK_Greek_SIGMA => VirtualKeyCode::Greek_sigma, - //ffi::XK_Greek_TAU => VirtualKeyCode::Greek_tau, - //ffi::XK_Greek_UPSILON => VirtualKeyCode::Greek_upsilon, - //ffi::XK_Greek_PHI => VirtualKeyCode::Greek_phi, - //ffi::XK_Greek_CHI => VirtualKeyCode::Greek_chi, - //ffi::XK_Greek_PSI => VirtualKeyCode::Greek_psi, - //ffi::XK_Greek_OMEGA => VirtualKeyCode::Greek_omega, - //ffi::XK_Greek_alpha => VirtualKeyCode::Greek_alpha, - //ffi::XK_Greek_beta => VirtualKeyCode::Greek_beta, - //ffi::XK_Greek_gamma => VirtualKeyCode::Greek_gamma, - //ffi::XK_Greek_delta => VirtualKeyCode::Greek_delta, - //ffi::XK_Greek_epsilon => VirtualKeyCode::Greek_epsilon, - //ffi::XK_Greek_zeta => VirtualKeyCode::Greek_zeta, - //ffi::XK_Greek_eta => VirtualKeyCode::Greek_eta, - //ffi::XK_Greek_theta => VirtualKeyCode::Greek_theta, - //ffi::XK_Greek_iota => VirtualKeyCode::Greek_iota, - //ffi::XK_Greek_kappa => VirtualKeyCode::Greek_kappa, - //ffi::XK_Greek_lamda => VirtualKeyCode::Greek_lamda, - //ffi::XK_Greek_lambda => VirtualKeyCode::Greek_lambda, - //ffi::XK_Greek_mu => VirtualKeyCode::Greek_mu, - //ffi::XK_Greek_nu => VirtualKeyCode::Greek_nu, - //ffi::XK_Greek_xi => VirtualKeyCode::Greek_xi, - //ffi::XK_Greek_omicron => VirtualKeyCode::Greek_omicron, - //ffi::XK_Greek_pi => VirtualKeyCode::Greek_pi, - //ffi::XK_Greek_rho => VirtualKeyCode::Greek_rho, - //ffi::XK_Greek_sigma => VirtualKeyCode::Greek_sigma, - //ffi::XK_Greek_finalsmallsigma => VirtualKeyCode::Greek_finalsmallsigma, - //ffi::XK_Greek_tau => VirtualKeyCode::Greek_tau, - //ffi::XK_Greek_upsilon => VirtualKeyCode::Greek_upsilon, - //ffi::XK_Greek_phi => VirtualKeyCode::Greek_phi, - //ffi::XK_Greek_chi => VirtualKeyCode::Greek_chi, - //ffi::XK_Greek_psi => VirtualKeyCode::Greek_psi, - //ffi::XK_Greek_omega => VirtualKeyCode::Greek_omega, - //ffi::XK_Greek_switch => VirtualKeyCode::Greek_switch, - //ffi::XK_leftradical => VirtualKeyCode::Leftradical, - //ffi::XK_topleftradical => VirtualKeyCode::Topleftradical, - //ffi::XK_horizconnector => VirtualKeyCode::Horizconnector, - //ffi::XK_topintegral => VirtualKeyCode::Topintegral, - //ffi::XK_botintegral => VirtualKeyCode::Botintegral, - //ffi::XK_vertconnector => VirtualKeyCode::Vertconnector, - //ffi::XK_topleftsqbracket => VirtualKeyCode::Topleftsqbracket, - //ffi::XK_botleftsqbracket => VirtualKeyCode::Botleftsqbracket, - //ffi::XK_toprightsqbracket => VirtualKeyCode::Toprightsqbracket, - //ffi::XK_botrightsqbracket => VirtualKeyCode::Botrightsqbracket, - //ffi::XK_topleftparens => VirtualKeyCode::Topleftparens, - //ffi::XK_botleftparens => VirtualKeyCode::Botleftparens, - //ffi::XK_toprightparens => VirtualKeyCode::Toprightparens, - //ffi::XK_botrightparens => VirtualKeyCode::Botrightparens, - //ffi::XK_leftmiddlecurlybrace => VirtualKeyCode::Leftmiddlecurlybrace, - //ffi::XK_rightmiddlecurlybrace => VirtualKeyCode::Rightmiddlecurlybrace, - //ffi::XK_topleftsummation => VirtualKeyCode::Topleftsummation, - //ffi::XK_botleftsummation => VirtualKeyCode::Botleftsummation, - //ffi::XK_topvertsummationconnector => VirtualKeyCode::Topvertsummationconnector, - //ffi::XK_botvertsummationconnector => VirtualKeyCode::Botvertsummationconnector, - //ffi::XK_toprightsummation => VirtualKeyCode::Toprightsummation, - //ffi::XK_botrightsummation => VirtualKeyCode::Botrightsummation, - //ffi::XK_rightmiddlesummation => VirtualKeyCode::Rightmiddlesummation, - //ffi::XK_lessthanequal => VirtualKeyCode::Lessthanequal, - //ffi::XK_notequal => VirtualKeyCode::Notequal, - //ffi::XK_greaterthanequal => VirtualKeyCode::Greaterthanequal, - //ffi::XK_integral => VirtualKeyCode::Integral, - //ffi::XK_therefore => VirtualKeyCode::Therefore, - //ffi::XK_variation => VirtualKeyCode::Variation, - //ffi::XK_infinity => VirtualKeyCode::Infinity, - //ffi::XK_nabla => VirtualKeyCode::Nabla, - //ffi::XK_approximate => VirtualKeyCode::Approximate, - //ffi::XK_similarequal => VirtualKeyCode::Similarequal, - //ffi::XK_ifonlyif => VirtualKeyCode::Ifonlyif, - //ffi::XK_implies => VirtualKeyCode::Implies, - //ffi::XK_identical => VirtualKeyCode::Identical, - //ffi::XK_radical => VirtualKeyCode::Radical, - //ffi::XK_includedin => VirtualKeyCode::Includedin, - //ffi::XK_includes => VirtualKeyCode::Includes, - //ffi::XK_intersection => VirtualKeyCode::Intersection, - //ffi::XK_union => VirtualKeyCode::Union, - //ffi::XK_logicaland => VirtualKeyCode::Logicaland, - //ffi::XK_logicalor => VirtualKeyCode::Logicalor, - //ffi::XK_partialderivative => VirtualKeyCode::Partialderivative, - //ffi::XK_function => VirtualKeyCode::Function, - //ffi::XK_leftarrow => VirtualKeyCode::Leftarrow, - //ffi::XK_uparrow => VirtualKeyCode::Uparrow, - //ffi::XK_rightarrow => VirtualKeyCode::Rightarrow, - //ffi::XK_downarrow => VirtualKeyCode::Downarrow, - //ffi::XK_blank => VirtualKeyCode::Blank, - //ffi::XK_soliddiamond => VirtualKeyCode::Soliddiamond, - //ffi::XK_checkerboard => VirtualKeyCode::Checkerboard, - //ffi::XK_ht => VirtualKeyCode::Ht, - //ffi::XK_ff => VirtualKeyCode::Ff, - //ffi::XK_cr => VirtualKeyCode::Cr, - //ffi::XK_lf => VirtualKeyCode::Lf, - //ffi::XK_nl => VirtualKeyCode::Nl, - //ffi::XK_vt => VirtualKeyCode::Vt, - //ffi::XK_lowrightcorner => VirtualKeyCode::Lowrightcorner, - //ffi::XK_uprightcorner => VirtualKeyCode::Uprightcorner, - //ffi::XK_upleftcorner => VirtualKeyCode::Upleftcorner, - //ffi::XK_lowleftcorner => VirtualKeyCode::Lowleftcorner, - //ffi::XK_crossinglines => VirtualKeyCode::Crossinglines, - //ffi::XK_horizlinescan1 => VirtualKeyCode::Horizlinescan1, - //ffi::XK_horizlinescan3 => VirtualKeyCode::Horizlinescan3, - //ffi::XK_horizlinescan5 => VirtualKeyCode::Horizlinescan5, - //ffi::XK_horizlinescan7 => VirtualKeyCode::Horizlinescan7, - //ffi::XK_horizlinescan9 => VirtualKeyCode::Horizlinescan9, - //ffi::XK_leftt => VirtualKeyCode::Leftt, - //ffi::XK_rightt => VirtualKeyCode::Rightt, - //ffi::XK_bott => VirtualKeyCode::Bott, - //ffi::XK_topt => VirtualKeyCode::Topt, - //ffi::XK_vertbar => VirtualKeyCode::Vertbar, - //ffi::XK_emspace => VirtualKeyCode::Emspace, - //ffi::XK_enspace => VirtualKeyCode::Enspace, - //ffi::XK_em3space => VirtualKeyCode::Em3space, - //ffi::XK_em4space => VirtualKeyCode::Em4space, - //ffi::XK_digitspace => VirtualKeyCode::Digitspace, - //ffi::XK_punctspace => VirtualKeyCode::Punctspace, - //ffi::XK_thinspace => VirtualKeyCode::Thinspace, - //ffi::XK_hairspace => VirtualKeyCode::Hairspace, - //ffi::XK_emdash => VirtualKeyCode::Emdash, - //ffi::XK_endash => VirtualKeyCode::Endash, - //ffi::XK_signifblank => VirtualKeyCode::Signifblank, - //ffi::XK_ellipsis => VirtualKeyCode::Ellipsis, - //ffi::XK_doubbaselinedot => VirtualKeyCode::Doubbaselinedot, - //ffi::XK_onethird => VirtualKeyCode::Onethird, - //ffi::XK_twothirds => VirtualKeyCode::Twothirds, - //ffi::XK_onefifth => VirtualKeyCode::Onefifth, - //ffi::XK_twofifths => VirtualKeyCode::Twofifths, - //ffi::XK_threefifths => VirtualKeyCode::Threefifths, - //ffi::XK_fourfifths => VirtualKeyCode::Fourfifths, - //ffi::XK_onesixth => VirtualKeyCode::Onesixth, - //ffi::XK_fivesixths => VirtualKeyCode::Fivesixths, - //ffi::XK_careof => VirtualKeyCode::Careof, - //ffi::XK_figdash => VirtualKeyCode::Figdash, - //ffi::XK_leftanglebracket => VirtualKeyCode::Leftanglebracket, - //ffi::XK_decimalpoint => VirtualKeyCode::Decimalpoint, - //ffi::XK_rightanglebracket => VirtualKeyCode::Rightanglebracket, - //ffi::XK_marker => VirtualKeyCode::Marker, - //ffi::XK_oneeighth => VirtualKeyCode::Oneeighth, - //ffi::XK_threeeighths => VirtualKeyCode::Threeeighths, - //ffi::XK_fiveeighths => VirtualKeyCode::Fiveeighths, - //ffi::XK_seveneighths => VirtualKeyCode::Seveneighths, - //ffi::XK_trademark => VirtualKeyCode::Trademark, - //ffi::XK_signaturemark => VirtualKeyCode::Signaturemark, - //ffi::XK_trademarkincircle => VirtualKeyCode::Trademarkincircle, - //ffi::XK_leftopentriangle => VirtualKeyCode::Leftopentriangle, - //ffi::XK_rightopentriangle => VirtualKeyCode::Rightopentriangle, - //ffi::XK_emopencircle => VirtualKeyCode::Emopencircle, - //ffi::XK_emopenrectangle => VirtualKeyCode::Emopenrectangle, - //ffi::XK_leftsinglequotemark => VirtualKeyCode::Leftsinglequotemark, - //ffi::XK_rightsinglequotemark => VirtualKeyCode::Rightsinglequotemark, - //ffi::XK_leftdoublequotemark => VirtualKeyCode::Leftdoublequotemark, - //ffi::XK_rightdoublequotemark => VirtualKeyCode::Rightdoublequotemark, - //ffi::XK_prescription => VirtualKeyCode::Prescription, - //ffi::XK_minutes => VirtualKeyCode::Minutes, - //ffi::XK_seconds => VirtualKeyCode::Seconds, - //ffi::XK_latincross => VirtualKeyCode::Latincross, - //ffi::XK_hexagram => VirtualKeyCode::Hexagram, - //ffi::XK_filledrectbullet => VirtualKeyCode::Filledrectbullet, - //ffi::XK_filledlefttribullet => VirtualKeyCode::Filledlefttribullet, - //ffi::XK_filledrighttribullet => VirtualKeyCode::Filledrighttribullet, - //ffi::XK_emfilledcircle => VirtualKeyCode::Emfilledcircle, - //ffi::XK_emfilledrect => VirtualKeyCode::Emfilledrect, - //ffi::XK_enopencircbullet => VirtualKeyCode::Enopencircbullet, - //ffi::XK_enopensquarebullet => VirtualKeyCode::Enopensquarebullet, - //ffi::XK_openrectbullet => VirtualKeyCode::Openrectbullet, - //ffi::XK_opentribulletup => VirtualKeyCode::Opentribulletup, - //ffi::XK_opentribulletdown => VirtualKeyCode::Opentribulletdown, - //ffi::XK_openstar => VirtualKeyCode::Openstar, - //ffi::XK_enfilledcircbullet => VirtualKeyCode::Enfilledcircbullet, - //ffi::XK_enfilledsqbullet => VirtualKeyCode::Enfilledsqbullet, - //ffi::XK_filledtribulletup => VirtualKeyCode::Filledtribulletup, - //ffi::XK_filledtribulletdown => VirtualKeyCode::Filledtribulletdown, - //ffi::XK_leftpointer => VirtualKeyCode::Leftpointer, - //ffi::XK_rightpointer => VirtualKeyCode::Rightpointer, - //ffi::XK_club => VirtualKeyCode::Club, - //ffi::XK_diamond => VirtualKeyCode::Diamond, - //ffi::XK_heart => VirtualKeyCode::Heart, - //ffi::XK_maltesecross => VirtualKeyCode::Maltesecross, - //ffi::XK_dagger => VirtualKeyCode::Dagger, - //ffi::XK_doubledagger => VirtualKeyCode::Doubledagger, - //ffi::XK_checkmark => VirtualKeyCode::Checkmark, - //ffi::XK_ballotcross => VirtualKeyCode::Ballotcross, - //ffi::XK_musicalsharp => VirtualKeyCode::Musicalsharp, - //ffi::XK_musicalflat => VirtualKeyCode::Musicalflat, - //ffi::XK_malesymbol => VirtualKeyCode::Malesymbol, - //ffi::XK_femalesymbol => VirtualKeyCode::Femalesymbol, - //ffi::XK_telephone => VirtualKeyCode::Telephone, - //ffi::XK_telephonerecorder => VirtualKeyCode::Telephonerecorder, - //ffi::XK_phonographcopyright => VirtualKeyCode::Phonographcopyright, - //ffi::XK_caret => VirtualKeyCode::Caret, - //ffi::XK_singlelowquotemark => VirtualKeyCode::Singlelowquotemark, - //ffi::XK_doublelowquotemark => VirtualKeyCode::Doublelowquotemark, - //ffi::XK_cursor => VirtualKeyCode::Cursor, - //ffi::XK_leftcaret => VirtualKeyCode::Leftcaret, - //ffi::XK_rightcaret => VirtualKeyCode::Rightcaret, - //ffi::XK_downcaret => VirtualKeyCode::Downcaret, - //ffi::XK_upcaret => VirtualKeyCode::Upcaret, - //ffi::XK_overbar => VirtualKeyCode::Overbar, - //ffi::XK_downtack => VirtualKeyCode::Downtack, - //ffi::XK_upshoe => VirtualKeyCode::Upshoe, - //ffi::XK_downstile => VirtualKeyCode::Downstile, - //ffi::XK_underbar => VirtualKeyCode::Underbar, - //ffi::XK_jot => VirtualKeyCode::Jot, - //ffi::XK_quad => VirtualKeyCode::Quad, - //ffi::XK_uptack => VirtualKeyCode::Uptack, - //ffi::XK_circle => VirtualKeyCode::Circle, - //ffi::XK_upstile => VirtualKeyCode::Upstile, - //ffi::XK_downshoe => VirtualKeyCode::Downshoe, - //ffi::XK_rightshoe => VirtualKeyCode::Rightshoe, - //ffi::XK_leftshoe => VirtualKeyCode::Leftshoe, - //ffi::XK_lefttack => VirtualKeyCode::Lefttack, - //ffi::XK_righttack => VirtualKeyCode::Righttack, - //ffi::XK_hebrew_doublelowline => VirtualKeyCode::Hebrew_doublelowline, - //ffi::XK_hebrew_aleph => VirtualKeyCode::Hebrew_aleph, - //ffi::XK_hebrew_bet => VirtualKeyCode::Hebrew_bet, - //ffi::XK_hebrew_beth => VirtualKeyCode::Hebrew_beth, - //ffi::XK_hebrew_gimel => VirtualKeyCode::Hebrew_gimel, - //ffi::XK_hebrew_gimmel => VirtualKeyCode::Hebrew_gimmel, - //ffi::XK_hebrew_dalet => VirtualKeyCode::Hebrew_dalet, - //ffi::XK_hebrew_daleth => VirtualKeyCode::Hebrew_daleth, - //ffi::XK_hebrew_he => VirtualKeyCode::Hebrew_he, - //ffi::XK_hebrew_waw => VirtualKeyCode::Hebrew_waw, - //ffi::XK_hebrew_zain => VirtualKeyCode::Hebrew_zain, - //ffi::XK_hebrew_zayin => VirtualKeyCode::Hebrew_zayin, - //ffi::XK_hebrew_chet => VirtualKeyCode::Hebrew_chet, - //ffi::XK_hebrew_het => VirtualKeyCode::Hebrew_het, - //ffi::XK_hebrew_tet => VirtualKeyCode::Hebrew_tet, - //ffi::XK_hebrew_teth => VirtualKeyCode::Hebrew_teth, - //ffi::XK_hebrew_yod => VirtualKeyCode::Hebrew_yod, - //ffi::XK_hebrew_finalkaph => VirtualKeyCode::Hebrew_finalkaph, - //ffi::XK_hebrew_kaph => VirtualKeyCode::Hebrew_kaph, - //ffi::XK_hebrew_lamed => VirtualKeyCode::Hebrew_lamed, - //ffi::XK_hebrew_finalmem => VirtualKeyCode::Hebrew_finalmem, - //ffi::XK_hebrew_mem => VirtualKeyCode::Hebrew_mem, - //ffi::XK_hebrew_finalnun => VirtualKeyCode::Hebrew_finalnun, - //ffi::XK_hebrew_nun => VirtualKeyCode::Hebrew_nun, - //ffi::XK_hebrew_samech => VirtualKeyCode::Hebrew_samech, - //ffi::XK_hebrew_samekh => VirtualKeyCode::Hebrew_samekh, - //ffi::XK_hebrew_ayin => VirtualKeyCode::Hebrew_ayin, - //ffi::XK_hebrew_finalpe => VirtualKeyCode::Hebrew_finalpe, - //ffi::XK_hebrew_pe => VirtualKeyCode::Hebrew_pe, - //ffi::XK_hebrew_finalzade => VirtualKeyCode::Hebrew_finalzade, - //ffi::XK_hebrew_finalzadi => VirtualKeyCode::Hebrew_finalzadi, - //ffi::XK_hebrew_zade => VirtualKeyCode::Hebrew_zade, - //ffi::XK_hebrew_zadi => VirtualKeyCode::Hebrew_zadi, - //ffi::XK_hebrew_qoph => VirtualKeyCode::Hebrew_qoph, - //ffi::XK_hebrew_kuf => VirtualKeyCode::Hebrew_kuf, - //ffi::XK_hebrew_resh => VirtualKeyCode::Hebrew_resh, - //ffi::XK_hebrew_shin => VirtualKeyCode::Hebrew_shin, - //ffi::XK_hebrew_taw => VirtualKeyCode::Hebrew_taw, - //ffi::XK_hebrew_taf => VirtualKeyCode::Hebrew_taf, - //ffi::XK_Hebrew_switch => VirtualKeyCode::Hebrew_switch, - ffi::XF86XK_Back => VirtualKeyCode::NavigateBackward, - ffi::XF86XK_Forward => VirtualKeyCode::NavigateForward, - ffi::XF86XK_Copy => VirtualKeyCode::Copy, - ffi::XF86XK_Paste => VirtualKeyCode::Paste, - ffi::XF86XK_Cut => VirtualKeyCode::Cut, - _ => return None, - }) -} diff --git a/src/platform_impl/linux/x11/ffi.rs b/src/platform_impl/linux/x11/ffi.rs deleted file mode 100644 index 6d7c20894b..0000000000 --- a/src/platform_impl/linux/x11/ffi.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub use x11_dl::{ - error::OpenError, keysym::*, xcursor::*, xinput::*, xinput2::*, xlib::*, xlib_xcb::*, - xrandr::*, xrender::*, -}; diff --git a/src/platform_impl/linux/x11/ime/callbacks.rs b/src/platform_impl/linux/x11/ime/callbacks.rs deleted file mode 100644 index f254a04e55..0000000000 --- a/src/platform_impl/linux/x11/ime/callbacks.rs +++ /dev/null @@ -1,175 +0,0 @@ -use std::{collections::HashMap, os::raw::c_char, ptr, sync::Arc}; - -use super::{ffi, XConnection, XError}; - -use super::{ - context::{ImeContext, ImeContextCreationError}, - inner::{close_im, ImeInner}, - input_method::PotentialInputMethods, -}; - -pub unsafe fn xim_set_callback( - xconn: &Arc, - xim: ffi::XIM, - field: *const c_char, - callback: *mut ffi::XIMCallback, -) -> Result<(), XError> { - // It's advisable to wrap variadic FFI functions in our own functions, as we want to minimize - // access that isn't type-checked. - (xconn.xlib.XSetIMValues)(xim, field, callback, ptr::null_mut::<()>()); - xconn.check_errors() -} - -// Set a callback for when an input method matching the current locale modifiers becomes -// available. Note that this has nothing to do with what input methods are open or able to be -// opened, and simply uses the modifiers that are set when the callback is set. -// * This is called per locale modifier, not per input method opened with that locale modifier. -// * Trying to set this for multiple locale modifiers causes problems, i.e. one of the rebuilt -// input contexts would always silently fail to use the input method. -pub unsafe fn set_instantiate_callback( - xconn: &Arc, - client_data: ffi::XPointer, -) -> Result<(), XError> { - (xconn.xlib.XRegisterIMInstantiateCallback)( - xconn.display, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - Some(xim_instantiate_callback), - client_data, - ); - xconn.check_errors() -} - -pub unsafe fn unset_instantiate_callback( - xconn: &Arc, - client_data: ffi::XPointer, -) -> Result<(), XError> { - (xconn.xlib.XUnregisterIMInstantiateCallback)( - xconn.display, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - Some(xim_instantiate_callback), - client_data, - ); - xconn.check_errors() -} - -pub unsafe fn set_destroy_callback( - xconn: &Arc, - im: ffi::XIM, - inner: &ImeInner, -) -> Result<(), XError> { - xim_set_callback( - &xconn, - im, - ffi::XNDestroyCallback_0.as_ptr() as *const _, - &inner.destroy_callback as *const _ as *mut _, - ) -} - -#[derive(Debug)] -enum ReplaceImError { - MethodOpenFailed(PotentialInputMethods), - ContextCreationFailed(ImeContextCreationError), - SetDestroyCallbackFailed(XError), -} - -// Attempt to replace current IM (which may or may not be presently valid) with a new one. This -// includes replacing all existing input contexts and free'ing resources as necessary. This only -// modifies existing state if all operations succeed. -unsafe fn replace_im(inner: *mut ImeInner) -> Result<(), ReplaceImError> { - let xconn = &(*inner).xconn; - - let (new_im, is_fallback) = { - let new_im = (*inner).potential_input_methods.open_im(xconn, None); - let is_fallback = new_im.is_fallback(); - ( - new_im.ok().ok_or_else(|| { - ReplaceImError::MethodOpenFailed((*inner).potential_input_methods.clone()) - })?, - is_fallback, - ) - }; - - // It's important to always set a destroy callback, since there's otherwise potential for us - // to try to use or free a resource that's already been destroyed on the server. - { - let result = set_destroy_callback(xconn, new_im.im, &*inner); - if result.is_err() { - let _ = close_im(xconn, new_im.im); - } - result - } - .map_err(ReplaceImError::SetDestroyCallbackFailed)?; - - let mut new_contexts = HashMap::new(); - for (window, old_context) in (*inner).contexts.iter() { - let spot = old_context.as_ref().map(|old_context| old_context.ic_spot); - let new_context = { - let result = ImeContext::new(xconn, new_im.im, *window, spot); - if result.is_err() { - let _ = close_im(xconn, new_im.im); - } - result.map_err(ReplaceImError::ContextCreationFailed)? - }; - new_contexts.insert(*window, Some(new_context)); - } - - // If we've made it this far, everything succeeded. - let _ = (*inner).destroy_all_contexts_if_necessary(); - let _ = (*inner).close_im_if_necessary(); - (*inner).im = new_im.im; - (*inner).contexts = new_contexts; - (*inner).is_destroyed = false; - (*inner).is_fallback = is_fallback; - Ok(()) -} - -pub unsafe extern "C" fn xim_instantiate_callback( - _display: *mut ffi::Display, - client_data: ffi::XPointer, - // This field is unsupplied. - _call_data: ffi::XPointer, -) { - let inner: *mut ImeInner = client_data as _; - if !inner.is_null() { - let xconn = &(*inner).xconn; - let result = replace_im(inner); - if result.is_ok() { - let _ = unset_instantiate_callback(xconn, client_data); - (*inner).is_fallback = false; - } else if result.is_err() && (*inner).is_destroyed { - // We have no usable input methods! - result.expect("Failed to reopen input method"); - } - } -} - -// This callback is triggered when the input method is closed on the server end. When this -// happens, XCloseIM/XDestroyIC doesn't need to be called, as the resources have already been -// free'd (attempting to do so causes our connection to freeze). -pub unsafe extern "C" fn xim_destroy_callback( - _xim: ffi::XIM, - client_data: ffi::XPointer, - // This field is unsupplied. - _call_data: ffi::XPointer, -) { - let inner: *mut ImeInner = client_data as _; - if !inner.is_null() { - (*inner).is_destroyed = true; - let xconn = &(*inner).xconn; - if !(*inner).is_fallback { - let _ = set_instantiate_callback(xconn, client_data); - // Attempt to open fallback input method. - let result = replace_im(inner); - if result.is_ok() { - (*inner).is_fallback = true; - } else { - // We have no usable input methods! - result.expect("Failed to open fallback input method"); - } - } - } -} diff --git a/src/platform_impl/linux/x11/ime/context.rs b/src/platform_impl/linux/x11/ime/context.rs deleted file mode 100644 index 8c2ff4cf50..0000000000 --- a/src/platform_impl/linux/x11/ime/context.rs +++ /dev/null @@ -1,139 +0,0 @@ -use std::{ - os::raw::{c_short, c_void}, - ptr, - sync::Arc, -}; - -use super::{ffi, util, XConnection, XError}; - -#[derive(Debug)] -pub enum ImeContextCreationError { - XError(XError), - Null, -} - -unsafe fn create_pre_edit_attr<'a>( - xconn: &'a Arc, - ic_spot: &'a ffi::XPoint, -) -> util::XSmartPointer<'a, c_void> { - util::XSmartPointer::new( - xconn, - (xconn.xlib.XVaCreateNestedList)( - 0, - ffi::XNSpotLocation_0.as_ptr() as *const _, - ic_spot, - ptr::null_mut::<()>(), - ), - ) - .expect("XVaCreateNestedList returned NULL") -} - -// WARNING: this struct doesn't destroy its XIC resource when dropped. -// This is intentional, as it doesn't have enough information to know whether or not the context -// still exists on the server. Since `ImeInner` has that awareness, destruction must be handled -// through `ImeInner`. -#[derive(Debug)] -pub struct ImeContext { - pub ic: ffi::XIC, - pub ic_spot: ffi::XPoint, -} - -impl ImeContext { - pub unsafe fn new( - xconn: &Arc, - im: ffi::XIM, - window: ffi::Window, - ic_spot: Option, - ) -> Result { - let ic = if let Some(ic_spot) = ic_spot { - ImeContext::create_ic_with_spot(xconn, im, window, ic_spot) - } else { - ImeContext::create_ic(xconn, im, window) - }; - - let ic = ic.ok_or(ImeContextCreationError::Null)?; - xconn - .check_errors() - .map_err(ImeContextCreationError::XError)?; - - Ok(ImeContext { - ic, - ic_spot: ic_spot.unwrap_or_else(|| ffi::XPoint { x: 0, y: 0 }), - }) - } - - unsafe fn create_ic( - xconn: &Arc, - im: ffi::XIM, - window: ffi::Window, - ) -> Option { - let ic = (xconn.xlib.XCreateIC)( - im, - ffi::XNInputStyle_0.as_ptr() as *const _, - ffi::XIMPreeditNothing | ffi::XIMStatusNothing, - ffi::XNClientWindow_0.as_ptr() as *const _, - window, - ptr::null_mut::<()>(), - ); - if ic.is_null() { - None - } else { - Some(ic) - } - } - - unsafe fn create_ic_with_spot( - xconn: &Arc, - im: ffi::XIM, - window: ffi::Window, - ic_spot: ffi::XPoint, - ) -> Option { - let pre_edit_attr = create_pre_edit_attr(xconn, &ic_spot); - let ic = (xconn.xlib.XCreateIC)( - im, - ffi::XNInputStyle_0.as_ptr() as *const _, - ffi::XIMPreeditNothing | ffi::XIMStatusNothing, - ffi::XNClientWindow_0.as_ptr() as *const _, - window, - ffi::XNPreeditAttributes_0.as_ptr() as *const _, - pre_edit_attr.ptr, - ptr::null_mut::<()>(), - ); - if ic.is_null() { - None - } else { - Some(ic) - } - } - - pub fn focus(&self, xconn: &Arc) -> Result<(), XError> { - unsafe { - (xconn.xlib.XSetICFocus)(self.ic); - } - xconn.check_errors() - } - - pub fn unfocus(&self, xconn: &Arc) -> Result<(), XError> { - unsafe { - (xconn.xlib.XUnsetICFocus)(self.ic); - } - xconn.check_errors() - } - - pub fn set_spot(&mut self, xconn: &Arc, x: c_short, y: c_short) { - if self.ic_spot.x == x && self.ic_spot.y == y { - return; - } - self.ic_spot = ffi::XPoint { x, y }; - - unsafe { - let pre_edit_attr = create_pre_edit_attr(xconn, &self.ic_spot); - (xconn.xlib.XSetICValues)( - self.ic, - ffi::XNPreeditAttributes_0.as_ptr() as *const _, - pre_edit_attr.ptr, - ptr::null_mut::<()>(), - ); - } - } -} diff --git a/src/platform_impl/linux/x11/ime/inner.rs b/src/platform_impl/linux/x11/ime/inner.rs deleted file mode 100644 index 011e22aa13..0000000000 --- a/src/platform_impl/linux/x11/ime/inner.rs +++ /dev/null @@ -1,68 +0,0 @@ -use std::{collections::HashMap, mem, ptr, sync::Arc}; - -use super::{ffi, XConnection, XError}; - -use super::{context::ImeContext, input_method::PotentialInputMethods}; - -pub unsafe fn close_im(xconn: &Arc, im: ffi::XIM) -> Result<(), XError> { - (xconn.xlib.XCloseIM)(im); - xconn.check_errors() -} - -pub unsafe fn destroy_ic(xconn: &Arc, ic: ffi::XIC) -> Result<(), XError> { - (xconn.xlib.XDestroyIC)(ic); - xconn.check_errors() -} - -pub struct ImeInner { - pub xconn: Arc, - // WARNING: this is initially null! - pub im: ffi::XIM, - pub potential_input_methods: PotentialInputMethods, - pub contexts: HashMap>, - // WARNING: this is initially zeroed! - pub destroy_callback: ffi::XIMCallback, - // Indicates whether or not the the input method was destroyed on the server end - // (i.e. if ibus/fcitx/etc. was terminated/restarted) - pub is_destroyed: bool, - pub is_fallback: bool, -} - -impl ImeInner { - pub fn new(xconn: Arc, potential_input_methods: PotentialInputMethods) -> Self { - ImeInner { - xconn, - im: ptr::null_mut(), - potential_input_methods, - contexts: HashMap::new(), - destroy_callback: unsafe { mem::zeroed() }, - is_destroyed: false, - is_fallback: false, - } - } - - pub unsafe fn close_im_if_necessary(&self) -> Result { - if !self.is_destroyed { - close_im(&self.xconn, self.im).map(|_| true) - } else { - Ok(false) - } - } - - pub unsafe fn destroy_ic_if_necessary(&self, ic: ffi::XIC) -> Result { - if !self.is_destroyed { - destroy_ic(&self.xconn, ic).map(|_| true) - } else { - Ok(false) - } - } - - pub unsafe fn destroy_all_contexts_if_necessary(&self) -> Result { - for context in self.contexts.values() { - if let &Some(ref context) = context { - self.destroy_ic_if_necessary(context.ic)?; - } - } - Ok(!self.is_destroyed) - } -} diff --git a/src/platform_impl/linux/x11/ime/input_method.rs b/src/platform_impl/linux/x11/ime/input_method.rs deleted file mode 100644 index 142c150199..0000000000 --- a/src/platform_impl/linux/x11/ime/input_method.rs +++ /dev/null @@ -1,278 +0,0 @@ -use std::{ - env, - ffi::{CStr, CString, IntoStringError}, - fmt, - os::raw::c_char, - ptr, - sync::Arc, -}; - -use parking_lot::Mutex; - -use super::{ffi, util, XConnection, XError}; - -lazy_static! { - static ref GLOBAL_LOCK: Mutex<()> = Default::default(); -} - -unsafe fn open_im(xconn: &Arc, locale_modifiers: &CStr) -> Option { - let _lock = GLOBAL_LOCK.lock(); - - // XSetLocaleModifiers returns... - // * The current locale modifiers if it's given a NULL pointer. - // * The new locale modifiers if we succeeded in setting them. - // * NULL if the locale modifiers string is malformed or if the - // current locale is not supported by Xlib. - (xconn.xlib.XSetLocaleModifiers)(locale_modifiers.as_ptr()); - - let im = (xconn.xlib.XOpenIM)( - xconn.display, - ptr::null_mut(), - ptr::null_mut(), - ptr::null_mut(), - ); - - if im.is_null() { - None - } else { - Some(im) - } -} - -#[derive(Debug)] -pub struct InputMethod { - pub im: ffi::XIM, - name: String, -} - -impl InputMethod { - fn new(im: ffi::XIM, name: String) -> Self { - InputMethod { im, name } - } -} - -#[derive(Debug)] -pub enum InputMethodResult { - /// Input method used locale modifier from `XMODIFIERS` environment variable. - XModifiers(InputMethod), - /// Input method used internal fallback locale modifier. - Fallback(InputMethod), - /// Input method could not be opened using any locale modifier tried. - Failure, -} - -impl InputMethodResult { - pub fn is_fallback(&self) -> bool { - if let &InputMethodResult::Fallback(_) = self { - true - } else { - false - } - } - - pub fn ok(self) -> Option { - use self::InputMethodResult::*; - match self { - XModifiers(im) | Fallback(im) => Some(im), - Failure => None, - } - } -} - -#[derive(Debug, Clone)] -enum GetXimServersError { - XError(XError), - GetPropertyError(util::GetPropertyError), - InvalidUtf8(IntoStringError), -} - -// The root window has a property named XIM_SERVERS, which contains a list of atoms represeting -// the availabile XIM servers. For instance, if you're using ibus, it would contain an atom named -// "@server=ibus". It's possible for this property to contain multiple atoms, though presumably -// rare. Note that we replace "@server=" with "@im=" in order to match the format of locale -// modifiers, since we don't want a user who's looking at logs to ask "am I supposed to set -// XMODIFIERS to `@server=ibus`?!?" -unsafe fn get_xim_servers(xconn: &Arc) -> Result, GetXimServersError> { - let servers_atom = xconn.get_atom_unchecked(b"XIM_SERVERS\0"); - - let root = (xconn.xlib.XDefaultRootWindow)(xconn.display); - - let mut atoms: Vec = xconn - .get_property(root, servers_atom, ffi::XA_ATOM) - .map_err(GetXimServersError::GetPropertyError)?; - - let mut names: Vec<*const c_char> = Vec::with_capacity(atoms.len()); - (xconn.xlib.XGetAtomNames)( - xconn.display, - atoms.as_mut_ptr(), - atoms.len() as _, - names.as_mut_ptr() as _, - ); - names.set_len(atoms.len()); - - let mut formatted_names = Vec::with_capacity(names.len()); - for name in names { - let string = CStr::from_ptr(name) - .to_owned() - .into_string() - .map_err(GetXimServersError::InvalidUtf8)?; - (xconn.xlib.XFree)(name as _); - formatted_names.push(string.replace("@server=", "@im=")); - } - xconn.check_errors().map_err(GetXimServersError::XError)?; - Ok(formatted_names) -} - -#[derive(Clone)] -struct InputMethodName { - c_string: CString, - string: String, -} - -impl InputMethodName { - pub fn from_string(string: String) -> Self { - let c_string = CString::new(string.clone()) - .expect("String used to construct CString contained null byte"); - InputMethodName { c_string, string } - } - - pub fn from_str(string: &str) -> Self { - let c_string = - CString::new(string).expect("String used to construct CString contained null byte"); - InputMethodName { - c_string, - string: string.to_owned(), - } - } -} - -impl fmt::Debug for InputMethodName { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.string.fmt(f) - } -} - -#[derive(Debug, Clone)] -struct PotentialInputMethod { - name: InputMethodName, - successful: Option, -} - -impl PotentialInputMethod { - pub fn from_string(string: String) -> Self { - PotentialInputMethod { - name: InputMethodName::from_string(string), - successful: None, - } - } - - pub fn from_str(string: &str) -> Self { - PotentialInputMethod { - name: InputMethodName::from_str(string), - successful: None, - } - } - - pub fn reset(&mut self) { - self.successful = None; - } - - pub fn open_im(&mut self, xconn: &Arc) -> Option { - let im = unsafe { open_im(xconn, &self.name.c_string) }; - self.successful = Some(im.is_some()); - im.map(|im| InputMethod::new(im, self.name.string.clone())) - } -} - -// By logging this struct, you get a sequential listing of every locale modifier tried, where it -// came from, and if it succceeded. -#[derive(Debug, Clone)] -pub struct PotentialInputMethods { - // On correctly configured systems, the XMODIFIERS environemnt variable tells us everything we - // need to know. - xmodifiers: Option, - // We have some standard options at our disposal that should ostensibly always work. For users - // who only need compose sequences, this ensures that the program launches without a hitch - // For users who need more sophisticated IME features, this is more or less a silent failure. - // Logging features should be added in the future to allow both audiences to be effectively - // served. - fallbacks: [PotentialInputMethod; 2], - // For diagnostic purposes, we include the list of XIM servers that the server reports as - // being available. - _xim_servers: Result, GetXimServersError>, -} - -impl PotentialInputMethods { - pub fn new(xconn: &Arc) -> Self { - let xmodifiers = env::var("XMODIFIERS") - .ok() - .map(PotentialInputMethod::from_string); - PotentialInputMethods { - // Since passing "" to XSetLocaleModifiers results in it defaulting to the value of - // XMODIFIERS, it's worth noting what happens if XMODIFIERS is also "". If simply - // running the program with `XMODIFIERS="" cargo run`, then assuming XMODIFIERS is - // defined in the profile (or parent environment) then that parent XMODIFIERS is used. - // If that XMODIFIERS value is also "" (i.e. if you ran `export XMODIFIERS=""`), then - // XSetLocaleModifiers uses the default local input method. Note that defining - // XMODIFIERS as "" is different from XMODIFIERS not being defined at all, since in - // that case, we get `None` and end up skipping ahead to the next method. - xmodifiers, - fallbacks: [ - // This is a standard input method that supports compose equences, which should - // always be available. `@im=none` appears to mean the same thing. - PotentialInputMethod::from_str("@im=local"), - // This explicitly specifies to use the implementation-dependent default, though - // that seems to be equivalent to just using the local input method. - PotentialInputMethod::from_str("@im="), - ], - // The XIM_SERVERS property can have surprising values. For instance, when I exited - // ibus to run fcitx, it retained the value denoting ibus. Even more surprising is - // that the fcitx input method could only be successfully opened using "@im=ibus". - // Presumably due to this quirk, it's actually possible to alternate between ibus and - // fcitx in a running application. - _xim_servers: unsafe { get_xim_servers(xconn) }, - } - } - - // This resets the `successful` field of every potential input method, ensuring we have - // accurate information when this struct is re-used by the destruction/instantiation callbacks. - fn reset(&mut self) { - if let Some(ref mut input_method) = self.xmodifiers { - input_method.reset(); - } - - for input_method in &mut self.fallbacks { - input_method.reset(); - } - } - - pub fn open_im( - &mut self, - xconn: &Arc, - callback: Option<&dyn Fn() -> ()>, - ) -> InputMethodResult { - use self::InputMethodResult::*; - - self.reset(); - - if let Some(ref mut input_method) = self.xmodifiers { - let im = input_method.open_im(xconn); - if let Some(im) = im { - return XModifiers(im); - } else { - if let Some(ref callback) = callback { - callback(); - } - } - } - - for input_method in &mut self.fallbacks { - let im = input_method.open_im(xconn); - if let Some(im) = im { - return Fallback(im); - } - } - - Failure - } -} diff --git a/src/platform_impl/linux/x11/ime/mod.rs b/src/platform_impl/linux/x11/ime/mod.rs deleted file mode 100644 index b95da71101..0000000000 --- a/src/platform_impl/linux/x11/ime/mod.rs +++ /dev/null @@ -1,163 +0,0 @@ -// Important: all XIM calls need to happen from the same thread! - -mod callbacks; -mod context; -mod inner; -mod input_method; - -use std::sync::{ - mpsc::{Receiver, Sender}, - Arc, -}; - -use super::{ffi, util, XConnection, XError}; - -pub use self::context::ImeContextCreationError; -use self::{ - callbacks::*, - context::ImeContext, - inner::{close_im, ImeInner}, - input_method::PotentialInputMethods, -}; - -pub type ImeReceiver = Receiver<(ffi::Window, i16, i16)>; -pub type ImeSender = Sender<(ffi::Window, i16, i16)>; - -#[derive(Debug)] -pub enum ImeCreationError { - OpenFailure(PotentialInputMethods), - SetDestroyCallbackFailed(XError), -} - -pub struct Ime { - xconn: Arc, - // The actual meat of this struct is boxed away, since it needs to have a fixed location in - // memory so we can pass a pointer to it around. - inner: Box, -} - -impl Ime { - pub fn new(xconn: Arc) -> Result { - let potential_input_methods = PotentialInputMethods::new(&xconn); - - let (mut inner, client_data) = { - let mut inner = Box::new(ImeInner::new(xconn, potential_input_methods)); - let inner_ptr = Box::into_raw(inner); - let client_data = inner_ptr as _; - let destroy_callback = ffi::XIMCallback { - client_data, - callback: Some(xim_destroy_callback), - }; - inner = unsafe { Box::from_raw(inner_ptr) }; - inner.destroy_callback = destroy_callback; - (inner, client_data) - }; - - let xconn = Arc::clone(&inner.xconn); - - let input_method = inner.potential_input_methods.open_im( - &xconn, - Some(&|| { - let _ = unsafe { set_instantiate_callback(&xconn, client_data) }; - }), - ); - - let is_fallback = input_method.is_fallback(); - if let Some(input_method) = input_method.ok() { - inner.im = input_method.im; - inner.is_fallback = is_fallback; - unsafe { - let result = set_destroy_callback(&xconn, input_method.im, &*inner) - .map_err(ImeCreationError::SetDestroyCallbackFailed); - if result.is_err() { - let _ = close_im(&xconn, input_method.im); - } - result?; - } - Ok(Ime { xconn, inner }) - } else { - Err(ImeCreationError::OpenFailure(inner.potential_input_methods)) - } - } - - pub fn is_destroyed(&self) -> bool { - self.inner.is_destroyed - } - - // This pattern is used for various methods here: - // Ok(_) indicates that nothing went wrong internally - // Ok(true) indicates that the action was actually performed - // Ok(false) indicates that the action is not presently applicable - pub fn create_context(&mut self, window: ffi::Window) -> Result { - let context = if self.is_destroyed() { - // Create empty entry in map, so that when IME is rebuilt, this window has a context. - None - } else { - Some(unsafe { ImeContext::new(&self.inner.xconn, self.inner.im, window, None) }?) - }; - self.inner.contexts.insert(window, context); - Ok(!self.is_destroyed()) - } - - pub fn get_context(&self, window: ffi::Window) -> Option { - if self.is_destroyed() { - return None; - } - if let Some(&Some(ref context)) = self.inner.contexts.get(&window) { - Some(context.ic) - } else { - None - } - } - - pub fn remove_context(&mut self, window: ffi::Window) -> Result { - if let Some(Some(context)) = self.inner.contexts.remove(&window) { - unsafe { - self.inner.destroy_ic_if_necessary(context.ic)?; - } - Ok(true) - } else { - Ok(false) - } - } - - pub fn focus(&mut self, window: ffi::Window) -> Result { - if self.is_destroyed() { - return Ok(false); - } - if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { - context.focus(&self.xconn).map(|_| true) - } else { - Ok(false) - } - } - - pub fn unfocus(&mut self, window: ffi::Window) -> Result { - if self.is_destroyed() { - return Ok(false); - } - if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { - context.unfocus(&self.xconn).map(|_| true) - } else { - Ok(false) - } - } - - pub fn send_xim_spot(&mut self, window: ffi::Window, x: i16, y: i16) { - if self.is_destroyed() { - return; - } - if let Some(&mut Some(ref mut context)) = self.inner.contexts.get_mut(&window) { - context.set_spot(&self.xconn, x as _, y as _); - } - } -} - -impl Drop for Ime { - fn drop(&mut self) { - unsafe { - let _ = self.inner.destroy_all_contexts_if_necessary(); - let _ = self.inner.close_im_if_necessary(); - } - } -} diff --git a/src/platform_impl/linux/x11/mod.rs b/src/platform_impl/linux/x11/mod.rs index a784e8b2f1..a52d705aa1 100644 --- a/src/platform_impl/linux/x11/mod.rs +++ b/src/platform_impl/linux/x11/mod.rs @@ -8,13 +8,11 @@ mod dnd; mod event_processor; -mod events; -pub mod ffi; -mod ime; mod monitor; pub mod util; mod window; mod xdisplay; +pub mod xlib; pub use self::{ monitor::{MonitorHandle, VideoMode}, @@ -22,23 +20,19 @@ pub use self::{ xdisplay::{XConnection, XError, XNotSupported}, }; +use std::sync::atomic::AtomicUsize; use std::{ cell::RefCell, collections::{HashMap, HashSet}, - ffi::CStr, - mem::{self, MaybeUninit}, ops::Deref, os::raw::*, ptr, rc::Rc, - slice, sync::mpsc::Receiver, - sync::{mpsc, Arc, Weak}, + sync::{Arc, Weak}, time::{Duration, Instant}, }; -use libc::{self, setlocale, LC_CTYPE}; - use mio::{unix::SourceFd, Events, Interest, Poll, Token, Waker}; use mio_misc::{ @@ -50,8 +44,6 @@ use mio_misc::{ use self::{ dnd::{Dnd, DndState}, event_processor::EventProcessor, - ime::{Ime, ImeCreationError, ImeReceiver, ImeSender}, - util::modifiers::ModifierKeymap, }; use crate::{ error::OsError as RootOsError, @@ -61,23 +53,26 @@ use crate::{ window::WindowAttributes, }; +use crate::platform_impl::x11::util::EventQueue; +use xcb_dl::{ffi, XcbXinput}; +use xcb_dl_util::xcb_box::XcbBox; + const X_TOKEN: Token = Token(0); const USER_REDRAW_TOKEN: Token = Token(1); pub struct EventLoopWindowTarget { xconn: Arc, - wm_delete_window: ffi::Atom, - net_wm_ping: ffi::Atom, - ime_sender: ImeSender, - root: ffi::Window, - ime: RefCell, + wm_delete_window: ffi::xcb_atom_t, + net_wm_ping: ffi::xcb_atom_t, windows: RefCell>>, redraw_sender: Sender, + reset_dead_keys: Arc, _marker: ::std::marker::PhantomData, } pub struct EventLoop { poll: Poll, + event_queue: EventQueue, event_processor: EventProcessor, redraw_channel: Receiver, user_channel: Receiver, @@ -99,142 +94,73 @@ impl Clone for EventLoopProxy { impl EventLoop { pub fn new(xconn: Arc) -> EventLoop { - let root = unsafe { (xconn.xlib.XDefaultRootWindow)(xconn.display) }; - - let wm_delete_window = unsafe { xconn.get_atom_unchecked(b"WM_DELETE_WINDOW\0") }; - - let net_wm_ping = unsafe { xconn.get_atom_unchecked(b"_NET_WM_PING\0") }; - - let dnd = Dnd::new(Arc::clone(&xconn)) - .expect("Failed to call XInternAtoms when initializing drag and drop"); - - let (ime_sender, ime_receiver) = mpsc::channel(); - // Input methods will open successfully without setting the locale, but it won't be - // possible to actually commit pre-edit sequences. - unsafe { - // Remember default locale to restore it if target locale is unsupported - // by Xlib - let default_locale = setlocale(LC_CTYPE, ptr::null()); - setlocale(LC_CTYPE, b"\0".as_ptr() as *const _); - - // Check if set locale is supported by Xlib. - // If not, calls to some Xlib functions like `XSetLocaleModifiers` - // will fail. - let locale_supported = (xconn.xlib.XSupportsLocale)() == 1; - if !locale_supported { - let unsupported_locale = setlocale(LC_CTYPE, ptr::null()); - warn!( - "Unsupported locale \"{}\". Restoring default locale \"{}\".", - CStr::from_ptr(unsupported_locale).to_string_lossy(), - CStr::from_ptr(default_locale).to_string_lossy() - ); - // Restore default locale - setlocale(LC_CTYPE, default_locale); - } - } - let ime = RefCell::new({ - let result = Ime::new(Arc::clone(&xconn)); - if let Err(ImeCreationError::OpenFailure(ref state)) = result { - panic!("Failed to open input method: {:#?}", state); - } - result.expect("Failed to set input method destruction callback") - }); - - let randr_event_offset = xconn - .select_xrandr_input(root) - .expect("Failed to query XRandR extension"); - - let xi2ext = unsafe { - let mut ext = XExtension::default(); + let wm_delete_window = xconn.get_atom("WM_DELETE_WINDOW"); - let res = (xconn.xlib.XQueryExtension)( - xconn.display, - b"XInputExtension\0".as_ptr() as *const c_char, - &mut ext.opcode, - &mut ext.first_event_id, - &mut ext.first_error_id, - ); + let net_wm_ping = xconn.get_atom("_NET_WM_PING"); - if res == ffi::False { - panic!("X server missing XInput extension"); - } + let dnd = Dnd::new(Arc::clone(&xconn)); - ext - }; - - unsafe { - let mut xinput_major_ver = ffi::XI_2_Major; - let mut xinput_minor_ver = ffi::XI_2_Minor; - if (xconn.xinput2.XIQueryVersion)( - xconn.display, - &mut xinput_major_ver, - &mut xinput_minor_ver, - ) != ffi::Success as libc::c_int - { - panic!( - "X server has XInput extension {}.{} but does not support XInput2", - xinput_major_ver, xinput_minor_ver, - ); - } - } - - xconn.update_cached_wm_info(root); - - let mut mod_keymap = ModifierKeymap::new(); - mod_keymap.reset_from_x_connection(&xconn); + xconn.update_cached_wm_info(); let poll = Poll::new().unwrap(); let waker = Arc::new(Waker::new(poll.registry(), USER_REDRAW_TOKEN).unwrap()); let queue = Arc::new(NotificationQueue::new(waker)); poll.registry() - .register(&mut SourceFd(&xconn.x11_fd), X_TOKEN, Interest::READABLE) + .register(&mut SourceFd(&xconn.fd), X_TOKEN, Interest::READABLE) .unwrap(); let (user_sender, user_channel) = channel(queue.clone(), NotificationId::gen_next()); let (redraw_sender, redraw_channel) = channel(queue, NotificationId::gen_next()); + let event_queue = EventQueue::new(&xconn); + let target = Rc::new(RootELW { p: super::EventLoopWindowTarget::X(EventLoopWindowTarget { - ime, - root, windows: Default::default(), _marker: ::std::marker::PhantomData, - ime_sender, xconn, wm_delete_window, net_wm_ping, redraw_sender, + reset_dead_keys: Arc::new(AtomicUsize::new(0)), }), _marker: ::std::marker::PhantomData, }); - let event_processor = EventProcessor { + let mut event_processor = EventProcessor { target: target.clone(), dnd, devices: Default::default(), - randr_event_offset, - ime_receiver, - xi2ext, - mod_keymap, - device_mod_state: Default::default(), num_touch: 0, first_touch: None, - active_window: None, + seats: Default::default(), }; // Register for device hotplug events - // (The request buffer is flushed during `init_device`) - get_xtarget(&target) - .xconn - .select_xinput_events(root, ffi::XIAllDevices, ffi::XI_HierarchyChangedMask) - .queue(); + let wt = get_xtarget(&target); + for screen in &wt.xconn.screens { + let pending = wt.xconn.select_xinput_events( + screen.root, + ffi::XCB_INPUT_DEVICE_ALL as _, + ffi::XCB_INPUT_XI_EVENT_MASK_HIERARCHY, + ); + if let Err(e) = wt.xconn.check_pending1(pending) { + log::error!("Cannot listen for device hotplug events: {}", e); + } + } - event_processor.init_device(ffi::XIAllDevices); + EventProcessor::init_device( + &target, + &mut event_processor.devices, + &mut event_processor.seats, + ffi::XCB_INPUT_DEVICE_ALL as _, + ); let result = EventLoop { poll, + event_queue, redraw_channel, user_channel, user_sender, @@ -274,6 +200,9 @@ impl EventLoop { // Process all pending events self.drain_events(&mut callback, &mut control_flow); + // Send unchecked requests + self.flush_requests(); + // Empty the user event buffer { while let Ok(event) = self.user_channel.try_recv() { @@ -322,6 +251,18 @@ impl EventLoop { ); } + if self.event_queue.has_pending_events() { + // If there are pending events that have already been read from the socket + // but not yet dispatched, we HAVE to handle them now. Otherwise, if the + // application is using run_return, it has no way to get notified that it + // should call run_return again. The application will probably try to wait + // for the socket to become readable but that's no good because the socket + // might be empty while we already have events queued. + // + // TODO: Should we change `cause`? + continue; + } + let start = Instant::now(); let (deadline, timeout); @@ -354,12 +295,8 @@ impl EventLoop { } } - // If the XConnection already contains buffered events, we don't - // need to wait for data on the socket. - if !self.event_processor.poll() { - self.poll.poll(&mut events, timeout).unwrap(); - events.clear(); - } + self.poll.poll(&mut events, timeout).unwrap(); + events.clear(); let wait_cancelled = deadline.map_or(false, |deadline| Instant::now() < deadline); @@ -391,13 +328,11 @@ impl EventLoop { F: FnMut(Event<'_, T>, &RootELW, &mut ControlFlow), { let target = &self.target; - let mut xev = MaybeUninit::uninit(); let wt = get_xtarget(&self.target); - while unsafe { self.event_processor.poll_one_event(xev.as_mut_ptr()) } { - let mut xev = unsafe { xev.assume_init() }; - self.event_processor.process_event(&mut xev, |event| { + while let Some(mut event) = self.event_queue.poll_for_event() { + self.event_processor.process_event(&mut *event, |event| { sticky_exit_callback( event, target, @@ -416,6 +351,13 @@ impl EventLoop { }); } } + + fn flush_requests(&self) { + let wt = get_xtarget(&self.target); + if let Err(e) = wt.xconn.flush() { + panic!("The connection to the X server failed: {}", e); + } + } } pub(crate) fn get_xtarget(target: &RootELW) -> &EventLoopWindowTarget { @@ -448,57 +390,71 @@ impl EventLoopProxy { struct DeviceInfo<'a> { xconn: &'a XConnection, - info: *const ffi::XIDeviceInfo, - count: usize, + _reply: XcbBox, + iter: ffi::xcb_input_xi_device_info_iterator_t, } impl<'a> DeviceInfo<'a> { - fn get(xconn: &'a XConnection, device: c_int) -> Option { + fn get(xconn: &'a XConnection, device: ffi::xcb_input_device_id_t) -> Option { unsafe { - let mut count = 0; - let info = (xconn.xinput2.XIQueryDevice)(xconn.display, device, &mut count); - xconn.check_errors().ok()?; - - if info.is_null() || count == 0 { - None - } else { - Some(DeviceInfo { - xconn, - info, - count: count as usize, - }) - } + let mut err = ptr::null_mut(); + let reply = xconn.xinput.xcb_input_xi_query_device_reply( + xconn.c, + xconn.xinput.xcb_input_xi_query_device(xconn.c, device), + &mut err, + ); + let reply = match xconn.check(reply, err) { + Ok(i) => i, + Err(e) => { + log::error!("Could not query device data: {}", e); + return None; + } + }; + let iter = xconn + .xinput + .xcb_input_xi_query_device_infos_iterator(&*reply); + Some(DeviceInfo { + xconn, + _reply: reply, + iter, + }) } } } -impl<'a> Drop for DeviceInfo<'a> { - fn drop(&mut self) { - assert!(!self.info.is_null()); - unsafe { (self.xconn.xinput2.XIFreeDeviceInfo)(self.info as *mut _) }; - } -} +impl<'a> Iterator for DeviceInfo<'a> { + type Item = *mut ffi::xcb_input_xi_device_info_t; -impl<'a> Deref for DeviceInfo<'a> { - type Target = [ffi::XIDeviceInfo]; - fn deref(&self) -> &Self::Target { - unsafe { slice::from_raw_parts(self.info, self.count) } + fn next(&mut self) -> Option { + if self.iter.rem > 0 { + let data = self.iter.data; + unsafe { + self.xconn + .xinput + .xcb_input_xi_device_info_next(&mut self.iter); + } + Some(data) + } else { + None + } } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct WindowId(ffi::Window); +pub struct WindowId(ffi::xcb_window_t); impl WindowId { + #[cfg(not(feature = "wayland"))] pub unsafe fn dummy() -> Self { WindowId(0) } } #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] -pub struct DeviceId(c_int); +pub struct DeviceId(pub(crate) ffi::xcb_input_device_id_t); impl DeviceId { + #[cfg(not(feature = "wayland"))] pub unsafe fn dummy() -> Self { DeviceId(0) } @@ -534,44 +490,14 @@ impl Drop for Window { let window = self.deref(); let xconn = &window.xconn; unsafe { - (xconn.xlib.XDestroyWindow)(xconn.display, window.id().0); - // If the window was somehow already destroyed, we'll get a `BadWindow` error, which we don't care about. - let _ = xconn.check_errors(); - } - } -} - -/// XEvents of type GenericEvent store their actual data in an XGenericEventCookie data structure. This is a wrapper to -/// extract the cookie from a GenericEvent XEvent and release the cookie data once it has been processed -struct GenericEventCookie<'a> { - xconn: &'a XConnection, - cookie: ffi::XGenericEventCookie, -} - -impl<'a> GenericEventCookie<'a> { - fn from_event<'b>( - xconn: &'b XConnection, - event: ffi::XEvent, - ) -> Option> { - unsafe { - let mut cookie: ffi::XGenericEventCookie = From::from(event); - if (xconn.xlib.XGetEventData)(xconn.display, &mut cookie) == ffi::True { - Some(GenericEventCookie { xconn, cookie }) - } else { - None + let cookie = xconn.xcb.xcb_destroy_window_checked(xconn.c, window.id().0); + if let Err(e) = xconn.check_cookie(cookie) { + log::error!("Could not destroy window: {}", e); } } } } -impl<'a> Drop for GenericEventCookie<'a> { - fn drop(&mut self) { - unsafe { - (self.xconn.xlib.XFreeEventData)(self.xconn.display, &mut self.cookie); - } - } -} - #[derive(Debug, Default, Copy, Clone)] struct XExtension { opcode: c_int, @@ -579,20 +505,20 @@ struct XExtension { first_error_id: c_int, } -fn mkwid(w: ffi::Window) -> crate::window::WindowId { +fn mkwid(w: ffi::xcb_window_t) -> crate::window::WindowId { crate::window::WindowId(crate::platform_impl::WindowId::X(WindowId(w))) } -fn mkdid(w: c_int) -> crate::event::DeviceId { +fn mkdid(w: ffi::xcb_input_device_id_t) -> crate::event::DeviceId { crate::event::DeviceId(crate::platform_impl::DeviceId::X(DeviceId(w))) } #[derive(Debug)] struct Device { name: String, - scroll_axes: Vec<(i32, ScrollAxis)>, + scroll_axes: Vec<(u16, ScrollAxis)>, // For master devices, this is the paired device (pointer <-> keyboard). // For slave devices, this is the master. - attachment: c_int, + attachment: ffi::xcb_input_device_id_t, } #[derive(Debug, Copy, Clone)] @@ -609,39 +535,73 @@ enum ScrollOrientation { } impl Device { - fn new(el: &EventProcessor, info: &ffi::XIDeviceInfo) -> Self { - let name = unsafe { CStr::from_ptr(info.name).to_string_lossy() }; + fn new( + wt: &EventLoopWindowTarget, + info: &ffi::xcb_input_xi_device_info_t, + ) -> Self { + let name = unsafe { + let name = std::slice::from_raw_parts( + wt.xconn.xinput.xcb_input_xi_device_info_name(info) as _, + info.name_len as _, + ); + String::from_utf8_lossy(name) + }; let mut scroll_axes = Vec::new(); - let wt = get_xtarget(&el.target); + if info.type_ == ffi::XCB_INPUT_DEVICE_TYPE_MASTER_KEYBOARD as u16 { + let pending = wt.xconn.select_xkb_events( + info.deviceid, + ffi::XCB_XKB_EVENT_TYPE_NEW_KEYBOARD_NOTIFY | ffi::XCB_XKB_EVENT_TYPE_MAP_NOTIFY, + ); + if let Err(e) = wt.xconn.check_pending1(pending) { + log::error!( + "Cannot listen for keyboard layout changes of keyboard {}: {}", + info.deviceid, + e + ); + } + } if Device::physical_device(info) { // Register for global raw events - let mask = ffi::XI_RawMotionMask - | ffi::XI_RawButtonPressMask - | ffi::XI_RawButtonReleaseMask - | ffi::XI_RawKeyPressMask - | ffi::XI_RawKeyReleaseMask; - // The request buffer is flushed when we poll for events - wt.xconn - .select_xinput_events(wt.root, info.deviceid, mask) - .queue(); + let mask = ffi::XCB_INPUT_XI_EVENT_MASK_RAW_MOTION + | ffi::XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS + | ffi::XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE + | ffi::XCB_INPUT_XI_EVENT_MASK_RAW_KEY_PRESS + | ffi::XCB_INPUT_XI_EVENT_MASK_RAW_KEY_RELEASE; + for screen in &wt.xconn.screens { + let pending = wt + .xconn + .select_xinput_events(screen.root, info.deviceid, mask); + if let Err(e) = wt.xconn.check_pending1(pending) { + log::error!( + "Cannot listen for raw input events of device {}: {}", + info.deviceid, + e + ); + } + } // Identify scroll axes - for class_ptr in Device::classes(info) { - let class = unsafe { &**class_ptr }; - match class._type { - ffi::XIScrollClass => { + let classes = unsafe { Classes::new(&wt.xconn.xinput, info) }; + for class in classes { + match class.type_ as ffi::xcb_input_device_class_type_t { + ffi::XCB_INPUT_DEVICE_CLASS_TYPE_SCROLL => { let info = unsafe { - mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIScrollClassInfo>(class) + &*(class as *const _ as *const ffi::xcb_input_scroll_class_t) }; scroll_axes.push(( info.number, ScrollAxis { - increment: info.increment, - orientation: match info.scroll_type { - ffi::XIScrollTypeHorizontal => ScrollOrientation::Horizontal, - ffi::XIScrollTypeVertical => ScrollOrientation::Vertical, + increment: util::fp3232_to_f64(info.increment), + orientation: match info.scroll_type as ffi::xcb_input_scroll_type_t + { + ffi::XCB_INPUT_SCROLL_TYPE_HORIZONTAL => { + ScrollOrientation::Horizontal + } + ffi::XCB_INPUT_SCROLL_TYPE_VERTICAL => { + ScrollOrientation::Vertical + } _ => unreachable!(), }, position: 0.0, @@ -658,25 +618,29 @@ impl Device { scroll_axes, attachment: info.attachment, }; - device.reset_scroll_position(info); + device.reset_scroll_position(wt, info); device } - fn reset_scroll_position(&mut self, info: &ffi::XIDeviceInfo) { + fn reset_scroll_position( + &mut self, + wt: &EventLoopWindowTarget, + info: &ffi::xcb_input_xi_device_info_t, + ) { if Device::physical_device(info) { - for class_ptr in Device::classes(info) { - let class = unsafe { &**class_ptr }; - match class._type { - ffi::XIValuatorClass => { + let classes = unsafe { Classes::new(&wt.xconn.xinput, info) }; + for class in classes { + match class.type_ as ffi::xcb_input_device_class_type_t { + ffi::XCB_INPUT_DEVICE_CLASS_TYPE_VALUATOR => { let info = unsafe { - mem::transmute::<&ffi::XIAnyClassInfo, &ffi::XIValuatorClassInfo>(class) + &*(class as *const _ as *const ffi::xcb_input_valuator_class_t) }; if let Some(&mut (_, ref mut axis)) = self .scroll_axes .iter_mut() .find(|&&mut (axis, _)| axis == info.number) { - axis.position = info.value; + axis.position = util::fp3232_to_f64(info.value); } } _ => {} @@ -686,19 +650,40 @@ impl Device { } #[inline] - fn physical_device(info: &ffi::XIDeviceInfo) -> bool { - info._use == ffi::XISlaveKeyboard - || info._use == ffi::XISlavePointer - || info._use == ffi::XIFloatingSlave + fn physical_device(info: &ffi::xcb_input_xi_device_info_t) -> bool { + let ty = info.type_ as ffi::xcb_input_device_type_t; + ty == ffi::XCB_INPUT_DEVICE_TYPE_SLAVE_KEYBOARD + || ty == ffi::XCB_INPUT_DEVICE_TYPE_SLAVE_POINTER + || ty == ffi::XCB_INPUT_DEVICE_TYPE_FLOATING_SLAVE } +} - #[inline] - fn classes(info: &ffi::XIDeviceInfo) -> &[*const ffi::XIAnyClassInfo] { - unsafe { - slice::from_raw_parts( - info.classes as *const *const ffi::XIAnyClassInfo, - info.num_classes as usize, - ) +struct Classes<'a> { + xinput: &'a XcbXinput, + iter: ffi::xcb_input_device_class_iterator_t, +} + +impl<'a> Classes<'a> { + unsafe fn new(xinput: &'a XcbXinput, info: &'a ffi::xcb_input_xi_device_info_t) -> Self { + Self { + xinput, + iter: xinput.xcb_input_xi_device_info_classes_iterator(info), + } + } +} + +impl<'a> Iterator for Classes<'a> { + type Item = &'a ffi::xcb_input_device_class_t; + + fn next(&mut self) -> Option { + if self.iter.rem > 0 { + unsafe { + let res = &*self.iter.data; + self.xinput.xcb_input_device_class_next(&mut self.iter); + Some(res) + } + } else { + None } } } diff --git a/src/platform_impl/linux/x11/monitor.rs b/src/platform_impl/linux/x11/monitor.rs index 274e0cda9f..ae009c51bd 100644 --- a/src/platform_impl/linux/x11/monitor.rs +++ b/src/platform_impl/linux/x11/monitor.rs @@ -1,38 +1,24 @@ -use std::os::raw::*; - -use parking_lot::Mutex; - -use super::{ - ffi::{ - RRCrtc, RRCrtcChangeNotifyMask, RRMode, RROutputPropertyNotifyMask, - RRScreenChangeNotifyMask, True, Window, XRRCrtcInfo, XRRScreenResources, - }, - util, XConnection, XError, -}; +use super::{ffi, util, XConnection}; +use crate::platform_impl::x11::xdisplay::Screen; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform_impl::{MonitorHandle as PlatformMonitorHandle, VideoMode as PlatformVideoMode}, }; +use std::ptr; +use std::sync::Arc; +use xcb_dl_util::error::XcbError; +use xcb_dl_util::xcb_box::XcbBox; // Used for testing. This should always be committed as false. const DISABLE_MONITOR_LIST_CACHING: bool = false; -lazy_static! { - static ref MONITORS: Mutex>> = Mutex::default(); -} - -pub fn invalidate_cached_monitor_list() -> Option> { - // We update this lazily. - (*MONITORS.lock()).take() -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub struct VideoMode { - pub(crate) size: (u32, u32), + pub(crate) size: (u16, u16), pub(crate) bit_depth: u16, pub(crate) refresh_rate: u16, - pub(crate) native_mode: RRMode, + pub(crate) native_mode: u32, pub(crate) monitor: Option, } @@ -63,9 +49,11 @@ impl VideoMode { #[derive(Debug, Clone)] pub struct MonitorHandle { /// The actual id - pub(crate) id: RRCrtc, + pub(crate) id: ffi::xcb_randr_crtc_t, /// The name of the monitor pub(crate) name: String, + /// The screen this monitor is attached to + pub(crate) screen: Option>, /// The size of the monitor dimensions: (u32, u32), /// The position of the monitor in the X screen @@ -109,31 +97,38 @@ impl std::hash::Hash for MonitorHandle { impl MonitorHandle { fn new( xconn: &XConnection, - resources: *mut XRRScreenResources, - id: RRCrtc, - crtc: *mut XRRCrtcInfo, + screen: &Arc, + resources: &ffi::xcb_randr_get_screen_resources_reply_t, + id: ffi::xcb_randr_crtc_t, + crtc: &ffi::xcb_randr_get_crtc_info_reply_t, primary: bool, - ) -> Option { - let (name, scale_factor, video_modes) = unsafe { xconn.get_output_info(resources, crtc)? }; - let dimensions = unsafe { ((*crtc).width as u32, (*crtc).height as u32) }; - let position = unsafe { ((*crtc).x as i32, (*crtc).y as i32) }; + ) -> Result, XcbError> { + let output_info = unsafe { xconn.get_output_info(screen, resources, crtc)? }; + let (name, scale_factor, video_modes) = match output_info { + Some(o) => o, + _ => return Ok(None), + }; + let dimensions = (crtc.width as u32, crtc.height as u32); + let position = (crtc.x as i32, crtc.y as i32); let rect = util::AaRect::new(position, dimensions); - Some(MonitorHandle { + Ok(Some(MonitorHandle { id, name, + screen: Some(screen.clone()), scale_factor, dimensions, position, primary, rect, video_modes, - }) + })) } pub fn dummy() -> Self { MonitorHandle { id: 0, name: "".into(), + screen: None, scale_factor: 1.0, dimensions: (1, 1), position: (0, 0), @@ -184,7 +179,13 @@ impl MonitorHandle { impl XConnection { pub fn get_monitor_for_window(&self, window_rect: Option) -> MonitorHandle { - let monitors = self.available_monitors(); + let monitors = match self.available_monitors_inner() { + Ok(m) => m, + Err(e) => { + log::error!("Could not retrieve monitors: {}", e); + return MonitorHandle::dummy(); + } + }; if monitors.is_empty() { // Return a dummy monitor to avoid panicking @@ -211,41 +212,51 @@ impl XConnection { matched_monitor.to_owned() } - fn query_monitor_list(&self) -> Vec { + unsafe fn screen_resources( + &self, + screen: &Screen, + ) -> Result, XcbError> { + let cookie = if self.randr_version >= (1, 3) { + self.randr + .xcb_randr_get_screen_resources_current(self.c, screen.root) + .sequence + } else { + self.randr + .xcb_randr_get_screen_resources(self.c, screen.root) + .sequence + }; + let mut err = ptr::null_mut(); + let reply = self.xcb.xcb_wait_for_reply(self.c, cookie, &mut err) as *mut _; + self.check(reply, err) + } + + fn query_monitor_list(&self, screen: &Arc) -> Result, XcbError> { unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); - - let root = (self.xlib.XDefaultRootWindow)(self.display); - let resources = if (major == 1 && minor >= 3) || major > 1 { - (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) - } else { - // WARNING: this function is supposedly very slow, on the order of hundreds of ms. - // Upon failure, `resources` will be null. - (self.xrandr.XRRGetScreenResources)(self.display, root) - }; - - if resources.is_null() { - panic!("[winit] `XRRGetScreenResources` returned NULL. That should only happen if the root window doesn't exist."); - } + let resources = self.screen_resources(screen)?; + let crtcs = std::slice::from_raw_parts( + self.randr.xcb_randr_get_screen_resources_crtcs(&*resources), + resources.num_crtcs as _, + ); - let mut available; let mut has_primary = false; - let primary = (self.xrandr.XRRGetOutputPrimary)(self.display, root); - available = Vec::with_capacity((*resources).ncrtc as usize); - for crtc_index in 0..(*resources).ncrtc { - let crtc_id = *((*resources).crtcs.offset(crtc_index as isize)); - let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); - let is_active = (*crtc).width > 0 && (*crtc).height > 0 && (*crtc).noutput > 0; + let mut err = ptr::null_mut(); + let primary = self.randr.xcb_randr_get_output_primary_reply( + self.c, + self.randr.xcb_randr_get_output_primary(self.c, screen.root), + &mut err, + ); + let primary = self.check(primary, err)?.output; + let mut available = Vec::with_capacity(crtcs.len()); + for &crtc_id in crtcs { + let crtc = self.get_crtc_info(crtc_id)?; + let is_active = crtc.width > 0 && crtc.height > 0 && crtc.num_outputs > 0; if is_active { - let is_primary = *(*crtc).outputs.offset(0) == primary; + let is_primary = *self.randr.xcb_randr_get_crtc_info_outputs(&*crtc) == primary; has_primary |= is_primary; - MonitorHandle::new(self, resources, crtc_id, crtc, is_primary) + MonitorHandle::new(self, screen, &resources, crtc_id, &crtc, is_primary)? .map(|monitor_id| available.push(monitor_id)); } - (self.xrandr.XRRFreeCrtcInfo)(crtc); } // If no monitors were detected as being primary, we just pick one ourselves! @@ -256,59 +267,48 @@ impl XConnection { } } - (self.xrandr.XRRFreeScreenResources)(resources); - available + Ok(available) } } pub fn available_monitors(&self) -> Vec { - let mut monitors_lock = MONITORS.lock(); - (*monitors_lock) - .as_ref() - .cloned() - .or_else(|| { - let monitors = Some(self.query_monitor_list()); - if !DISABLE_MONITOR_LIST_CACHING { - (*monitors_lock) = monitors.clone(); - } - monitors - }) - .unwrap() + match self.available_monitors_inner() { + Ok(m) => m, + Err(e) => { + log::error!("Could not retrive monitors: {}", e); + vec![] + } + } } - #[inline] - pub fn primary_monitor(&self) -> MonitorHandle { - self.available_monitors() - .into_iter() - .find(|monitor| monitor.primary) - .unwrap_or_else(MonitorHandle::dummy) + pub fn available_monitors_inner(&self) -> Result, XcbError> { + let mut monitors_lock = self.monitors.lock(); + if let Some(monitors) = &*monitors_lock { + return Ok(monitors.clone()); + } + let mut monitors = vec![]; + for screen in &self.screens { + monitors.extend(self.query_monitor_list(screen)?); + } + if !DISABLE_MONITOR_LIST_CACHING { + (*monitors_lock) = Some(monitors.clone()); + } + Ok(monitors) } - pub fn select_xrandr_input(&self, root: Window) -> Result { - let has_xrandr = unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor) - }; - assert!( - has_xrandr == True, - "[winit] XRandR extension not available." - ); - - let mut event_offset = 0; - let mut error_offset = 0; - let status = unsafe { - (self.xrandr.XRRQueryExtension)(self.display, &mut event_offset, &mut error_offset) - }; - - if status != True { - self.check_errors()?; - unreachable!("[winit] `XRRQueryExtension` failed but no error was received."); + #[inline] + pub fn primary_monitor(&self) -> MonitorHandle { + match self.available_monitors_inner() { + Ok(m) => m + .into_iter() + .find(|monitor| monitor.primary) + .unwrap_or_else(MonitorHandle::dummy), + _ => MonitorHandle::dummy(), } + } - let mask = RRCrtcChangeNotifyMask | RROutputPropertyNotifyMask | RRScreenChangeNotifyMask; - unsafe { (self.xrandr.XRRSelectInput)(self.display, root, mask) }; - - Ok(event_offset) + pub fn invalidate_cached_monitor_list(&self) -> Option> { + // We update this lazily. + (*self.monitors.lock()).take() } } diff --git a/src/platform_impl/linux/x11/util/atom.rs b/src/platform_impl/linux/x11/util/atom.rs index 4138722483..ba0466a27f 100644 --- a/src/platform_impl/linux/x11/util/atom.rs +++ b/src/platform_impl/linux/x11/util/atom.rs @@ -1,71 +1,29 @@ -use std::{ - collections::HashMap, - ffi::{CStr, CString}, - fmt::Debug, - os::raw::*, -}; - -use parking_lot::Mutex; - use super::*; -type AtomCache = HashMap; - -lazy_static! { - static ref ATOM_CACHE: Mutex = Mutex::new(HashMap::with_capacity(2048)); -} - impl XConnection { - pub fn get_atom + Debug>(&self, name: T) -> ffi::Atom { - let name = name.as_ref(); - let mut atom_cache_lock = ATOM_CACHE.lock(); + pub fn get_atom(&self, name: &str) -> ffi::xcb_atom_t { + let mut atom_cache_lock = self.atom_cache.lock(); let cached_atom = (*atom_cache_lock).get(name).cloned(); if let Some(atom) = cached_atom { atom } else { - let atom = unsafe { - (self.xlib.XInternAtom)(self.display, name.as_ptr() as *const c_char, ffi::False) - }; - if atom == 0 { - panic!( - "`XInternAtom` failed, which really shouldn't happen. Atom: {:?}, Error: {:#?}", - name, - self.check_errors(), - ); - } - /*println!( - "XInternAtom name:{:?} atom:{:?}", - name, - atom, - );*/ + let atom = self.get_atom_uncached(name); (*atom_cache_lock).insert(name.to_owned(), atom); atom } } - pub unsafe fn get_atom_unchecked(&self, name: &[u8]) -> ffi::Atom { - debug_assert!(CStr::from_bytes_with_nul(name).is_ok()); - let name = CStr::from_bytes_with_nul_unchecked(name); - self.get_atom(name) - } - - // Note: this doesn't use caching, for the sake of simplicity. - // If you're dealing with this many atoms, you'll usually want to cache them locally anyway. - pub unsafe fn get_atoms(&self, names: &[*mut c_char]) -> Result, XError> { - let mut atoms = Vec::with_capacity(names.len()); - (self.xlib.XInternAtoms)( - self.display, - names.as_ptr() as *mut _, - names.len() as c_int, - ffi::False, - atoms.as_mut_ptr(), - ); - self.check_errors()?; - atoms.set_len(names.len()); - /*println!( - "XInternAtoms atoms:{:?}", - atoms, - );*/ - Ok(atoms) + pub fn get_atom_uncached(&self, name: &str) -> ffi::xcb_atom_t { + unsafe { + let cookie = self + .xcb + .xcb_intern_atom(self.c, 0, name.len() as _, name.as_ptr() as _); + let mut err = ptr::null_mut(); + let reply = self.xcb.xcb_intern_atom_reply(self.c, cookie, &mut err); + match self.check(reply, err) { + Ok(r) => r.atom, + Err(e) => panic!("Could not intern the atom `{}`: {}", name, e), + } + } } } diff --git a/src/platform_impl/linux/x11/util/client_msg.rs b/src/platform_impl/linux/x11/util/client_msg.rs index 2f2b7edf9c..8679de90fa 100644 --- a/src/platform_impl/linux/x11/util/client_msg.rs +++ b/src/platform_impl/linux/x11/util/client_msg.rs @@ -1,46 +1,72 @@ use super::*; - -pub type ClientMsgPayload = [c_long; 5]; +use xcb_dl_util::format::XcbDataType; +use xcb_dl_util::void::XcbPendingCommand; impl XConnection { - pub fn send_event>( + pub fn send_event( &self, - target_window: c_ulong, - event_mask: Option, - event: T, - ) -> Flusher<'_> { - let event_mask = event_mask.unwrap_or(ffi::NoEventMask); + target_window: ffi::xcb_window_t, + event_mask: Option, + event: &T, + ) -> XcbPendingCommand { + assert_eq!( + mem::size_of::(), + mem::size_of::() + ); + let event_mask = event_mask.unwrap_or(ffi::XCB_EVENT_MASK_NO_EVENT); unsafe { - (self.xlib.XSendEvent)( - self.display, - target_window, - ffi::False, - event_mask, - &mut event.into(), - ); + self.xcb + .xcb_send_event_checked( + self.c, + 0, + target_window, + event_mask, + event as *const _ as _, + ) + .into() } - Flusher::new(self) } - pub fn send_client_msg( + pub fn send_client_msg( &self, - window: c_ulong, // The window this is "about"; not necessarily this window - target_window: c_ulong, // The window we're sending to - message_type: ffi::Atom, - event_mask: Option, - data: ClientMsgPayload, - ) -> Flusher<'_> { - let event = ffi::XClientMessageEvent { - type_: ffi::ClientMessage, - display: self.display, + window: ffi::xcb_window_t, // The window this is "about"; not necessarily this window + target_window: ffi::xcb_window_t, // The window we're sending to + message_type: ffi::xcb_atom_t, + event_mask: Option, + data: T, + ) -> XcbPendingCommand { + let event = ffi::xcb_client_message_event_t { + response_type: ffi::XCB_CLIENT_MESSAGE, + format: T::DataType::XCB_BITS, + type_: message_type, window, - message_type, - format: c_long::FORMAT as c_int, - data: unsafe { mem::transmute(data) }, - // These fields are ignored by `XSendEvent` - serial: 0, - send_event: 0, + data: unsafe { + assert_eq!(mem::size_of_val(&data), mem::size_of::<[u8; 20]>()); + ffi::xcb_client_message_data_t { + data8: *(&data as *const T as *const [u8; 20]), + } + }, + ..Default::default() }; - self.send_event(target_window, event_mask, event) + self.send_event(target_window, event_mask, &event) } } + +pub unsafe trait ClientMessageType { + type DataType: XcbDataType; +} + +macro_rules! imp { + ($ty:ty, $n:expr) => { + unsafe impl ClientMessageType for [$ty; $n] { + type DataType = $ty; + } + }; +} + +imp!(u8, 20); +imp!(u16, 10); +imp!(u32, 5); +imp!(i8, 20); +imp!(i16, 10); +imp!(i32, 5); diff --git a/src/platform_impl/linux/x11/util/cursor.rs b/src/platform_impl/linux/x11/util/cursor.rs index 684af49da9..40271474ad 100644 --- a/src/platform_impl/linux/x11/util/cursor.rs +++ b/src/platform_impl/linux/x11/util/cursor.rs @@ -1,9 +1,11 @@ use crate::window::CursorIcon; use super::*; +use std::slice; +use xcb_dl_util::cursor::{XcbCursorImage, XcbLoadCursorConfig}; impl XConnection { - pub fn set_cursor_icon(&self, window: ffi::Window, cursor: Option) { + pub fn set_cursor_icon(&self, window: ffi::xcb_window_t, cursor: Option) { let cursor = *self .cursor_cache .lock() @@ -13,117 +15,127 @@ impl XConnection { self.update_cursor(window, cursor); } - fn create_empty_cursor(&self) -> ffi::Cursor { - let data = 0; - let pixmap = unsafe { - let screen = (self.xlib.XDefaultScreen)(self.display); - let window = (self.xlib.XRootWindow)(self.display, screen); - (self.xlib.XCreateBitmapFromData)(self.display, window, &data, 1, 1) + fn create_empty_cursor(&self) -> ffi::xcb_cursor_t { + let image = XcbCursorImage { + width: 1, + height: 1, + xhot: 0, + yhot: 0, + delay: 0, + pixels: vec![0], + ..Default::default() }; - - if pixmap == 0 { - panic!("failed to allocate pixmap for cursor"); - } - unsafe { - // We don't care about this color, since it only fills bytes - // in the pixmap which are not 0 in the mask. - let mut dummy_color = MaybeUninit::uninit(); - let cursor = (self.xlib.XCreatePixmapCursor)( - self.display, - pixmap, - pixmap, - dummy_color.as_mut_ptr(), - dummy_color.as_mut_ptr(), - 0, - 0, - ); - (self.xlib.XFreePixmap)(self.display, pixmap); - - cursor + let cursor = + self.cursors + .create_cursor(&self.xcb, &self.render, slice::from_ref(&image)); + match cursor { + Ok(c) => c, + Err(e) => { + log::error!("Could not create empty cursor: {}", e); + 0 + } + } } } - fn load_cursor(&self, name: &[u8]) -> ffi::Cursor { + fn load_cursor(&self, name: &str) -> Option { unsafe { - (self.xcursor.XcursorLibraryLoadCursor)(self.display, name.as_ptr() as *const c_char) + let config = XcbLoadCursorConfig { + name, + ..Default::default() + }; + match self.cursors.load_cursor(&self.xcb, &self.render, &config) { + Ok(c) => Some(c), + Err(e) => { + log::debug!("Could not load cursor {}: {}", name, e); + None + } + } } } - fn load_first_existing_cursor(&self, names: &[&[u8]]) -> ffi::Cursor { + fn load_first_existing_cursor(&self, names: &[&str]) -> Option { for name in names.iter() { - let xcursor = self.load_cursor(name); - if xcursor != 0 { - return xcursor; + if let Some(xcursor) = self.load_cursor(name) { + return Some(xcursor); } } - 0 + None } - fn get_cursor(&self, cursor: Option) -> ffi::Cursor { + fn get_cursor(&self, cursor: Option) -> ffi::xcb_cursor_t { let cursor = match cursor { Some(cursor) => cursor, None => return self.create_empty_cursor(), }; - let load = |name: &[u8]| self.load_cursor(name); + let load = |name: &str| self.load_cursor(name); - let loadn = |names: &[&[u8]]| self.load_first_existing_cursor(names); + let loadn = |names: &[&str]| self.load_first_existing_cursor(names); // Try multiple names in some cases where the name // differs on the desktop environments or themes. // // Try the better looking (or more suiting) names first. - match cursor { - CursorIcon::Alias => load(b"link\0"), - CursorIcon::Arrow => load(b"arrow\0"), - CursorIcon::Cell => load(b"plus\0"), - CursorIcon::Copy => load(b"copy\0"), - CursorIcon::Crosshair => load(b"crosshair\0"), - CursorIcon::Default => load(b"left_ptr\0"), - CursorIcon::Hand => loadn(&[b"hand2\0", b"hand1\0"]), - CursorIcon::Help => load(b"question_arrow\0"), - CursorIcon::Move => load(b"move\0"), - CursorIcon::Grab => loadn(&[b"openhand\0", b"grab\0"]), - CursorIcon::Grabbing => loadn(&[b"closedhand\0", b"grabbing\0"]), - CursorIcon::Progress => load(b"left_ptr_watch\0"), - CursorIcon::AllScroll => load(b"all-scroll\0"), - CursorIcon::ContextMenu => load(b"context-menu\0"), - - CursorIcon::NoDrop => loadn(&[b"no-drop\0", b"circle\0"]), - CursorIcon::NotAllowed => load(b"crossed_circle\0"), + let cursor = match cursor { + CursorIcon::Alias => load("link"), + CursorIcon::Arrow => load("arrow"), + CursorIcon::Cell => load("plus"), + CursorIcon::Copy => load("copy"), + CursorIcon::Crosshair => load("crosshair"), + CursorIcon::Default => load("left_ptr"), + CursorIcon::Hand => loadn(&["hand2", "hand1"]), + CursorIcon::Help => load("question_arrow"), + CursorIcon::Move => load("move"), + CursorIcon::Grab => loadn(&["openhand", "grab"]), + CursorIcon::Grabbing => loadn(&["closedhand", "grabbing"]), + CursorIcon::Progress => load("left_ptr_watch"), + CursorIcon::AllScroll => load("all-scroll"), + CursorIcon::ContextMenu => load("context-menu"), + + CursorIcon::NoDrop => loadn(&["no-drop", "circle"]), + CursorIcon::NotAllowed => load("crossed_circle"), // Resize cursors - CursorIcon::EResize => load(b"right_side\0"), - CursorIcon::NResize => load(b"top_side\0"), - CursorIcon::NeResize => load(b"top_right_corner\0"), - CursorIcon::NwResize => load(b"top_left_corner\0"), - CursorIcon::SResize => load(b"bottom_side\0"), - CursorIcon::SeResize => load(b"bottom_right_corner\0"), - CursorIcon::SwResize => load(b"bottom_left_corner\0"), - CursorIcon::WResize => load(b"left_side\0"), - CursorIcon::EwResize => load(b"h_double_arrow\0"), - CursorIcon::NsResize => load(b"v_double_arrow\0"), - CursorIcon::NwseResize => loadn(&[b"bd_double_arrow\0", b"size_bdiag\0"]), - CursorIcon::NeswResize => loadn(&[b"fd_double_arrow\0", b"size_fdiag\0"]), - CursorIcon::ColResize => loadn(&[b"split_h\0", b"h_double_arrow\0"]), - CursorIcon::RowResize => loadn(&[b"split_v\0", b"v_double_arrow\0"]), - - CursorIcon::Text => loadn(&[b"text\0", b"xterm\0"]), - CursorIcon::VerticalText => load(b"vertical-text\0"), - - CursorIcon::Wait => load(b"watch\0"), - - CursorIcon::ZoomIn => load(b"zoom-in\0"), - CursorIcon::ZoomOut => load(b"zoom-out\0"), - } + CursorIcon::EResize => load("right_side"), + CursorIcon::NResize => load("top_side"), + CursorIcon::NeResize => load("top_right_corner"), + CursorIcon::NwResize => load("top_left_corner"), + CursorIcon::SResize => load("bottom_side"), + CursorIcon::SeResize => load("bottom_right_corner"), + CursorIcon::SwResize => load("bottom_left_corner"), + CursorIcon::WResize => load("left_side"), + CursorIcon::EwResize => load("h_double_arrow"), + CursorIcon::NsResize => load("v_double_arrow"), + CursorIcon::NwseResize => loadn(&["bd_double_arrow", "size_bdiag"]), + CursorIcon::NeswResize => loadn(&["fd_double_arrow", "size_fdiag"]), + CursorIcon::ColResize => loadn(&["split_h", "h_double_arrow"]), + CursorIcon::RowResize => loadn(&["split_v", "v_double_arrow"]), + + CursorIcon::Text => loadn(&["text", "xterm"]), + CursorIcon::VerticalText => load("vertical-text"), + + CursorIcon::Wait => load("watch"), + + CursorIcon::ZoomIn => load("zoom-in"), + CursorIcon::ZoomOut => load("zoom-out"), + }; + + cursor.unwrap_or(0) } - fn update_cursor(&self, window: ffi::Window, cursor: ffi::Cursor) { + fn update_cursor(&self, window: ffi::xcb_window_t, cursor: ffi::xcb_cursor_t) { unsafe { - (self.xlib.XDefineCursor)(self.display, window, cursor); - - self.flush_requests().expect("Failed to set the cursor"); + let cookie = self.xcb.xcb_change_window_attributes( + self.c, + window, + ffi::XCB_CW_CURSOR, + &cursor as *const _ as _, + ); + if let Err(e) = self.check_cookie(cookie) { + log::error!("Failed to set the cursor: {}", e); + } } } } diff --git a/src/platform_impl/linux/x11/util/format.rs b/src/platform_impl/linux/x11/util/format.rs deleted file mode 100644 index 1ab5e01f7e..0000000000 --- a/src/platform_impl/linux/x11/util/format.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::{fmt::Debug, mem, os::raw::*}; - -// This isn't actually the number of the bits in the format. -// X11 does a match on this value to determine which type to call sizeof on. -// Thus, we use 32 for c_long, since 32 maps to c_long which maps to 64. -// ...if that sounds confusing, then you know why this enum is here. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum Format { - Char = 8, - Short = 16, - Long = 32, -} - -impl Format { - pub fn from_format(format: usize) -> Option { - match format { - 8 => Some(Format::Char), - 16 => Some(Format::Short), - 32 => Some(Format::Long), - _ => None, - } - } - - pub fn get_actual_size(&self) -> usize { - match self { - &Format::Char => mem::size_of::(), - &Format::Short => mem::size_of::(), - &Format::Long => mem::size_of::(), - } - } -} - -pub trait Formattable: Debug + Clone + Copy + PartialEq + PartialOrd { - const FORMAT: Format; -} - -// You might be surprised by the absence of c_int, but not as surprised as X11 would be by the presence of it. -impl Formattable for c_schar { - const FORMAT: Format = Format::Char; -} -impl Formattable for c_uchar { - const FORMAT: Format = Format::Char; -} -impl Formattable for c_short { - const FORMAT: Format = Format::Short; -} -impl Formattable for c_ushort { - const FORMAT: Format = Format::Short; -} -impl Formattable for c_long { - const FORMAT: Format = Format::Long; -} -impl Formattable for c_ulong { - const FORMAT: Format = Format::Long; -} diff --git a/src/platform_impl/linux/x11/util/geometry.rs b/src/platform_impl/linux/x11/util/geometry.rs index d8f466fc2a..b8d89293e6 100644 --- a/src/platform_impl/linux/x11/util/geometry.rs +++ b/src/platform_impl/linux/x11/util/geometry.rs @@ -1,3 +1,5 @@ +use crate::platform_impl::x11::xdisplay::Screen; +use crate::platform_impl::x11::UnownedWindow; use std::cmp; use super::*; @@ -42,41 +44,41 @@ impl AaRect { #[derive(Debug, Default)] pub struct TranslatedCoords { - pub x_rel_root: c_int, - pub y_rel_root: c_int, - pub child: ffi::Window, + pub x_rel_root: i16, + pub y_rel_root: i16, + pub child: ffi::xcb_window_t, } #[derive(Debug, Default)] pub struct Geometry { - pub root: ffi::Window, + pub root: ffi::xcb_window_t, // If you want positions relative to the root window, use translate_coords. // Note that the overwhelming majority of window managers are reparenting WMs, thus the window // ID we get from window creation is for a nested window used as the window's client area. If // you call get_geometry with that window ID, then you'll get the position of that client area // window relative to the parent it's nested in (the frame), which isn't helpful if you want // to know the frame position. - pub x_rel_parent: c_int, - pub y_rel_parent: c_int, + pub x_rel_parent: i16, + pub y_rel_parent: i16, // In that same case, this will give you client area size. - pub width: c_uint, - pub height: c_uint, + pub width: u16, + pub height: u16, // xmonad and dwm were the only WMs tested that use the border return at all. // The majority of WMs seem to simply fill it with 0 unconditionally. - pub border: c_uint, - pub depth: c_uint, + pub border: u16, + pub depth: u8, } #[derive(Debug, Clone)] pub struct FrameExtents { - pub left: c_ulong, - pub right: c_ulong, - pub top: c_ulong, - pub bottom: c_ulong, + pub left: u32, + pub right: u32, + pub top: u32, + pub bottom: u32, } impl FrameExtents { - pub fn new(left: c_ulong, right: c_ulong, top: c_ulong, bottom: c_ulong) -> Self { + pub fn new(left: u32, right: u32, top: u32, bottom: u32) -> Self { FrameExtents { left, right, @@ -85,7 +87,7 @@ impl FrameExtents { } } - pub fn from_border(border: c_ulong) -> Self { + pub fn from_border(border: u32) -> Self { Self::new(border, border, border, border) } } @@ -144,62 +146,60 @@ impl XConnection { // This is adequate for inner_position pub fn translate_coords( &self, - window: ffi::Window, - root: ffi::Window, - ) -> Result { - let mut coords = TranslatedCoords::default(); - + window: ffi::xcb_window_t, + root: ffi::xcb_window_t, + ) -> Result { unsafe { - (self.xlib.XTranslateCoordinates)( - self.display, - window, - root, - 0, - 0, - &mut coords.x_rel_root, - &mut coords.y_rel_root, - &mut coords.child, + let mut err = ptr::null_mut(); + let reply = self.xcb.xcb_translate_coordinates_reply( + self.c, + self.xcb + .xcb_translate_coordinates(self.c, window, root, 0, 0), + &mut err, ); + let reply = self.check(reply, err)?; + Ok(TranslatedCoords { + x_rel_root: reply.dst_x, + y_rel_root: reply.dst_y, + child: reply.child, + }) } - - self.check_errors()?; - Ok(coords) } // This is adequate for inner_size - pub fn get_geometry(&self, window: ffi::Window) -> Result { - let mut geometry = Geometry::default(); - - let _status = unsafe { - (self.xlib.XGetGeometry)( - self.display, - window, - &mut geometry.root, - &mut geometry.x_rel_parent, - &mut geometry.y_rel_parent, - &mut geometry.width, - &mut geometry.height, - &mut geometry.border, - &mut geometry.depth, - ) - }; - - self.check_errors()?; - Ok(geometry) + pub fn get_geometry(&self, window: ffi::xcb_window_t) -> Result { + unsafe { + let mut err = ptr::null_mut(); + let reply = self.xcb.xcb_get_geometry_reply( + self.c, + self.xcb.xcb_get_geometry(self.c, window), + &mut err, + ); + let reply = self.check(reply, err)?; + Ok(Geometry { + root: reply.root, + x_rel_parent: reply.x, + y_rel_parent: reply.y, + width: reply.width, + height: reply.height, + border: reply.border_width, + depth: reply.depth, + }) + } } - fn get_frame_extents(&self, window: ffi::Window) -> Option { - let extents_atom = unsafe { self.get_atom_unchecked(b"_NET_FRAME_EXTENTS\0") }; + fn get_frame_extents(&self, window: &UnownedWindow) -> Option { + let extents_atom = self.get_atom("_NET_FRAME_EXTENTS"); - if !hint_is_supported(extents_atom) { + if !window.screen.hint_is_supported(extents_atom) { return None; } // Of the WMs tested, xmonad, i3, dwm, IceWM (1.3.x and earlier), and blackbox don't // support this. As this is part of EWMH (Extended Window Manager Hints), it's likely to // be unsupported by many smaller WMs. - let extents: Option> = self - .get_property(window, extents_atom, ffi::XA_CARDINAL) + let extents: Option> = self + .get_property(window.xwindow, extents_atom, ffi::XCB_ATOM_CARDINAL) .ok(); extents.and_then(|extents| { @@ -216,52 +216,37 @@ impl XConnection { }) } - pub fn is_top_level(&self, window: ffi::Window, root: ffi::Window) -> Option { - let client_list_atom = unsafe { self.get_atom_unchecked(b"_NET_CLIENT_LIST\0") }; + pub fn is_top_level(&self, screen: &Screen, window: ffi::xcb_window_t) -> Option { + let client_list_atom = self.get_atom("_NET_CLIENT_LIST"); - if !hint_is_supported(client_list_atom) { + if !screen.hint_is_supported(client_list_atom) { return None; } - let client_list: Option> = self - .get_property(root, client_list_atom, ffi::XA_WINDOW) + let client_list: Option> = self + .get_property(screen.root, client_list_atom, ffi::XCB_ATOM_WINDOW) .ok(); client_list.map(|client_list| client_list.contains(&window)) } - fn get_parent_window(&self, window: ffi::Window) -> Result { - let parent = unsafe { - let mut root = 0; - let mut parent = 0; - let mut children: *mut ffi::Window = ptr::null_mut(); - let mut nchildren = 0; - - // What's filled into `parent` if `window` is the root window? - let _status = (self.xlib.XQueryTree)( - self.display, - window, - &mut root, - &mut parent, - &mut children, - &mut nchildren, + fn get_parent_window(&self, window: ffi::xcb_window_t) -> Result { + unsafe { + let mut err = ptr::null_mut(); + let reply = self.xcb.xcb_query_tree_reply( + self.c, + self.xcb.xcb_query_tree(self.c, window), + &mut err, ); - - // The list of children isn't used - if children != ptr::null_mut() { - (self.xlib.XFree)(children as *mut _); - } - - parent - }; - self.check_errors().map(|_| parent) + Ok(self.check(reply, err)?.parent) + } } fn climb_hierarchy( &self, - window: ffi::Window, - root: ffi::Window, - ) -> Result { + window: ffi::xcb_window_t, + root: ffi::xcb_window_t, + ) -> Result { let mut outer_window = window; loop { let candidate = self.get_parent_window(outer_window)?; @@ -273,11 +258,7 @@ impl XConnection { Ok(outer_window) } - pub fn get_frame_extents_heuristic( - &self, - window: ffi::Window, - root: ffi::Window, - ) -> FrameExtentsHeuristic { + pub fn get_frame_extents_heuristic(&self, window: &UnownedWindow) -> FrameExtentsHeuristic { use self::FrameExtentsHeuristicPath::*; // Position relative to root window. @@ -286,14 +267,14 @@ impl XConnection { // that, fullscreen windows often aren't nested. let (inner_y_rel_root, child) = { let coords = self - .translate_coords(window, root) + .translate_coords(window.xwindow, window.screen.root) .expect("Failed to translate window coordinates"); (coords.y_rel_root, coords.child) }; let (width, height, border) = { let inner_geometry = self - .get_geometry(window) + .get_geometry(window.xwindow) .expect("Failed to get inner window geometry"); ( inner_geometry.width, @@ -307,7 +288,8 @@ impl XConnection { // when y is on the range [0, 2] and if the window has been unfocused since being // undecorated (or was undecorated upon construction), the first condition is true, // requiring us to rely on the second condition. - let nested = !(window == child || self.is_top_level(child, root) == Some(true)); + let nested = + !(window.xwindow == child || self.is_top_level(&window.screen, child) == Some(true)); // Hopefully the WM supports EWMH, allowing us to get exact info on the window frames. if let Some(mut frame_extents) = self.get_frame_extents(window) { @@ -346,7 +328,7 @@ impl XConnection { // just climb up the hierarchy and get the geometry of the outermost window we're // nested in. let outer_window = self - .climb_hierarchy(window, root) + .climb_hierarchy(window.xwindow, window.screen.root) .expect("Failed to climb window hierarchy"); let (outer_y, outer_width, outer_height) = { let outer_geometry = self @@ -363,7 +345,7 @@ impl XConnection { // area, we can figure out what's in between. let diff_x = outer_width.saturating_sub(width); let diff_y = outer_height.saturating_sub(height); - let offset_y = inner_y_rel_root.saturating_sub(outer_y) as c_uint; + let offset_y = inner_y_rel_root.saturating_sub(outer_y) as u16; let left = diff_x / 2; let right = left; diff --git a/src/platform_impl/linux/x11/util/hint.rs b/src/platform_impl/linux/x11/util/hint.rs index 222d81748e..5f9cc82601 100644 --- a/src/platform_impl/linux/x11/util/hint.rs +++ b/src/platform_impl/linux/x11/util/hint.rs @@ -1,7 +1,10 @@ -use std::slice; use std::sync::Arc; use super::*; +use std::convert::TryInto; +use thiserror::Error; +use xcb_dl_util::hint::{XcbHints, XcbHintsError, XcbSizeHints, XcbSizeHintsError}; +use xcb_dl_util::property::XcbGetPropertyError; #[derive(Debug)] #[allow(dead_code)] @@ -71,25 +74,25 @@ impl Default for WindowType { } impl WindowType { - pub(crate) fn as_atom(&self, xconn: &Arc) -> ffi::Atom { + pub(crate) fn as_atom(&self, xconn: &Arc) -> ffi::xcb_atom_t { use self::WindowType::*; - let atom_name: &[u8] = match *self { - Desktop => b"_NET_WM_WINDOW_TYPE_DESKTOP\0", - Dock => b"_NET_WM_WINDOW_TYPE_DOCK\0", - Toolbar => b"_NET_WM_WINDOW_TYPE_TOOLBAR\0", - Menu => b"_NET_WM_WINDOW_TYPE_MENU\0", - Utility => b"_NET_WM_WINDOW_TYPE_UTILITY\0", - Splash => b"_NET_WM_WINDOW_TYPE_SPLASH\0", - Dialog => b"_NET_WM_WINDOW_TYPE_DIALOG\0", - DropdownMenu => b"_NET_WM_WINDOW_TYPE_DROPDOWN_MENU\0", - PopupMenu => b"_NET_WM_WINDOW_TYPE_POPUP_MENU\0", - Tooltip => b"_NET_WM_WINDOW_TYPE_TOOLTIP\0", - Notification => b"_NET_WM_WINDOW_TYPE_NOTIFICATION\0", - Combo => b"_NET_WM_WINDOW_TYPE_COMBO\0", - Dnd => b"_NET_WM_WINDOW_TYPE_DND\0", - Normal => b"_NET_WM_WINDOW_TYPE_NORMAL\0", + let atom_name: &str = match *self { + Desktop => "_NET_WM_WINDOW_TYPE_DESKTOP", + Dock => "_NET_WM_WINDOW_TYPE_DOCK", + Toolbar => "_NET_WM_WINDOW_TYPE_TOOLBAR", + Menu => "_NET_WM_WINDOW_TYPE_MENU", + Utility => "_NET_WM_WINDOW_TYPE_UTILITY", + Splash => "_NET_WM_WINDOW_TYPE_SPLASH", + Dialog => "_NET_WM_WINDOW_TYPE_DIALOG", + DropdownMenu => "_NET_WM_WINDOW_TYPE_DROPDOWN_MENU", + PopupMenu => "_NET_WM_WINDOW_TYPE_POPUP_MENU", + Tooltip => "_NET_WM_WINDOW_TYPE_TOOLTIP", + Notification => "_NET_WM_WINDOW_TYPE_NOTIFICATION", + Combo => "_NET_WM_WINDOW_TYPE_COMBO", + Dnd => "_NET_WM_WINDOW_TYPE_DND", + Normal => "_NET_WM_WINDOW_TYPE_NORMAL", }; - unsafe { xconn.get_atom_unchecked(atom_name) } + xconn.get_atom(atom_name) } } @@ -97,30 +100,27 @@ pub struct MotifHints { hints: MwmHints, } -#[repr(C)] struct MwmHints { - flags: c_ulong, - functions: c_ulong, - decorations: c_ulong, - input_mode: c_long, - status: c_ulong, + flags: u32, + functions: u32, + decorations: u32, + input_mode: u32, + status: u32, } #[allow(dead_code)] mod mwm { - use libc::c_ulong; - // Motif WM hints are obsolete, but still widely supported. // https://stackoverflow.com/a/1909708 - pub const MWM_HINTS_FUNCTIONS: c_ulong = 1 << 0; - pub const MWM_HINTS_DECORATIONS: c_ulong = 1 << 1; - - pub const MWM_FUNC_ALL: c_ulong = 1 << 0; - pub const MWM_FUNC_RESIZE: c_ulong = 1 << 1; - pub const MWM_FUNC_MOVE: c_ulong = 1 << 2; - pub const MWM_FUNC_MINIMIZE: c_ulong = 1 << 3; - pub const MWM_FUNC_MAXIMIZE: c_ulong = 1 << 4; - pub const MWM_FUNC_CLOSE: c_ulong = 1 << 5; + pub const MWM_HINTS_FUNCTIONS: u32 = 1 << 0; + pub const MWM_HINTS_DECORATIONS: u32 = 1 << 1; + + pub const MWM_FUNC_ALL: u32 = 1 << 0; + pub const MWM_FUNC_RESIZE: u32 = 1 << 1; + pub const MWM_FUNC_MOVE: u32 = 1 << 2; + pub const MWM_FUNC_MINIMIZE: u32 = 1 << 3; + pub const MWM_FUNC_MAXIMIZE: u32 = 1 << 4; + pub const MWM_FUNC_CLOSE: u32 = 1 << 5; } impl MotifHints { @@ -138,7 +138,7 @@ impl MotifHints { pub fn set_decorations(&mut self, decorations: bool) { self.hints.flags |= mwm::MWM_HINTS_DECORATIONS; - self.hints.decorations = decorations as c_ulong; + self.hints.decorations = decorations as u32; } pub fn set_maximizable(&mut self, maximizable: bool) { @@ -149,7 +149,7 @@ impl MotifHints { } } - fn add_func(&mut self, func: c_ulong) { + fn add_func(&mut self, func: u32) { if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS != 0 { if self.hints.functions & mwm::MWM_FUNC_ALL != 0 { self.hints.functions &= !func; @@ -159,7 +159,7 @@ impl MotifHints { } } - fn remove_func(&mut self, func: c_ulong) { + fn remove_func(&mut self, func: u32) { if self.hints.flags & mwm::MWM_HINTS_FUNCTIONS == 0 { self.hints.flags |= mwm::MWM_HINTS_FUNCTIONS; self.hints.functions = mwm::MWM_FUNC_ALL; @@ -174,168 +174,103 @@ impl MotifHints { } impl MwmHints { - fn as_slice(&self) -> &[c_ulong] { - unsafe { slice::from_raw_parts(self as *const _ as *const c_ulong, 5) } + fn as_array(&self) -> [u32; 5] { + [ + self.flags, + self.functions, + self.decorations, + self.input_mode, + self.status, + ] } } -pub struct NormalHints<'a> { - size_hints: XSmartPointer<'a, ffi::XSizeHints>, -} - -impl<'a> NormalHints<'a> { - pub fn new(xconn: &'a XConnection) -> Self { - NormalHints { - size_hints: xconn.alloc_size_hints(), - } - } - - pub fn get_position(&self) -> Option<(i32, i32)> { - if has_flag(self.size_hints.flags, ffi::PPosition) { - Some((self.size_hints.x as i32, self.size_hints.y as i32)) - } else { - None - } - } - - pub fn set_position(&mut self, position: Option<(i32, i32)>) { - if let Some((x, y)) = position { - self.size_hints.flags |= ffi::PPosition; - self.size_hints.x = x as c_int; - self.size_hints.y = y as c_int; - } else { - self.size_hints.flags &= !ffi::PPosition; - } - } - - // WARNING: This hint is obsolete - pub fn set_size(&mut self, size: Option<(u32, u32)>) { - if let Some((width, height)) = size { - self.size_hints.flags |= ffi::PSize; - self.size_hints.width = width as c_int; - self.size_hints.height = height as c_int; - } else { - self.size_hints.flags &= !ffi::PSize; - } - } - - pub fn set_max_size(&mut self, max_size: Option<(u32, u32)>) { - if let Some((max_width, max_height)) = max_size { - self.size_hints.flags |= ffi::PMaxSize; - self.size_hints.max_width = max_width as c_int; - self.size_hints.max_height = max_height as c_int; - } else { - self.size_hints.flags &= !ffi::PMaxSize; - } - } - - pub fn set_min_size(&mut self, min_size: Option<(u32, u32)>) { - if let Some((min_width, min_height)) = min_size { - self.size_hints.flags |= ffi::PMinSize; - self.size_hints.min_width = min_width as c_int; - self.size_hints.min_height = min_height as c_int; - } else { - self.size_hints.flags &= !ffi::PMinSize; - } - } - - pub fn set_resize_increments(&mut self, resize_increments: Option<(u32, u32)>) { - if let Some((width_inc, height_inc)) = resize_increments { - self.size_hints.flags |= ffi::PResizeInc; - self.size_hints.width_inc = width_inc as c_int; - self.size_hints.height_inc = height_inc as c_int; - } else { - self.size_hints.flags &= !ffi::PResizeInc; - } - } - - pub fn set_base_size(&mut self, base_size: Option<(u32, u32)>) { - if let Some((base_width, base_height)) = base_size { - self.size_hints.flags |= ffi::PBaseSize; - self.size_hints.base_width = base_width as c_int; - self.size_hints.base_height = base_height as c_int; - } else { - self.size_hints.flags &= !ffi::PBaseSize; - } - } +#[derive(Debug, Error)] +pub enum HintsError { + #[error("Could not convert the property contents to XcbHints: {0}")] + Contents(#[from] XcbHintsError), + #[error("Could not convert the property contents to XcbSizeHints: {0}")] + SizeContents(#[from] XcbSizeHintsError), + #[error("Could not retrieve the property: {0}")] + Property(#[from] XcbGetPropertyError), + #[error("An xcb error occurred: {0}")] + Xcb(#[from] XcbError), } impl XConnection { - pub fn get_wm_hints( - &self, - window: ffi::Window, - ) -> Result, XError> { - let wm_hints = unsafe { (self.xlib.XGetWMHints)(self.display, window) }; - self.check_errors()?; - let wm_hints = if wm_hints.is_null() { - self.alloc_wm_hints() - } else { - XSmartPointer::new(self, wm_hints).unwrap() + pub fn get_wm_hints(&self, window: ffi::xcb_window_t) -> Result { + let prop = self.get_property::(window, ffi::XCB_ATOM_WM_HINTS, ffi::XCB_ATOM_WM_HINTS); + let bytes = match prop { + Ok(b) => b, + Err(XcbGetPropertyError::Unset) => return Ok(XcbHints::default()), + Err(e) => return Err(e.into()), }; - Ok(wm_hints) + Ok((&*bytes).try_into()?) } - pub fn set_wm_hints( - &self, - window: ffi::Window, - wm_hints: XSmartPointer<'_, ffi::XWMHints>, - ) -> Flusher<'_> { - unsafe { - (self.xlib.XSetWMHints)(self.display, window, wm_hints.ptr); - } - Flusher::new(self) + pub fn set_wm_hints(&self, window: ffi::xcb_window_t, wm_hints: XcbHints) -> XcbPendingCommand { + self.change_property( + window, + ffi::XCB_ATOM_WM_HINTS, + ffi::XCB_ATOM_WM_HINTS, + PropMode::Replace, + wm_hints.as_bytes(), + ) } - pub fn get_normal_hints(&self, window: ffi::Window) -> Result, XError> { - let size_hints = self.alloc_size_hints(); - let mut supplied_by_user = MaybeUninit::uninit(); - unsafe { - (self.xlib.XGetWMNormalHints)( - self.display, - window, - size_hints.ptr, - supplied_by_user.as_mut_ptr(), - ); - } - self.check_errors().map(|_| NormalHints { size_hints }) + pub fn get_normal_hints(&self, window: ffi::xcb_window_t) -> Result { + let bytes = self.get_property::( + window, + ffi::XCB_ATOM_WM_NORMAL_HINTS, + ffi::XCB_ATOM_WM_SIZE_HINTS, + )?; + Ok((&*bytes).try_into()?) } pub fn set_normal_hints( &self, - window: ffi::Window, - normal_hints: NormalHints<'_>, - ) -> Flusher<'_> { - unsafe { - (self.xlib.XSetWMNormalHints)(self.display, window, normal_hints.size_hints.ptr); - } - Flusher::new(self) + window: ffi::xcb_window_t, + normal_hints: XcbSizeHints, + ) -> XcbPendingCommand { + self.change_property( + window, + ffi::XCB_ATOM_WM_NORMAL_HINTS, + ffi::XCB_ATOM_WM_SIZE_HINTS, + PropMode::Replace, + normal_hints.as_bytes(), + ) } - pub fn get_motif_hints(&self, window: ffi::Window) -> MotifHints { - let motif_hints = unsafe { self.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") }; + pub fn get_motif_hints(&self, window: ffi::xcb_window_t) -> MotifHints { + let motif_hints = self.get_atom("_MOTIF_WM_HINTS"); let mut hints = MotifHints::new(); - if let Ok(props) = self.get_property::(window, motif_hints, motif_hints) { + if let Ok(props) = self.get_property::(window, motif_hints, motif_hints) { hints.hints.flags = props.get(0).cloned().unwrap_or(0); hints.hints.functions = props.get(1).cloned().unwrap_or(0); hints.hints.decorations = props.get(2).cloned().unwrap_or(0); - hints.hints.input_mode = props.get(3).cloned().unwrap_or(0) as c_long; + hints.hints.input_mode = props.get(3).cloned().unwrap_or(0); hints.hints.status = props.get(4).cloned().unwrap_or(0); } hints } - pub fn set_motif_hints(&self, window: ffi::Window, hints: &MotifHints) -> Flusher<'_> { - let motif_hints = unsafe { self.get_atom_unchecked(b"_MOTIF_WM_HINTS\0") }; + pub fn set_motif_hints( + &self, + window: ffi::xcb_window_t, + hints: &MotifHints, + ) -> XcbPendingCommand { + let motif_hints = self.get_atom("_MOTIF_WM_HINTS"); self.change_property( window, motif_hints, motif_hints, PropMode::Replace, - hints.hints.as_slice(), + &hints.hints.as_array(), ) + .into() } } diff --git a/src/platform_impl/linux/x11/util/icon.rs b/src/platform_impl/linux/x11/util/icon.rs index eb13d48b5b..b1da46818f 100644 --- a/src/platform_impl/linux/x11/util/icon.rs +++ b/src/platform_impl/linux/x11/util/icon.rs @@ -4,7 +4,7 @@ use crate::icon::{Icon, Pixel, PIXEL_SIZE}; impl Pixel { pub fn to_packed_argb(&self) -> Cardinal { let mut cardinal = 0; - assert!(CARDINAL_SIZE >= PIXEL_SIZE); + assert_eq!(CARDINAL_SIZE, PIXEL_SIZE); let as_bytes = &mut cardinal as *mut _ as *mut u8; unsafe { *as_bytes.offset(0) = self.b; diff --git a/src/platform_impl/linux/x11/util/input.rs b/src/platform_impl/linux/x11/util/input.rs index e9f45aee1c..c4b0602e45 100644 --- a/src/platform_impl/linux/x11/util/input.rs +++ b/src/platform_impl/linux/x11/util/input.rs @@ -1,190 +1,108 @@ -use std::{slice, str}; - use super::*; -use crate::event::ModifiersState; - -pub const VIRTUAL_CORE_POINTER: c_int = 2; -pub const VIRTUAL_CORE_KEYBOARD: c_int = 3; - -// A base buffer size of 1kB uses a negligible amount of RAM while preventing us from having to -// re-allocate (and make another round-trip) in the *vast* majority of cases. -// To test if `lookup_utf8` works correctly, set this to 1. -const TEXT_BUFFER_SIZE: usize = 1024; +use xcb_dl_util::error::XcbError; +use xcb_dl_util::xcb_box::XcbBox; -impl ModifiersState { - pub(crate) fn from_x11(state: &ffi::XIModifierState) -> Self { - ModifiersState::from_x11_mask(state.effective as c_uint) - } - - pub(crate) fn from_x11_mask(mask: c_uint) -> Self { - let mut m = ModifiersState::empty(); - m.set(ModifiersState::ALT, mask & ffi::Mod1Mask != 0); - m.set(ModifiersState::SHIFT, mask & ffi::ShiftMask != 0); - m.set(ModifiersState::CTRL, mask & ffi::ControlMask != 0); - m.set(ModifiersState::LOGO, mask & ffi::Mod4Mask != 0); - m - } -} - -// NOTE: Some of these fields are not used, but may be of use in the future. -pub struct PointerState<'a> { - xconn: &'a XConnection, - pub root: ffi::Window, - pub child: ffi::Window, - pub root_x: c_double, - pub root_y: c_double, - pub win_x: c_double, - pub win_y: c_double, - buttons: ffi::XIButtonState, - modifiers: ffi::XIModifierState, - pub group: ffi::XIGroupState, - pub relative_to_window: bool, -} - -impl<'a> PointerState<'a> { - pub fn get_modifier_state(&self) -> ModifiersState { - ModifiersState::from_x11(&self.modifiers) - } -} - -impl<'a> Drop for PointerState<'a> { - fn drop(&mut self) { - if !self.buttons.mask.is_null() { - unsafe { - // This is why you need to read the docs carefully... - (self.xconn.xlib.XFree)(self.buttons.mask as _); - } - } - } -} +pub const VIRTUAL_CORE_POINTER: ffi::xcb_input_device_id_t = 2; impl XConnection { pub fn select_xinput_events( &self, - window: c_ulong, - device_id: c_int, - mask: i32, - ) -> Flusher<'_> { - let mut event_mask = ffi::XIEventMask { - deviceid: device_id, - mask: &mask as *const _ as *mut c_uchar, - mask_len: mem::size_of_val(&mask) as c_int, - }; + window: ffi::xcb_window_t, + device_id: ffi::xcb_input_device_id_t, + mask: u32, + ) -> XcbPendingCommand { unsafe { - (self.xinput2.XISelectEvents)( - self.display, + xcb_dl_util::input::select_events_checked( + &self.xinput, + self.c, window, - &mut event_mask as *mut ffi::XIEventMask, - 1, // number of masks to read from pointer above - ); + device_id, + [mask], + ) + .into() } - Flusher::new(self) } - #[allow(dead_code)] - pub fn select_xkb_events(&self, device_id: c_uint, mask: c_ulong) -> Option> { - let status = unsafe { (self.xlib.XkbSelectEvents)(self.display, device_id, mask, mask) }; - if status == ffi::True { - Some(Flusher::new(self)) - } else { - None + pub fn select_xkb_events( + &self, + device_id: ffi::xcb_input_device_id_t, + events: ffi::xcb_xkb_event_type_t, + ) -> XcbPendingCommand { + unsafe { + self.xkb + .xcb_xkb_select_events_checked( + self.c, + device_id, + events as _, + 0, + events as _, + !0, + !0, + ptr::null(), + ) + .into() } } - pub fn query_pointer( + pub fn select_xkb_event_details( &self, - window: ffi::Window, - device_id: c_int, - ) -> Result, XError> { + device_id: ffi::xcb_input_device_id_t, + events: ffi::xcb_xkb_event_type_t, + details: &ffi::xcb_xkb_select_events_details_t, + ) -> XcbPendingCommand { unsafe { - let mut root = 0; - let mut child = 0; - let mut root_x = 0.0; - let mut root_y = 0.0; - let mut win_x = 0.0; - let mut win_y = 0.0; - let mut buttons = Default::default(); - let mut modifiers = Default::default(); - let mut group = Default::default(); - - let relative_to_window = (self.xinput2.XIQueryPointer)( - self.display, - device_id, - window, - &mut root, - &mut child, - &mut root_x, - &mut root_y, - &mut win_x, - &mut win_y, - &mut buttons, - &mut modifiers, - &mut group, - ) == ffi::True; - - self.check_errors()?; - - Ok(PointerState { - xconn: self, - root, - child, - root_x, - root_y, - win_x, - win_y, - buttons, - modifiers, - group, - relative_to_window, - }) + self.xkb + .xcb_xkb_select_events_aux_checked( + self.c, + device_id, + events as _, + 0, + 0, + !0, + !0, + details, + ) + .into() } } - fn lookup_utf8_inner( + pub fn query_pointer( &self, - ic: ffi::XIC, - key_event: &mut ffi::XKeyEvent, - buffer: *mut u8, - size: usize, - ) -> (ffi::KeySym, ffi::Status, c_int) { - let mut keysym: ffi::KeySym = 0; - let mut status: ffi::Status = 0; - let count = unsafe { - (self.xlib.Xutf8LookupString)( - ic, - key_event, - buffer as *mut c_char, - size as c_int, - &mut keysym, - &mut status, - ) - }; - (keysym, status, count) + window: ffi::xcb_window_t, + device_id: ffi::xcb_input_device_id_t, + ) -> Result, XcbError> { + unsafe { + let mut err = ptr::null_mut(); + let reply = self.xinput.xcb_input_xi_query_pointer_reply( + self.c, + self.xinput + .xcb_input_xi_query_pointer(self.c, window, device_id), + &mut err, + ); + self.check(reply, err) + } } - pub fn lookup_utf8(&self, ic: ffi::XIC, key_event: &mut ffi::XKeyEvent) -> String { - // `assume_init` is safe here because the array consists of `MaybeUninit` values, - // which do not require initialization. - let mut buffer: [MaybeUninit; TEXT_BUFFER_SIZE] = - unsafe { MaybeUninit::uninit().assume_init() }; - // If the buffer overflows, we'll make a new one on the heap. - let mut vec; - - let (_, status, count) = - self.lookup_utf8_inner(ic, key_event, buffer.as_mut_ptr() as *mut u8, buffer.len()); - - let bytes = if status == ffi::XBufferOverflow { - vec = Vec::with_capacity(count as usize); - let (_, _, new_count) = - self.lookup_utf8_inner(ic, key_event, vec.as_mut_ptr(), vec.capacity()); - debug_assert_eq!(count, new_count); - - unsafe { vec.set_len(count as usize) }; - &vec[..count as usize] - } else { - unsafe { slice::from_raw_parts(buffer.as_ptr() as *const u8, count as usize) } - }; - - str::from_utf8(bytes).unwrap_or("").to_string() + pub fn make_auto_repeat_detectable( + &self, + device_id: ffi::xcb_input_device_id_t, + ) -> Result { + unsafe { + let cookie = self.xkb.xcb_xkb_per_client_flags( + self.c, + device_id, + ffi::XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, + ffi::XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT, + 0, + 0, + 0, + ); + let mut err = ptr::null_mut(); + let reply = self + .xkb + .xcb_xkb_per_client_flags_reply(self.c, cookie, &mut err); + let reply = self.check(reply, err)?; + Ok(reply.supported & ffi::XCB_XKB_PER_CLIENT_FLAG_DETECTABLE_AUTO_REPEAT != 0) + } } } diff --git a/src/platform_impl/linux/x11/util/keys.rs b/src/platform_impl/linux/x11/util/keys.rs deleted file mode 100644 index fc0c9d9062..0000000000 --- a/src/platform_impl/linux/x11/util/keys.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::{iter::Enumerate, ptr, slice::Iter}; - -use super::*; - -pub struct Keymap { - keys: [u8; 32], -} - -pub struct KeymapIter<'a> { - iter: Enumerate>, - index: usize, - item: Option, -} - -impl Keymap { - pub fn iter(&self) -> KeymapIter<'_> { - KeymapIter { - iter: self.keys.iter().enumerate(), - index: 0, - item: None, - } - } -} - -impl<'a> IntoIterator for &'a Keymap { - type Item = ffi::KeyCode; - type IntoIter = KeymapIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl Iterator for KeymapIter<'_> { - type Item = ffi::KeyCode; - - fn next(&mut self) -> Option { - if self.item.is_none() { - while let Some((index, &item)) = self.iter.next() { - if item != 0 { - self.index = index; - self.item = Some(item); - break; - } - } - } - - self.item.take().map(|item| { - debug_assert!(item != 0); - - let bit = first_bit(item); - - if item != bit { - // Remove the first bit; save the rest for further iterations - self.item = Some(item ^ bit); - } - - let shift = bit.trailing_zeros() + (self.index * 8) as u32; - shift as ffi::KeyCode - }) - } -} - -impl XConnection { - pub fn keycode_to_keysym(&self, keycode: ffi::KeyCode) -> ffi::KeySym { - unsafe { (self.xlib.XKeycodeToKeysym)(self.display, keycode, 0) } - } - - pub fn lookup_keysym(&self, xkev: &mut ffi::XKeyEvent) -> ffi::KeySym { - let mut keysym = 0; - - unsafe { - (self.xlib.XLookupString)(xkev, ptr::null_mut(), 0, &mut keysym, ptr::null_mut()); - } - - keysym - } - - pub fn query_keymap(&self) -> Keymap { - let mut keys = [0; 32]; - - unsafe { - (self.xlib.XQueryKeymap)(self.display, keys.as_mut_ptr() as *mut c_char); - } - - Keymap { keys } - } -} - -fn first_bit(b: u8) -> u8 { - 1 << b.trailing_zeros() -} diff --git a/src/platform_impl/linux/x11/util/memory.rs b/src/platform_impl/linux/x11/util/memory.rs deleted file mode 100644 index c85cc37139..0000000000 --- a/src/platform_impl/linux/x11/util/memory.rs +++ /dev/null @@ -1,59 +0,0 @@ -use std::ops::{Deref, DerefMut}; - -use super::*; - -pub struct XSmartPointer<'a, T> { - xconn: &'a XConnection, - pub ptr: *mut T, -} - -impl<'a, T> XSmartPointer<'a, T> { - // You're responsible for only passing things to this that should be XFree'd. - // Returns None if ptr is null. - pub fn new(xconn: &'a XConnection, ptr: *mut T) -> Option { - if !ptr.is_null() { - Some(XSmartPointer { xconn, ptr }) - } else { - None - } - } -} - -impl<'a, T> Deref for XSmartPointer<'a, T> { - type Target = T; - - fn deref(&self) -> &T { - unsafe { &*self.ptr } - } -} - -impl<'a, T> DerefMut for XSmartPointer<'a, T> { - fn deref_mut(&mut self) -> &mut T { - unsafe { &mut *self.ptr } - } -} - -impl<'a, T> Drop for XSmartPointer<'a, T> { - fn drop(&mut self) { - unsafe { - (self.xconn.xlib.XFree)(self.ptr as *mut _); - } - } -} - -impl XConnection { - pub fn alloc_class_hint(&self) -> XSmartPointer<'_, ffi::XClassHint> { - XSmartPointer::new(self, unsafe { (self.xlib.XAllocClassHint)() }) - .expect("`XAllocClassHint` returned null; out of memory") - } - - pub fn alloc_size_hints(&self) -> XSmartPointer<'_, ffi::XSizeHints> { - XSmartPointer::new(self, unsafe { (self.xlib.XAllocSizeHints)() }) - .expect("`XAllocSizeHints` returned null; out of memory") - } - - pub fn alloc_wm_hints(&self) -> XSmartPointer<'_, ffi::XWMHints> { - XSmartPointer::new(self, unsafe { (self.xlib.XAllocWMHints)() }) - .expect("`XAllocWMHints` returned null; out of memory") - } -} diff --git a/src/platform_impl/linux/x11/util/mod.rs b/src/platform_impl/linux/x11/util/mod.rs index df3d89138a..9efab6a936 100644 --- a/src/platform_impl/linux/x11/util/mod.rs +++ b/src/platform_impl/linux/x11/util/mod.rs @@ -4,31 +4,29 @@ mod atom; mod client_msg; mod cursor; -mod format; mod geometry; mod hint; mod icon; mod input; -pub mod keys; -mod memory; -pub mod modifiers; +mod queue; mod randr; mod window_property; mod wm; pub use self::{ - atom::*, client_msg::*, format::*, geometry::*, hint::*, icon::*, input::*, memory::*, - randr::*, window_property::*, wm::*, + atom::*, client_msg::*, geometry::*, hint::*, icon::*, input::*, queue::*, randr::*, + window_property::*, wm::*, }; use std::{ - mem::{self, MaybeUninit}, - ops::BitAnd, - os::raw::*, + mem::{self}, ptr, }; -use super::{ffi, XConnection, XError}; +use super::{ffi, XConnection}; +use xcb_dl_util::error::XcbError; +use xcb_dl_util::void::{XcbPendingCommand, XcbPendingCommands}; +use xcb_dl_util::xcb_box::XcbBox; pub fn maybe_change(field: &mut Option, value: T) -> bool { let wrapped = Some(value); @@ -40,58 +38,50 @@ pub fn maybe_change(field: &mut Option, value: T) -> bool { } } -pub fn has_flag(bitset: T, flag: T) -> bool -where - T: Copy + PartialEq + BitAnd, -{ - bitset & flag == flag +pub fn fp1616_to_f64(x: ffi::xcb_input_fp1616_t) -> f64 { + (x as f64 * 1.0) / ((1 << 16) as f64) } -#[must_use = "This request was made asynchronously, and is still in the output buffer. You must explicitly choose to either `.flush()` (empty the output buffer, sending the request now) or `.queue()` (wait to send the request, allowing you to continue to add more requests without additional round-trips). For more information, see the documentation for `util::flush_requests`."] -pub struct Flusher<'a> { - xconn: &'a XConnection, +pub fn fp3232_to_f64(x: ffi::xcb_input_fp3232_t) -> f64 { + x.integral as f64 + (x.frac as f64) / ((1u64 << 32) as f64) } -impl<'a> Flusher<'a> { - pub fn new(xconn: &'a XConnection) -> Self { - Flusher { xconn } +impl XConnection { + pub fn check_cookie(&self, cookie: ffi::xcb_void_cookie_t) -> Result<(), XcbError> { + unsafe { self.errors.check_cookie(&self.xcb, cookie) } } - // "I want this request sent now!" - pub fn flush(self) -> Result<(), XError> { - self.xconn.flush_requests() + pub unsafe fn check( + &self, + val: *mut T, + err: *mut ffi::xcb_generic_error_t, + ) -> Result, XcbError> { + self.errors.check(&self.xcb, val, err) } - // "I want the response now too!" - pub fn sync(self) -> Result<(), XError> { - self.xconn.sync_with_server() + pub fn check_pending1(&self, pending: XcbPendingCommand) -> Result<(), XcbError> { + unsafe { pending.check(&self.xcb, &self.errors) } } - // "I'm aware that this request hasn't been sent, and I'm okay with waiting." - pub fn queue(self) {} -} + pub fn check_pending(&self, pending: XcbPendingCommands) -> Result<(), XcbError> { + unsafe { pending.check(&self.xcb, &self.errors) } + } -impl XConnection { - // This is impoartant, so pay attention! - // Xlib has an output buffer, and tries to hide the async nature of X from you. - // This buffer contains the requests you make, and is flushed under various circumstances: - // 1. `XPending`, `XNextEvent`, and `XWindowEvent` flush "as needed" - // 2. `XFlush` explicitly flushes - // 3. `XSync` flushes and blocks until all requests are responded to - // 4. Calls that have a return dependent on a response (i.e. `XGetWindowProperty`) sync internally. - // When in doubt, check the X11 source; if a function calls `_XReply`, it flushes and waits. - // All util functions that abstract an async function will return a `Flusher`. - pub fn flush_requests(&self) -> Result<(), XError> { - unsafe { (self.xlib.XFlush)(self.display) }; - //println!("XFlush"); - // This isn't necessarily a useful time to check for errors (since our request hasn't - // necessarily been processed yet) - self.check_errors() + pub fn discard(&self, pending: XcbPendingCommand) { + unsafe { pending.discard(&self.xcb, self.c) } + } + + pub fn flush(&self) -> Result<(), XcbError> { + unsafe { + if self.xcb.xcb_flush(self.c) == 0 { + self.errors.check_connection(&self.xcb) + } else { + Ok(()) + } + } } - pub fn sync_with_server(&self) -> Result<(), XError> { - unsafe { (self.xlib.XSync)(self.display, ffi::False) }; - //println!("XSync"); - self.check_errors() + pub fn generate_id(&self) -> u32 { + unsafe { self.xcb.xcb_generate_id(self.c) } } } diff --git a/src/platform_impl/linux/x11/util/modifiers.rs b/src/platform_impl/linux/x11/util/modifiers.rs deleted file mode 100644 index 7c951997ad..0000000000 --- a/src/platform_impl/linux/x11/util/modifiers.rs +++ /dev/null @@ -1,187 +0,0 @@ -use std::{collections::HashMap, slice}; - -use super::*; - -use crate::event::{ElementState, ModifiersState}; - -// Offsets within XModifierKeymap to each set of keycodes. -// We are only interested in Shift, Control, Alt, and Logo. -// -// There are 8 sets total. The order of keycode sets is: -// Shift, Lock, Control, Mod1 (Alt), Mod2, Mod3, Mod4 (Logo), Mod5 -// -// https://tronche.com/gui/x/xlib/input/XSetModifierMapping.html -const SHIFT_OFFSET: usize = 0; -const CONTROL_OFFSET: usize = 2; -const ALT_OFFSET: usize = 3; -const LOGO_OFFSET: usize = 6; -const NUM_MODS: usize = 8; - -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum Modifier { - Alt, - Ctrl, - Shift, - Logo, -} - -#[derive(Debug, Default)] -pub struct ModifierKeymap { - // Maps keycodes to modifiers - keys: HashMap, -} - -#[derive(Clone, Debug, Default)] -pub struct ModifierKeyState { - // Contains currently pressed modifier keys and their corresponding modifiers - keys: HashMap, - state: ModifiersState, -} - -impl ModifierKeymap { - pub fn new() -> ModifierKeymap { - ModifierKeymap::default() - } - - pub fn get_modifier(&self, keycode: ffi::KeyCode) -> Option { - self.keys.get(&keycode).cloned() - } - - pub fn reset_from_x_connection(&mut self, xconn: &XConnection) { - unsafe { - let keymap = (xconn.xlib.XGetModifierMapping)(xconn.display); - - if keymap.is_null() { - panic!("failed to allocate XModifierKeymap"); - } - - self.reset_from_x_keymap(&*keymap); - - (xconn.xlib.XFreeModifiermap)(keymap); - } - } - - pub fn reset_from_x_keymap(&mut self, keymap: &ffi::XModifierKeymap) { - let keys_per_mod = keymap.max_keypermod as usize; - - let keys = unsafe { - slice::from_raw_parts(keymap.modifiermap as *const _, keys_per_mod * NUM_MODS) - }; - - self.keys.clear(); - - self.read_x_keys(keys, SHIFT_OFFSET, keys_per_mod, Modifier::Shift); - self.read_x_keys(keys, CONTROL_OFFSET, keys_per_mod, Modifier::Ctrl); - self.read_x_keys(keys, ALT_OFFSET, keys_per_mod, Modifier::Alt); - self.read_x_keys(keys, LOGO_OFFSET, keys_per_mod, Modifier::Logo); - } - - fn read_x_keys( - &mut self, - keys: &[ffi::KeyCode], - offset: usize, - keys_per_mod: usize, - modifier: Modifier, - ) { - let start = offset * keys_per_mod; - let end = start + keys_per_mod; - - for &keycode in &keys[start..end] { - if keycode != 0 { - self.keys.insert(keycode, modifier); - } - } - } -} - -impl ModifierKeyState { - pub fn update_keymap(&mut self, mods: &ModifierKeymap) { - self.keys.retain(|k, v| { - if let Some(m) = mods.get_modifier(*k) { - *v = m; - true - } else { - false - } - }); - - self.reset_state(); - } - - pub fn update_state( - &mut self, - state: &ModifiersState, - except: Option, - ) -> Option { - let mut new_state = *state; - - match except { - Some(Modifier::Alt) => new_state.set(ModifiersState::ALT, self.state.alt()), - Some(Modifier::Ctrl) => new_state.set(ModifiersState::CTRL, self.state.ctrl()), - Some(Modifier::Shift) => new_state.set(ModifiersState::SHIFT, self.state.shift()), - Some(Modifier::Logo) => new_state.set(ModifiersState::LOGO, self.state.logo()), - None => (), - } - - if self.state == new_state { - None - } else { - self.keys.retain(|_k, v| get_modifier(&new_state, *v)); - self.state = new_state; - Some(new_state) - } - } - - pub fn modifiers(&self) -> ModifiersState { - self.state - } - - pub fn key_event(&mut self, state: ElementState, keycode: ffi::KeyCode, modifier: Modifier) { - match state { - ElementState::Pressed => self.key_press(keycode, modifier), - ElementState::Released => self.key_release(keycode), - } - } - - pub fn key_press(&mut self, keycode: ffi::KeyCode, modifier: Modifier) { - self.keys.insert(keycode, modifier); - - set_modifier(&mut self.state, modifier, true); - } - - pub fn key_release(&mut self, keycode: ffi::KeyCode) { - if let Some(modifier) = self.keys.remove(&keycode) { - if self.keys.values().find(|&&m| m == modifier).is_none() { - set_modifier(&mut self.state, modifier, false); - } - } - } - - fn reset_state(&mut self) { - let mut new_state = ModifiersState::default(); - - for &m in self.keys.values() { - set_modifier(&mut new_state, m, true); - } - - self.state = new_state; - } -} - -fn get_modifier(state: &ModifiersState, modifier: Modifier) -> bool { - match modifier { - Modifier::Alt => state.alt(), - Modifier::Ctrl => state.ctrl(), - Modifier::Shift => state.shift(), - Modifier::Logo => state.logo(), - } -} - -fn set_modifier(state: &mut ModifiersState, modifier: Modifier, value: bool) { - match modifier { - Modifier::Alt => state.set(ModifiersState::ALT, value), - Modifier::Ctrl => state.set(ModifiersState::CTRL, value), - Modifier::Shift => state.set(ModifiersState::SHIFT, value), - Modifier::Logo => state.set(ModifiersState::LOGO, value), - } -} diff --git a/src/platform_impl/linux/x11/util/queue.rs b/src/platform_impl/linux/x11/util/queue.rs new file mode 100644 index 0000000000..9db37c60ee --- /dev/null +++ b/src/platform_impl/linux/x11/util/queue.rs @@ -0,0 +1,50 @@ +use crate::platform_impl::x11::XConnection; +use std::sync::Arc; +use xcb_dl::ffi; +use xcb_dl_util::xcb_box::XcbBox; + +pub struct EventQueue { + xconn: Arc, + pending: Option>, +} + +impl EventQueue { + pub fn new(xconn: &Arc) -> Self { + Self { + xconn: xconn.clone(), + pending: None, + } + } + + pub fn has_pending_events(&mut self) -> bool { + if let Some(event) = self.poll_for_event2(true) { + self.pending = Some(event); + } + self.pending.is_some() + } + + pub fn poll_for_event(&mut self) -> Option> { + self.poll_for_event2(false) + } + + fn poll_for_event2(&mut self, only_queued: bool) -> Option> { + if self.pending.is_some() { + return self.pending.take(); + } + unsafe { + let event = if only_queued { + self.xconn.xcb.xcb_poll_for_queued_event(self.xconn.c) + } else { + self.xconn.xcb.xcb_poll_for_event(self.xconn.c) + }; + if event.is_null() { + if let Err(e) = self.xconn.errors.check_connection(&self.xconn.xcb) { + panic!("The X connection is broken: {}", e); + } + None + } else { + Some(XcbBox::new(event)) + } + } + } +} diff --git a/src/platform_impl/linux/x11/util/randr.rs b/src/platform_impl/linux/x11/util/randr.rs index b9258c68f1..72c2e77fed 100644 --- a/src/platform_impl/linux/x11/util/randr.rs +++ b/src/platform_impl/linux/x11/util/randr.rs @@ -1,9 +1,7 @@ -use std::{env, slice, str::FromStr}; +use std::{env, slice, str, str::FromStr}; -use super::{ - ffi::{CurrentTime, RRCrtc, RRMode, Success, XRRCrtcInfo, XRRScreenResources}, - *, -}; +use super::*; +use crate::platform_impl::x11::xdisplay::Screen; use crate::{dpi::validate_scale_factor, platform_impl::platform::x11::VideoMode}; /// Represents values of `WINIT_HIDPI_FACTOR`. @@ -32,44 +30,53 @@ pub fn calc_dpi_factor( impl XConnection { // Retrieve DPI from Xft.dpi property - pub unsafe fn get_xft_dpi(&self) -> Option { - (self.xlib.XrmInitialize)(); - let resource_manager_str = (self.xlib.XResourceManagerString)(self.display); - if resource_manager_str == ptr::null_mut() { - return None; - } - if let Ok(res) = ::std::ffi::CStr::from_ptr(resource_manager_str).to_str() { - let name: &str = "Xft.dpi:\t"; - for pair in res.split("\n") { - if pair.starts_with(&name) { - let res = &pair[name.len()..]; - return f64::from_str(&res).ok(); - } + pub unsafe fn get_xft_dpi(&self, screen: &Screen) -> Option { + let resource_manager_str = self + .get_property::( + screen.root, + ffi::XCB_ATOM_RESOURCE_MANAGER, + ffi::XCB_ATOM_STRING, + ) + .ok()?; + for pair in resource_manager_str.split(|b| *b == b'\n') { + if let Some(res) = pair.strip_prefix(b"Xft.dpi:\t") { + return f64::from_str(str::from_utf8(res).ok()?).ok(); } } None } pub unsafe fn get_output_info( &self, - resources: *mut XRRScreenResources, - crtc: *mut XRRCrtcInfo, - ) -> Option<(String, f64, Vec)> { - let output_info = - (self.xrandr.XRRGetOutputInfo)(self.display, resources, *(*crtc).outputs.offset(0)); - if output_info.is_null() { - // When calling `XRRGetOutputInfo` on a virtual monitor (versus a physical display) - // it's possible for it to return null. - // https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=816596 - let _ = self.check_errors(); // discard `BadRROutput` error - return None; - } - - let screen = (self.xlib.XDefaultScreen)(self.display); - let bit_depth = (self.xlib.XDefaultDepth)(self.display, screen); + screen: &Screen, + resources: &ffi::xcb_randr_get_screen_resources_reply_t, + crtc: &ffi::xcb_randr_get_crtc_info_reply_t, + ) -> Result)>, XcbError> { + let mut err = ptr::null_mut(); + + let first_output = { + if crtc.num_outputs == 0 { + return Ok(None); + } + *self.randr.xcb_randr_get_crtc_info_outputs(crtc) + }; + let output_info = { + let reply = self.randr.xcb_randr_get_output_info_reply( + self.c, + self.randr + .xcb_randr_get_output_info(self.c, first_output, 0), + &mut err, + ); + self.check(reply, err)? + }; + let output_modes = slice::from_raw_parts( + self.randr.xcb_randr_get_output_info_modes(&*output_info), + output_info.num_modes as _, + ); - let output_modes = - slice::from_raw_parts((*output_info).modes, (*output_info).nmode as usize); - let resource_modes = slice::from_raw_parts((*resources).modes, (*resources).nmode as usize); + let resource_modes = slice::from_raw_parts( + self.randr.xcb_randr_get_screen_resources_modes(resources), + resources.num_modes as _, + ); let modes = resource_modes .iter() @@ -77,8 +84,8 @@ impl XConnection { // modes in the array in XRRScreenResources .filter(|x| output_modes.iter().any(|id| x.id == *id)) .map(|x| { - let refresh_rate = if x.dotClock > 0 && x.hTotal > 0 && x.vTotal > 0 { - x.dotClock as u64 * 1000 / (x.hTotal as u64 * x.vTotal as u64) + let refresh_rate = if x.dot_clock > 0 && x.htotal > 0 && x.vtotal > 0 { + x.dot_clock as u64 * 1000 / (x.htotal as u64 * x.vtotal as u64) } else { 0 }; @@ -86,7 +93,7 @@ impl XConnection { VideoMode { size: (x.width, x.height), refresh_rate: (refresh_rate as f32 / 1000.0).round() as u16, - bit_depth: bit_depth as u16, + bit_depth: screen.root_depth as u16, native_mode: x.id, // This is populated in `MonitorHandle::video_modes` as the // video mode is returned to the user @@ -96,8 +103,8 @@ impl XConnection { .collect(); let name_slice = slice::from_raw_parts( - (*output_info).name as *mut u8, - (*output_info).nameLen as usize, + self.randr.xcb_randr_get_output_info_name(&*output_info), + output_info.name_len as usize, ); let name = String::from_utf8_lossy(name_slice).into(); // Override DPI if `WINIT_X11_SCALE_FACTOR` variable is set @@ -127,11 +134,8 @@ impl XConnection { let scale_factor = match dpi_env { EnvVarDPI::Randr => calc_dpi_factor( - ((*crtc).width as u32, (*crtc).height as u32), - ( - (*output_info).mm_width as u64, - (*output_info).mm_height as u64, - ), + (crtc.width as u32, crtc.height as u32), + (output_info.mm_width as u64, output_info.mm_height as u64), ), EnvVarDPI::Scale(dpi_override) => { if !validate_scale_factor(dpi_override) { @@ -143,78 +147,69 @@ impl XConnection { dpi_override } EnvVarDPI::NotSet => { - if let Some(dpi) = self.get_xft_dpi() { + if let Some(dpi) = self.get_xft_dpi(screen) { dpi / 96. } else { calc_dpi_factor( - ((*crtc).width as u32, (*crtc).height as u32), - ( - (*output_info).mm_width as u64, - (*output_info).mm_height as u64, - ), + (crtc.width as u32, crtc.height as u32), + (output_info.mm_width as u64, output_info.mm_height as u64), ) } } }; - (self.xrandr.XRRFreeOutputInfo)(output_info); - Some((name, scale_factor, modes)) + Ok(Some((name, scale_factor, modes))) } - pub fn set_crtc_config(&self, crtc_id: RRCrtc, mode_id: RRMode) -> Result<(), ()> { + pub fn set_crtc_config( + &self, + crtc_id: ffi::xcb_randr_crtc_t, + mode_id: ffi::xcb_randr_mode_t, + ) -> Result<(), XcbError> { + let info = self.get_crtc_info(crtc_id)?; unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); - - let root = (self.xlib.XDefaultRootWindow)(self.display); - let resources = if (major == 1 && minor >= 3) || major > 1 { - (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) - } else { - (self.xrandr.XRRGetScreenResources)(self.display, root) - }; - - let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); - let status = (self.xrandr.XRRSetCrtcConfig)( - self.display, - resources, + let cookie = self.randr.xcb_randr_set_crtc_config( + self.c, crtc_id, - CurrentTime, - (*crtc).x, - (*crtc).y, + ffi::XCB_TIME_CURRENT_TIME, + // See the comment in get_crtc_info. + 0, + info.x, + info.y, mode_id, - (*crtc).rotation, - (*crtc).outputs.offset(0), - 1, + info.rotation, + info.num_outputs as _, + self.randr.xcb_randr_get_crtc_info_outputs(&*info), ); - - (self.xrandr.XRRFreeCrtcInfo)(crtc); - (self.xrandr.XRRFreeScreenResources)(resources); - - if status == Success as i32 { - Ok(()) - } else { - Err(()) - } + let mut err = ptr::null_mut(); + let reply = self + .randr + .xcb_randr_set_crtc_config_reply(self.c, cookie, &mut err); + self.check(reply, err).map(|_| ()) } } - pub fn get_crtc_mode(&self, crtc_id: RRCrtc) -> RRMode { - unsafe { - let mut major = 0; - let mut minor = 0; - (self.xrandr.XRRQueryVersion)(self.display, &mut major, &mut minor); - let root = (self.xlib.XDefaultRootWindow)(self.display); - let resources = if (major == 1 && minor >= 3) || major > 1 { - (self.xrandr.XRRGetScreenResourcesCurrent)(self.display, root) - } else { - (self.xrandr.XRRGetScreenResources)(self.display, root) - }; + pub fn get_crtc_mode( + &self, + crtc_id: ffi::xcb_randr_crtc_t, + ) -> Result { + self.get_crtc_info(crtc_id).map(|i| i.mode) + } - let crtc = (self.xrandr.XRRGetCrtcInfo)(self.display, resources, crtc_id); - let mode = (*crtc).mode; - (self.xrandr.XRRFreeCrtcInfo)(crtc); - (self.xrandr.XRRFreeScreenResources)(resources); - mode + pub fn get_crtc_info( + &self, + crtc_id: ffi::xcb_randr_crtc_t, + ) -> Result, XcbError> { + unsafe { + let mut err = ptr::null_mut(); + let reply = self.randr.xcb_randr_get_crtc_info_reply( + self.c, + // NOTE: The randr spec says that the last argument must be equal to the current + // timestamp on the server but this has never been enforced by the x server. + // Therefore we can save us a call to retrieve that value. + self.randr.xcb_randr_get_crtc_info(self.c, crtc_id, 0), + &mut err, + ); + self.check(reply, err) } } } diff --git a/src/platform_impl/linux/x11/util/window_property.rs b/src/platform_impl/linux/x11/util/window_property.rs index 34977f36d2..e9987699e2 100644 --- a/src/platform_impl/linux/x11/util/window_property.rs +++ b/src/platform_impl/linux/x11/util/window_property.rs @@ -1,142 +1,61 @@ use super::*; +use xcb_dl_util::format::XcbDataType; +use xcb_dl_util::property::XcbGetPropertyError; -pub type Cardinal = c_long; -pub const CARDINAL_SIZE: usize = mem::size_of::(); - -#[derive(Debug, Clone)] -pub enum GetPropertyError { - XError(XError), - TypeMismatch(ffi::Atom), - FormatMismatch(c_int), - NothingAllocated, -} - -impl GetPropertyError { - pub fn is_actual_property_type(&self, t: ffi::Atom) -> bool { - if let GetPropertyError::TypeMismatch(actual_type) = *self { - actual_type == t - } else { - false - } - } -} - -// Number of 32-bit chunks to retrieve per iteration of get_property's inner loop. -// To test if `get_property` works correctly, set this to 1. -const PROPERTY_BUFFER_SIZE: c_long = 1024; // 4k of RAM ought to be enough for anyone! +pub type Cardinal = u32; +pub const CARDINAL_SIZE: usize = mem::size_of::(); #[derive(Debug)] #[allow(dead_code)] +#[repr(u32)] pub enum PropMode { - Replace = ffi::PropModeReplace as isize, - Prepend = ffi::PropModePrepend as isize, - Append = ffi::PropModeAppend as isize, + Replace = ffi::XCB_PROP_MODE_REPLACE, + Prepend = ffi::XCB_PROP_MODE_PREPEND, + Append = ffi::XCB_PROP_MODE_APPEND, } impl XConnection { - pub fn get_property( + pub fn get_property( &self, - window: c_ulong, - property: ffi::Atom, - property_type: ffi::Atom, - ) -> Result, GetPropertyError> { - let mut data = Vec::new(); - let mut offset = 0; - - let mut done = false; - let mut actual_type = 0; - let mut actual_format = 0; - let mut quantity_returned = 0; - let mut bytes_after = 0; - let mut buf: *mut c_uchar = ptr::null_mut(); - - while !done { - unsafe { - (self.xlib.XGetWindowProperty)( - self.display, - window, - property, - // This offset is in terms of 32-bit chunks. - offset, - // This is the quanity of 32-bit chunks to receive at once. - PROPERTY_BUFFER_SIZE, - ffi::False, - property_type, - &mut actual_type, - &mut actual_format, - // This is the quantity of items we retrieved in our format, NOT of 32-bit chunks! - &mut quantity_returned, - // ...and this is a quantity of bytes. So, this function deals in 3 different units. - &mut bytes_after, - &mut buf, - ); - - if let Err(e) = self.check_errors() { - return Err(GetPropertyError::XError(e)); - } - - if actual_type != property_type { - return Err(GetPropertyError::TypeMismatch(actual_type)); - } - - let format_mismatch = Format::from_format(actual_format as _) != Some(T::FORMAT); - if format_mismatch { - return Err(GetPropertyError::FormatMismatch(actual_format)); - } - - if !buf.is_null() { - offset += PROPERTY_BUFFER_SIZE; - let new_data = - std::slice::from_raw_parts(buf as *mut T, quantity_returned as usize); - /*println!( - "XGetWindowProperty prop:{:?} fmt:{:02} len:{:02} off:{:02} out:{:02}, buf:{:?}", - property, - mem::size_of::() * 8, - data.len(), - offset, - quantity_returned, - new_data, - );*/ - data.extend_from_slice(&new_data); - // Fun fact: XGetWindowProperty allocates one extra byte at the end. - (self.xlib.XFree)(buf as _); // Don't try to access new_data after this. - } else { - return Err(GetPropertyError::NothingAllocated); - } - - done = bytes_after == 0; - } + window: ffi::xcb_window_t, + property: ffi::xcb_atom_t, + property_type: ffi::xcb_atom_t, + ) -> Result, XcbGetPropertyError> { + const STEP: u32 = 256 * 1024; + unsafe { + xcb_dl_util::property::get_property( + &self.xcb, + &self.errors, + window, + property, + property_type, + false, + STEP, + ) } - - Ok(data) } - pub fn change_property<'a, T: Formattable>( - &'a self, - window: c_ulong, - property: ffi::Atom, - property_type: ffi::Atom, + pub fn change_property( + &self, + window: ffi::xcb_window_t, + property: ffi::xcb_atom_t, + property_type: ffi::xcb_atom_t, mode: PropMode, new_value: &[T], - ) -> Flusher<'a> { - debug_assert_eq!(mem::size_of::(), T::FORMAT.get_actual_size()); + ) -> XcbPendingCommand { unsafe { - (self.xlib.XChangeProperty)( - self.display, - window, - property, - property_type, - T::FORMAT as c_int, - mode as c_int, - new_value.as_ptr() as *const c_uchar, - new_value.len() as c_int, - ); + self.xcb + .xcb_change_property_checked( + self.c, + mode as u8, + window, + property, + property_type, + T::XCB_BITS, + new_value.len() as _, + new_value.as_ptr() as _, + ) + .into() } - /*println!( - "XChangeProperty prop:{:?} val:{:?}", - property, - new_value, - );*/ - Flusher::new(self) } } diff --git a/src/platform_impl/linux/x11/util/wm.rs b/src/platform_impl/linux/x11/util/wm.rs index 693bc5c02d..eb7debcc23 100644 --- a/src/platform_impl/linux/x11/util/wm.rs +++ b/src/platform_impl/linux/x11/util/wm.rs @@ -1,40 +1,23 @@ -use parking_lot::Mutex; - use super::*; - -// This info is global to the window manager. -lazy_static! { - static ref SUPPORTED_HINTS: Mutex> = Mutex::new(Vec::with_capacity(0)); - static ref WM_NAME: Mutex> = Mutex::new(None); -} - -pub fn hint_is_supported(hint: ffi::Atom) -> bool { - (*SUPPORTED_HINTS.lock()).contains(&hint) -} - -pub fn wm_name_is_one_of(names: &[&str]) -> bool { - if let Some(ref name) = *WM_NAME.lock() { - names.contains(&name.as_str()) - } else { - false - } -} +use xcb_dl_util::property::XcbGetPropertyError; impl XConnection { - pub fn update_cached_wm_info(&self, root: ffi::Window) { - *SUPPORTED_HINTS.lock() = self.get_supported_hints(root); - *WM_NAME.lock() = self.get_wm_name(root); + pub fn update_cached_wm_info(&self) { + for screen in &self.screens { + *screen.supported_hints.lock() = self.get_supported_hints(screen.root); + *screen.wm_name.lock() = self.get_wm_name(screen.root); + } } - fn get_supported_hints(&self, root: ffi::Window) -> Vec { - let supported_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTED\0") }; - self.get_property(root, supported_atom, ffi::XA_ATOM) + fn get_supported_hints(&self, root: ffi::xcb_window_t) -> Vec { + let supported_atom = self.get_atom("_NET_SUPPORTED"); + self.get_property(root, supported_atom, ffi::XCB_ATOM_ATOM) .unwrap_or_else(|_| Vec::with_capacity(0)) } - fn get_wm_name(&self, root: ffi::Window) -> Option { - let check_atom = unsafe { self.get_atom_unchecked(b"_NET_SUPPORTING_WM_CHECK\0") }; - let wm_name_atom = unsafe { self.get_atom_unchecked(b"_NET_WM_NAME\0") }; + fn get_wm_name(&self, root: ffi::xcb_window_t) -> Option { + let check_atom = self.get_atom("_NET_SUPPORTING_WM_CHECK"); + let wm_name_atom = self.get_atom("_NET_WM_NAME"); // Mutter/Muffin/Budgie doesn't have _NET_SUPPORTING_WM_CHECK in its _NET_SUPPORTED, despite // it working and being supported. This has been reported upstream, but due to the @@ -58,7 +41,7 @@ impl XConnection { // Querying this property on the root window will give us the ID of a child window created by // the WM. let root_window_wm_check = { - let result = self.get_property(root, check_atom, ffi::XA_WINDOW); + let result = self.get_property(root, check_atom, ffi::XCB_ATOM_WINDOW); let wm_check = result.ok().and_then(|wm_check| wm_check.get(0).cloned()); @@ -72,7 +55,7 @@ impl XConnection { // Querying the same property on the child window we were given, we should get this child // window's ID again. let child_window_wm_check = { - let result = self.get_property(root_window_wm_check, check_atom, ffi::XA_WINDOW); + let result = self.get_property(root_window_wm_check, check_atom, ffi::XCB_ATOM_WINDOW); let wm_check = result.ok().and_then(|wm_check| wm_check.get(0).cloned()); @@ -90,7 +73,7 @@ impl XConnection { // All of that work gives us a window ID that we can get the WM name from. let wm_name = { - let utf8_string_atom = unsafe { self.get_atom_unchecked(b"UTF8_STRING\0") }; + let utf8_string_atom = self.get_atom("UTF8_STRING"); let result = self.get_property(root_window_wm_check, wm_name_atom, utf8_string_atom); @@ -100,14 +83,16 @@ impl XConnection { // against it, though). // The unofficial 1.4 fork of IceWM still includes the extra details, but properly // returns a UTF8 string that isn't null-terminated. - let no_utf8 = if let Err(ref err) = result { - err.is_actual_property_type(ffi::XA_STRING) - } else { - false + let no_utf8 = match result { + Err(XcbGetPropertyError::InvalidPropertyType { + actual: ffi::XCB_ATOM_STRING, + .. + }) => true, + _ => false, }; if no_utf8 { - self.get_property(root_window_wm_check, wm_name_atom, ffi::XA_STRING) + self.get_property(root_window_wm_check, wm_name_atom, ffi::XCB_ATOM_STRING) } else { result } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 26d3b5dd1e..df979f97e0 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -1,8 +1,10 @@ -use raw_window_handle::unix::XlibHandle; +use raw_window_handle::unix::XcbHandle; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering::Relaxed; use std::{ cmp, env, ffi::CString, - mem::{self, replace, MaybeUninit}, + mem::{replace, MaybeUninit}, os::raw::*, path::Path, ptr, slice, @@ -18,14 +20,18 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, monitor::{MonitorHandle as RootMonitorHandle, VideoMode as RootVideoMode}, platform_impl::{ - x11::{ime::ImeContextCreationError, MonitorHandle as X11MonitorHandle}, - MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, - VideoMode as PlatformVideoMode, + x11::MonitorHandle as X11MonitorHandle, MonitorHandle as PlatformMonitorHandle, OsError, + PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, window::{CursorIcon, Fullscreen, Icon, UserAttentionType, WindowAttributes}, }; -use super::{ffi, util, EventLoopWindowTarget, ImeSender, WindowId, XConnection, XError}; +use super::{ffi, util, EventLoopWindowTarget, WindowId, XConnection}; +use crate::platform_impl::x11::util::HintsError; +use crate::platform_impl::x11::util::PropMode; +use crate::platform_impl::x11::xdisplay::Screen; +use xcb_dl_util::hint::XcbSizeHints; +use xcb_dl_util::void::{XcbPendingCommand, XcbPendingCommands}; #[derive(Debug)] pub struct SharedState { @@ -42,7 +48,7 @@ pub struct SharedState { // Used to restore position after exiting fullscreen pub restore_position: Option<(i32, i32)>, // Used to restore video mode after exiting fullscreen - pub desktop_video_mode: Option<(ffi::RRCrtc, ffi::RRMode)>, + pub desktop_video_mode: Option<(ffi::xcb_randr_crtc_t, ffi::xcb_randr_mode_t)>, pub frame_extents: Option, pub min_inner_size: Option, pub max_inner_size: Option, @@ -94,16 +100,15 @@ unsafe impl Send for UnownedWindow {} unsafe impl Sync for UnownedWindow {} pub struct UnownedWindow { - pub xconn: Arc, // never changes - xwindow: ffi::Window, // never changes - root: ffi::Window, // never changes - screen_id: i32, // never changes + pub xconn: Arc, // never changes + pub xwindow: ffi::xcb_window_t, // never changes + pub screen: Arc, cursor: Mutex, cursor_grabbed: Mutex, cursor_visible: Mutex, - ime_sender: Mutex, pub shared_state: Mutex, redraw_sender: Sender, + reset_dead_keys: Arc, } impl UnownedWindow { @@ -113,17 +118,29 @@ impl UnownedWindow { pl_attribs: PlatformSpecificWindowBuilderAttributes, ) -> Result { let xconn = &event_loop.xconn; - let root = event_loop.root; + + let screen_id = pl_attribs + .screen_id + .map(|s| s as usize) + .unwrap_or(xconn.default_screen_id); + let screen = match xconn.screens.iter().skip(screen_id).next() { + Some(s) => s, + _ => return Err(os_error!(OsError::XMisc("Screen id out of bounds"))), + }; + + let root = screen.root; let mut monitors = xconn.available_monitors(); let guessed_monitor = if monitors.is_empty() { X11MonitorHandle::dummy() } else { xconn - .query_pointer(root, util::VIRTUAL_CORE_POINTER) + .query_pointer(root as _, util::VIRTUAL_CORE_POINTER) .ok() .and_then(|pointer_state| { - let (x, y) = (pointer_state.root_x as i64, pointer_state.root_y as i64); + let root_x = util::fp1616_to_f64(pointer_state.root_x); + let root_y = util::fp1616_to_f64(pointer_state.root_y); + let (x, y) = (root_x as i64, root_y as i64); for i in 0..monitors.len() { if monitors[i].rect.contains_point(x, y) { @@ -174,117 +191,113 @@ impl UnownedWindow { dimensions }; - let screen_id = match pl_attribs.screen_id { - Some(id) => id, - None => unsafe { (xconn.xlib.XDefaultScreen)(xconn.display) }, - }; + let mut commands = XcbPendingCommands::new(); // creating - let mut set_win_attr = { - let mut swa: ffi::XSetWindowAttributes = unsafe { mem::zeroed() }; - swa.colormap = if let Some(vi) = pl_attribs.visual_infos { + let set_win_attr = { + let mut swa = ffi::xcb_create_window_value_list_t::default(); + if let Some(visual_id) = pl_attribs.visual_infos.visual_id { unsafe { - let visual = vi.visual; - (xconn.xlib.XCreateColormap)(xconn.display, root, visual, ffi::AllocNone) + swa.colormap = xconn.generate_id(); + commands.push( + xconn + .xcb + .xcb_create_colormap_checked(xconn.c, 0, swa.colormap, root, visual_id) + .into(), + ); } - } else { - 0 - }; - swa.event_mask = ffi::ExposureMask - | ffi::StructureNotifyMask - | ffi::VisibilityChangeMask - | ffi::KeyPressMask - | ffi::KeyReleaseMask - | ffi::KeymapStateMask - | ffi::ButtonPressMask - | ffi::ButtonReleaseMask - | ffi::PointerMotionMask; + } + swa.event_mask = ffi::XCB_EVENT_MASK_EXPOSURE + | ffi::XCB_EVENT_MASK_STRUCTURE_NOTIFY + | ffi::XCB_EVENT_MASK_VISIBILITY_CHANGE + | ffi::XCB_EVENT_MASK_KEY_PRESS + | ffi::XCB_EVENT_MASK_KEYMAP_STATE + | ffi::XCB_EVENT_MASK_BUTTON_PRESS + | ffi::XCB_EVENT_MASK_BUTTON_RELEASE + | ffi::XCB_EVENT_MASK_POINTER_MOTION; swa.border_pixel = 0; - swa.override_redirect = pl_attribs.override_redirect as c_int; + swa.override_redirect = pl_attribs.override_redirect as u32; swa }; - let mut window_attributes = ffi::CWBorderPixel | ffi::CWColormap | ffi::CWEventMask; + let mut window_attributes = + ffi::XCB_CW_BORDER_PIXEL | ffi::XCB_CW_COLORMAP | ffi::XCB_CW_EVENT_MASK; if pl_attribs.override_redirect { - window_attributes |= ffi::CWOverrideRedirect; + window_attributes |= ffi::XCB_CW_OVERRIDE_REDIRECT; } // finally creating the window - let xwindow = unsafe { - (xconn.xlib.XCreateWindow)( - xconn.display, - root, - position.map_or(0, |p: PhysicalPosition| p.x as c_int), - position.map_or(0, |p: PhysicalPosition| p.y as c_int), - dimensions.0 as c_uint, - dimensions.1 as c_uint, - 0, - match pl_attribs.visual_infos { - Some(vi) => vi.depth, - None => ffi::CopyFromParent, - }, - ffi::InputOutput as c_uint, - // TODO: If window wants transparency and `visual_infos` is None, - // we need to find our own visual which has an `alphaMask` which - // is > 0, like we do in glutin. - // - // It is non obvious which masks, if any, we should pass to - // `XGetVisualInfo`. winit doesn't receive any info about what - // properties the user wants. Users should consider choosing the - // visual themselves as glutin does. - match pl_attribs.visual_infos { - Some(vi) => vi.visual, - None => ffi::CopyFromParent as *mut ffi::Visual, - }, - window_attributes, - &mut set_win_attr, - ) + let xwindow = xconn.generate_id(); + unsafe { + let pending = xconn + .xcb + .xcb_create_window_aux_checked( + xconn.c, + pl_attribs + .visual_infos + .depth + .unwrap_or(ffi::XCB_COPY_FROM_PARENT as _), + xwindow, + screen.root, + position.map_or(0, |p: PhysicalPosition| p.x as i16), + position.map_or(0, |p: PhysicalPosition| p.y as i16), + dimensions.0 as u16, + dimensions.1 as u16, + 0, // border width + ffi::XCB_WINDOW_CLASS_INPUT_OUTPUT as u16, + // TODO: If window wants transparency and `visual_infos` is None, + // we need to find our own visual which has an `alphaMask` which + // is > 0, like we do in glutin. + // + // It is non obvious which masks, if any, we should pass to + // `XGetVisualInfo`. winit doesn't receive any info about what + // properties the user wants. Users should consider choosing the + // visual themselves as glutin does. + pl_attribs + .visual_infos + .visual_id + .unwrap_or(ffi::XCB_COPY_FROM_PARENT as _), + window_attributes, + &set_win_attr, + ) + .into(); + commands.push(pending); }; let mut window = UnownedWindow { xconn: Arc::clone(xconn), xwindow, - root, - screen_id, + screen: screen.clone(), cursor: Default::default(), cursor_grabbed: Mutex::new(false), cursor_visible: Mutex::new(true), - ime_sender: Mutex::new(event_loop.ime_sender.clone()), shared_state: SharedState::new(guessed_monitor, window_attrs.visible), redraw_sender: event_loop.redraw_sender.clone(), + reset_dead_keys: event_loop.reset_dead_keys.clone(), }; // Title must be set before mapping. Some tiling window managers (i.e. i3) use the window // title to determine placement/etc., so doing this after mapping would cause the WM to // act on the wrong title state. - window.set_title_inner(&window_attrs.title).queue(); - window - .set_decorations_inner(window_attrs.decorations) - .queue(); + commands.extend(window.set_title_inner(&window_attrs.title)); + commands.push(window.set_decorations_inner(window_attrs.decorations)); { // Enable drag and drop (TODO: extend API to make this toggleable) - unsafe { - let dnd_aware_atom = xconn.get_atom_unchecked(b"XdndAware\0"); - let version = &[5 as c_ulong]; // Latest version; hasn't changed since 2002 - xconn.change_property( - window.xwindow, - dnd_aware_atom, - ffi::XA_ATOM, - util::PropMode::Replace, - version, - ) - } - .queue(); + let dnd_aware_atom = xconn.get_atom("XdndAware"); + let version = &[5u32]; // Latest version; hasn't changed since 2002 + commands.push(xconn.change_property( + window.xwindow, + dnd_aware_atom, + ffi::XCB_ATOM_ATOM, + util::PropMode::Replace, + version, + )); // WM_CLASS must be set *before* mapping the window, as per ICCCM! { let (class, instance) = if let Some((instance, class)) = pl_attribs.class { - let instance = CString::new(instance.as_str()) - .expect("`WM_CLASS` instance contained null byte"); - let class = - CString::new(class.as_str()).expect("`WM_CLASS` class contained null byte"); (instance, class) } else { let class = env::args() @@ -294,33 +307,29 @@ impl UnownedWindow { .and_then(|path| Path::new(path).file_name()) .and_then(|bin_name| bin_name.to_str()) .map(|bin_name| bin_name.to_owned()) - .or_else(|| Some(window_attrs.title.clone())) - .and_then(|string| CString::new(string.as_str()).ok()) - .expect("Default `WM_CLASS` class contained null byte"); + .unwrap_or_else(|| window_attrs.title.clone()); // This environment variable is extraordinarily unlikely to actually be used... let instance = env::var("RESOURCE_NAME") .ok() - .and_then(|instance| CString::new(instance.as_str()).ok()) - .or_else(|| Some(class.clone())) - .expect("Default `WM_CLASS` instance contained null byte"); + .unwrap_or_else(|| class.clone()); (instance, class) }; - let mut class_hint = xconn.alloc_class_hint(); - (*class_hint).res_name = class.as_ptr() as *mut c_char; - (*class_hint).res_class = instance.as_ptr() as *mut c_char; - - unsafe { - (xconn.xlib.XSetClassHint)(xconn.display, window.xwindow, class_hint.ptr); - } //.queue(); + commands.push(xconn.change_property( + window.xwindow, + ffi::XCB_ATOM_WM_CLASS, + ffi::XCB_ATOM_STRING, + PropMode::Replace, + format!("{}\0{}\0", class, instance).as_bytes(), + )); } - window.set_pid().map(|flusher| flusher.queue()); + window.set_pid().map(|cmds| commands.extend(cmds)); - window.set_window_types(pl_attribs.x11_window_types).queue(); + commands.push(window.set_window_types(pl_attribs.x11_window_types)); if let Some(variant) = pl_attribs.gtk_theme_variant { - window.set_gtk_theme_variant(variant).queue(); + commands.push(window.set_gtk_theme_variant(variant)); } // set size hints @@ -333,7 +342,7 @@ impl UnownedWindow { .map(|size| size.to_physical::(scale_factor)); if !window_attrs.resizable { - if util::wm_name_is_one_of(&["Xfwm4"]) { + if screen.wm_name_is_one_of(&["Xfwm4"]) { warn!("To avoid a WM bug, disabling resizing has no effect on Xfwm4"); } else { max_inner_size = Some(dimensions.into()); @@ -347,7 +356,7 @@ impl UnownedWindow { } } - let mut normal_hints = util::NormalHints::new(xconn); + let mut normal_hints = XcbSizeHints::default(); normal_hints.set_position(position.map(|PhysicalPosition { x, y }| (x, y))); normal_hints.set_size(Some(dimensions)); normal_hints.set_min_size(min_inner_size.map(Into::into)); @@ -362,88 +371,66 @@ impl UnownedWindow { .base_size .map(|size| size.to_physical::(scale_factor).into()), ); - xconn.set_normal_hints(window.xwindow, normal_hints).queue(); + commands.push(xconn.set_normal_hints(window.xwindow, normal_hints)); } // Set window icons if let Some(icon) = window_attrs.window_icon { - window.set_icon_inner(icon).queue(); + commands.push(window.set_icon_inner(icon)); } // Opt into handling window close - unsafe { - (xconn.xlib.XSetWMProtocols)( - xconn.display, - window.xwindow, - &[event_loop.wm_delete_window, event_loop.net_wm_ping] as *const ffi::Atom - as *mut ffi::Atom, - 2, - ); - } //.queue(); + { + let prop = xconn.get_atom("WM_PROTOCOLS"); + let pending = xconn + .change_property( + window.xwindow, + prop, + ffi::XCB_ATOM_ATOM, + PropMode::Replace, + &[event_loop.wm_delete_window, event_loop.net_wm_ping], + ) + .into(); + commands.push(pending); + } // Set visibility (map window) if window_attrs.visible { - unsafe { - (xconn.xlib.XMapRaised)(xconn.display, window.xwindow); - } //.queue(); - } - - // Attempt to make keyboard input repeat detectable - unsafe { - let mut supported_ptr = ffi::False; - (xconn.xlib.XkbSetDetectableAutoRepeat)( - xconn.display, - ffi::True, - &mut supported_ptr, - ); - if supported_ptr == ffi::False { - return Err(os_error!(OsError::XMisc( - "`XkbSetDetectableAutoRepeat` failed" - ))); - } + commands.extend(window.map_raised()); } // Select XInput2 events let mask = { - let mask = ffi::XI_MotionMask - | ffi::XI_ButtonPressMask - | ffi::XI_ButtonReleaseMask - //| ffi::XI_KeyPressMask - //| ffi::XI_KeyReleaseMask - | ffi::XI_EnterMask - | ffi::XI_LeaveMask - | ffi::XI_FocusInMask - | ffi::XI_FocusOutMask - | ffi::XI_TouchBeginMask - | ffi::XI_TouchUpdateMask - | ffi::XI_TouchEndMask; + let mask = ffi::XCB_INPUT_XI_EVENT_MASK_MOTION + | ffi::XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS + | ffi::XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE + | ffi::XCB_INPUT_XI_EVENT_MASK_KEY_PRESS + | ffi::XCB_INPUT_XI_EVENT_MASK_KEY_RELEASE + | ffi::XCB_INPUT_XI_EVENT_MASK_ENTER + | ffi::XCB_INPUT_XI_EVENT_MASK_LEAVE + | ffi::XCB_INPUT_XI_EVENT_MASK_FOCUS_IN + | ffi::XCB_INPUT_XI_EVENT_MASK_FOCUS_OUT + | ffi::XCB_INPUT_XI_EVENT_MASK_TOUCH_BEGIN + | ffi::XCB_INPUT_XI_EVENT_MASK_TOUCH_UPDATE + | ffi::XCB_INPUT_XI_EVENT_MASK_TOUCH_END; mask }; - xconn - .select_xinput_events(window.xwindow, ffi::XIAllMasterDevices, mask) - .queue(); - - { - let result = event_loop.ime.borrow_mut().create_context(window.xwindow); - if let Err(err) = result { - let e = match err { - ImeContextCreationError::XError(err) => OsError::XError(err), - ImeContextCreationError::Null => { - OsError::XMisc("IME Context creation failed") - } - }; - return Err(os_error!(e)); - } - } + let pending = xconn.select_xinput_events( + window.xwindow as _, + ffi::XCB_INPUT_DEVICE_ALL_MASTER as _, + mask, + ); + commands.push(pending); // These properties must be set after mapping if window_attrs.maximized { - window.set_maximized_inner(window_attrs.maximized).queue(); + commands.push(window.set_maximized_inner(window_attrs.maximized)); } if window_attrs.fullscreen.is_some() { - window - .set_fullscreen_inner(window_attrs.fullscreen.clone()) - .map(|flusher| flusher.queue()); + if let Some(pending) = window.set_fullscreen_inner(window_attrs.fullscreen.clone()) + { + commands.extend(pending); + } if let Some(PhysicalPosition { x, y }) = position { let shared_state = window.shared_state.get_mut(); @@ -452,22 +439,21 @@ impl UnownedWindow { } } if window_attrs.always_on_top { - window - .set_always_on_top_inner(window_attrs.always_on_top) - .queue(); + commands.push(window.set_always_on_top_inner(window_attrs.always_on_top)); } } // We never want to give the user a broken window, since by then, it's too late to handle. - xconn - .sync_with_server() - .map(|_| window) - .map_err(|x_err| os_error!(OsError::XError(x_err))) + if let Err(e) = xconn.check_pending(commands) { + Err(os_error!(OsError::XError(e.into()))) + } else { + Ok(window) + } } - fn set_pid(&self) -> Option> { - let pid_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_PID\0") }; - let client_machine_atom = unsafe { self.xconn.get_atom_unchecked(b"WM_CLIENT_MACHINE\0") }; + fn set_pid(&self) -> Option { + let pid_atom = self.xconn.get_atom("_NET_WM_PID"); + let client_machine_atom = self.xconn.get_atom("WM_CLIENT_MACHINE"); unsafe { // 64 would suffice for Linux, but 256 will be enough everywhere (as per SUSv2). For instance, this is // the limit defined by OpenBSD. @@ -485,28 +471,26 @@ impl UnownedWindow { let hostname = slice::from_raw_parts(buffer.as_ptr() as *const c_char, hostname_length); - self.xconn - .change_property( - self.xwindow, - pid_atom, - ffi::XA_CARDINAL, - util::PropMode::Replace, - &[libc::getpid() as util::Cardinal], - ) - .queue(); - let flusher = self.xconn.change_property( + let pending1 = self.xconn.change_property( + self.xwindow, + pid_atom, + ffi::XCB_ATOM_CARDINAL, + util::PropMode::Replace, + &[libc::getpid() as util::Cardinal], + ); + let pending2 = self.xconn.change_property( self.xwindow, client_machine_atom, - ffi::XA_STRING, + ffi::XCB_ATOM_STRING, util::PropMode::Replace, &hostname[0..hostname_length], ); - Some(flusher) + Some(pending1.and_then(pending2)) } } - fn set_window_types(&self, window_types: Vec) -> util::Flusher<'_> { - let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_WINDOW_TYPE\0") }; + fn set_window_types(&self, window_types: Vec) -> XcbPendingCommand { + let hint_atom = self.xconn.get_atom("_NET_WM_WINDOW_TYPE"); let atoms: Vec<_> = window_types .iter() .map(|t| t.as_atom(&self.xconn)) @@ -515,15 +499,15 @@ impl UnownedWindow { self.xconn.change_property( self.xwindow, hint_atom, - ffi::XA_ATOM, + ffi::XCB_ATOM_ATOM, util::PropMode::Replace, &atoms, ) } - fn set_gtk_theme_variant(&self, variant: String) -> util::Flusher<'_> { - let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_GTK_THEME_VARIANT\0") }; - let utf8_atom = unsafe { self.xconn.get_atom_unchecked(b"UTF8_STRING\0") }; + fn set_gtk_theme_variant(&self, variant: String) -> XcbPendingCommand { + let hint_atom = self.xconn.get_atom("_GTK_THEME_VARIANT"); + let utf8_atom = self.xconn.get_atom("UTF8_STRING"); let variant = CString::new(variant).expect("`_GTK_THEME_VARIANT` contained null byte"); self.xconn.change_property( self.xwindow, @@ -537,16 +521,18 @@ impl UnownedWindow { fn set_netwm( &self, operation: util::StateOperation, - properties: (c_long, c_long, c_long, c_long), - ) -> util::Flusher<'_> { - let state_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE\0") }; + properties: (u32, u32, u32, u32), + ) -> XcbPendingCommand { + let state_atom = self.xconn.get_atom("_NET_WM_STATE"); self.xconn.send_client_msg( self.xwindow, - self.root, + self.screen.root, state_atom, - Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), + Some( + ffi::XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | ffi::XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, + ), [ - operation as c_long, + operation as u32, properties.0, properties.1, properties.2, @@ -555,28 +541,34 @@ impl UnownedWindow { ) } - fn set_fullscreen_hint(&self, fullscreen: bool) -> util::Flusher<'_> { - let fullscreen_atom = - unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_FULLSCREEN\0") }; - let flusher = self.set_netwm(fullscreen.into(), (fullscreen_atom as c_long, 0, 0, 0)); + fn set_fullscreen_hint(&self, fullscreen: bool) -> XcbPendingCommands { + let fullscreen_atom = self.xconn.get_atom("_NET_WM_STATE_FULLSCREEN"); + let mut pending: XcbPendingCommands = self + .set_netwm(fullscreen.into(), (fullscreen_atom, 0, 0, 0)) + .into(); if fullscreen { // Ensure that the fullscreen window receives input focus to prevent // locking up the user's display. unsafe { - (self.xconn.xlib.XSetInputFocus)( - self.xconn.display, - self.xwindow, - ffi::RevertToParent, - ffi::CurrentTime, - ); + let p = self + .xconn + .xcb + .xcb_set_input_focus_checked( + self.xconn.c, + ffi::XCB_INPUT_FOCUS_PARENT as _, + self.xwindow, + ffi::XCB_TIME_CURRENT_TIME, + ) + .into(); + pending.push(p); } } - flusher + pending } - fn set_fullscreen_inner(&self, fullscreen: Option) -> Option> { + fn set_fullscreen_inner(&self, fullscreen: Option) -> Option { let mut shared_state_lock = self.shared_state.lock(); match shared_state_lock.visibility { @@ -613,7 +605,7 @@ impl UnownedWindow { ) => { let monitor = video_mode.monitor.as_ref().unwrap(); shared_state_lock.desktop_video_mode = - Some((monitor.id, self.xconn.get_crtc_mode(monitor.id))); + Some((monitor.id, self.xconn.get_crtc_mode(monitor.id).unwrap())); } // Restore desktop video mode upon exiting exclusive fullscreen (&Some(Fullscreen::Exclusive(_)), &None) @@ -630,13 +622,13 @@ impl UnownedWindow { match fullscreen { None => { - let flusher = self.set_fullscreen_hint(false); + let mut pending = self.set_fullscreen_hint(false); let mut shared_state_lock = self.shared_state.lock(); if let Some(position) = shared_state_lock.restore_position.take() { drop(shared_state_lock); - self.set_position_inner(position.0, position.1).queue(); + pending.push(self.set_position_inner(position.0, position.1)); } - Some(flusher) + Some(pending) } Some(fullscreen) => { let (video_mode, monitor) = match fullscreen { @@ -687,12 +679,21 @@ impl UnownedWindow { .expect("failed to set video mode"); } - let window_position = self.outer_position_physical(); - self.shared_state.lock().restore_position = Some(window_position); + { + let window_position = self.outer_position_physical(); + let mut ss = self.shared_state.lock(); + // Don't overwrite the restore position if we're switching between + // fullscreen modes. + if ss.restore_position.is_none() { + ss.restore_position = Some(window_position); + } + } let monitor_origin: (i32, i32) = monitor.position().into(); - self.set_position_inner(monitor_origin.0, monitor_origin.1) - .queue(); - Some(self.set_fullscreen_hint(true)) + let mut pending: XcbPendingCommands = self + .set_position_inner(monitor_origin.0, monitor_origin.1) + .into(); + pending.extend(self.set_fullscreen_hint(true)); + Some(pending) } } } @@ -709,10 +710,14 @@ impl UnownedWindow { #[inline] pub fn set_fullscreen(&self, fullscreen: Option) { - if let Some(flusher) = self.set_fullscreen_inner(fullscreen) { - flusher - .sync() - .expect("Failed to change window fullscreen state"); + let fs = fullscreen.is_some(); + if let Some(pending) = self.set_fullscreen_inner(fullscreen) { + if let Err(e) = self.xconn.check_pending(pending) { + log::error!("Failed to change window fullscreen state: {}", e); + if fs { + panic!("Could not exit fullscreen."); + } + } self.invalidate_cached_frame_extents(); } } @@ -722,9 +727,12 @@ impl UnownedWindow { let mut shared_state = self.shared_state.lock(); match shared_state.visibility { - Visibility::No => unsafe { - (self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow); - }, + Visibility::No => { + let pending = self.unmap(); + if let Err(e) = self.xconn.check_pending1(pending) { + log::error!("Failed to unmap window: {}", e); + } + } Visibility::Yes => (), Visibility::YesWait => { shared_state.visibility = Visibility::Yes; @@ -743,93 +751,93 @@ impl UnownedWindow { } pub fn available_monitors(&self) -> Vec { - self.xconn.available_monitors() + match self.xconn.available_monitors_inner() { + Ok(m) => m, + Err(e) => { + log::error!("Could not query available monitors: {}", e); + vec![] + } + } } pub fn primary_monitor(&self) -> X11MonitorHandle { self.xconn.primary_monitor() } - fn set_minimized_inner(&self, minimized: bool) -> util::Flusher<'_> { - unsafe { - if minimized { - let screen = (self.xconn.xlib.XDefaultScreen)(self.xconn.display); - - (self.xconn.xlib.XIconifyWindow)(self.xconn.display, self.xwindow, screen); - - util::Flusher::new(&self.xconn) - } else { - let atom = self.xconn.get_atom_unchecked(b"_NET_ACTIVE_WINDOW\0"); - - self.xconn.send_client_msg( - self.xwindow, - self.root, - atom, - Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), - [1, ffi::CurrentTime as c_long, 0, 0, 0], - ) + fn set_minimized_inner(&self, minimized: bool) -> XcbPendingCommands { + let mut pending = XcbPendingCommands::new(); + if minimized { + pending.push( + self.xconn + .send_client_msg( + self.xwindow, + self.screen.root, + self.xconn.get_atom("WM_CHANGE_STATE"), + Some( + ffi::XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT + | ffi::XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, + ), + [3, 0, 0, 0, 0], + ) + .into(), + ); + } else { + if self.shared_state.lock().visibility != Visibility::No { + pending.extend(self.map_raised()); } } + pending } #[inline] pub fn set_minimized(&self, minimized: bool) { - self.set_minimized_inner(minimized) - .flush() - .expect("Failed to change window minimization"); + let pending = self.set_minimized_inner(minimized); + if let Err(e) = self.xconn.check_pending(pending) { + log::error!("Could not change minimized state: {}", e); + } } - fn set_maximized_inner(&self, maximized: bool) -> util::Flusher<'_> { - let horz_atom = unsafe { - self.xconn - .get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_HORZ\0") - }; - let vert_atom = unsafe { - self.xconn - .get_atom_unchecked(b"_NET_WM_STATE_MAXIMIZED_VERT\0") - }; - self.set_netwm( - maximized.into(), - (horz_atom as c_long, vert_atom as c_long, 0, 0), - ) + fn set_maximized_inner(&self, maximized: bool) -> XcbPendingCommand { + let horz_atom = self.xconn.get_atom("_NET_WM_STATE_MAXIMIZED_HORZ"); + let vert_atom = self.xconn.get_atom("_NET_WM_STATE_MAXIMIZED_VERT"); + self.set_netwm(maximized.into(), (horz_atom, vert_atom, 0, 0)) } #[inline] pub fn set_maximized(&self, maximized: bool) { - self.set_maximized_inner(maximized) - .flush() - .expect("Failed to change window maximization"); + let pending = self.set_maximized_inner(maximized); + if let Err(e) = self.xconn.check_pending1(pending) { + log::error!("Failed to change window maximization: {}", e); + } self.invalidate_cached_frame_extents(); } - fn set_title_inner(&self, title: &str) -> util::Flusher<'_> { - let wm_name_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_NAME\0") }; - let utf8_atom = unsafe { self.xconn.get_atom_unchecked(b"UTF8_STRING\0") }; - let title = CString::new(title).expect("Window title contained null byte"); - unsafe { - (self.xconn.xlib.XStoreName)( - self.xconn.display, - self.xwindow, - title.as_ptr() as *const c_char, - ); - self.xconn.change_property( - self.xwindow, - wm_name_atom, - utf8_atom, - util::PropMode::Replace, - title.as_bytes(), - ) - } + fn set_title_inner(&self, title: &str) -> XcbPendingCommands { + let pending1 = self.xconn.change_property( + self.xwindow, + ffi::XCB_ATOM_WM_NAME, + ffi::XCB_ATOM_STRING, + util::PropMode::Replace, + title.as_bytes(), + ); + let pending2 = self.xconn.change_property( + self.xwindow, + self.xconn.get_atom("_NET_WM_NAME"), + self.xconn.get_atom("UTF8_STRING"), + util::PropMode::Replace, + title.as_bytes(), + ); + pending1.and_then(pending2) } #[inline] pub fn set_title(&self, title: &str) { - self.set_title_inner(title) - .flush() - .expect("Failed to set window title"); + if let Err(e) = self.xconn.check_pending(self.set_title_inner(title)) { + log::error!("Could not set window title: {}", e); + } } - fn set_decorations_inner(&self, decorations: bool) -> util::Flusher<'_> { + fn set_decorations_inner(&self, decorations: bool) -> XcbPendingCommand { let mut hints = self.xconn.get_motif_hints(self.xwindow); hints.set_decorations(decorations); @@ -839,13 +847,14 @@ impl UnownedWindow { #[inline] pub fn set_decorations(&self, decorations: bool) { - self.set_decorations_inner(decorations) - .flush() - .expect("Failed to set decoration state"); + let pending = self.set_decorations_inner(decorations); + if let Err(e) = self.xconn.check_pending1(pending) { + log::error!("Could not set window decorations: {}", e); + } self.invalidate_cached_frame_extents(); } - fn set_maximizable_inner(&self, maximizable: bool) -> util::Flusher<'_> { + fn set_maximizable_inner(&self, maximizable: bool) -> XcbPendingCommand { let mut hints = self.xconn.get_motif_hints(self.xwindow); hints.set_maximizable(maximizable); @@ -853,37 +862,38 @@ impl UnownedWindow { self.xconn.set_motif_hints(self.xwindow, &hints) } - fn set_always_on_top_inner(&self, always_on_top: bool) -> util::Flusher<'_> { - let above_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_STATE_ABOVE\0") }; - self.set_netwm(always_on_top.into(), (above_atom as c_long, 0, 0, 0)) + fn set_always_on_top_inner(&self, always_on_top: bool) -> XcbPendingCommand { + let above_atom = self.xconn.get_atom("_NET_WM_STATE_ABOVE"); + self.set_netwm(always_on_top.into(), (above_atom, 0, 0, 0)) } #[inline] pub fn set_always_on_top(&self, always_on_top: bool) { - self.set_always_on_top_inner(always_on_top) - .flush() - .expect("Failed to set always-on-top state"); + let pending = self.set_always_on_top_inner(always_on_top); + if let Err(e) = self.xconn.check_pending1(pending) { + log::error!("Could not set always-on-top property: {}", e); + } } - fn set_icon_inner(&self, icon: Icon) -> util::Flusher<'_> { - let icon_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_ICON\0") }; + fn set_icon_inner(&self, icon: Icon) -> XcbPendingCommand { + let icon_atom = self.xconn.get_atom("_NET_WM_ICON"); let data = icon.to_cardinals(); self.xconn.change_property( self.xwindow, icon_atom, - ffi::XA_CARDINAL, + ffi::XCB_ATOM_CARDINAL, util::PropMode::Replace, data.as_slice(), ) } - fn unset_icon_inner(&self) -> util::Flusher<'_> { - let icon_atom = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_ICON\0") }; + fn unset_icon_inner(&self) -> XcbPendingCommand { + let icon_atom = self.xconn.get_atom("_NET_WM_ICON"); let empty_data: [util::Cardinal; 0] = []; self.xconn.change_property( self.xwindow, icon_atom, - ffi::XA_CARDINAL, + ffi::XCB_ATOM_CARDINAL, util::PropMode::Replace, &empty_data, ) @@ -891,12 +901,13 @@ impl UnownedWindow { #[inline] pub fn set_window_icon(&self, icon: Option) { - match icon { + let pending = match icon { Some(icon) => self.set_icon_inner(icon), None => self.unset_icon_inner(), + }; + if let Err(e) = self.xconn.check_pending1(pending) { + log::error!("Could not set window icon: {}", e); } - .flush() - .expect("Failed to set icons"); } #[inline] @@ -911,28 +922,51 @@ impl UnownedWindow { } if visible { - unsafe { - (self.xconn.xlib.XMapRaised)(self.xconn.display, self.xwindow); + if let Err(e) = self.xconn.check_pending(self.map_raised()) { + panic!("Failed to map window: {}", e); } - self.xconn - .flush_requests() - .expect("Failed to call XMapRaised"); shared_state.visibility = Visibility::YesWait; } else { - unsafe { - (self.xconn.xlib.XUnmapWindow)(self.xconn.display, self.xwindow); + if let Err(e) = self.xconn.check_pending1(self.unmap()) { + panic!("Failed to unmap window: {}", e); } - self.xconn - .flush_requests() - .expect("Failed to call XUnmapWindow"); shared_state.visibility = Visibility::No; } } + fn map_raised(&self) -> XcbPendingCommands { + unsafe { + let above = ffi::XCB_STACK_MODE_ABOVE as u32; + let pending1: XcbPendingCommand = self + .xconn + .xcb + .xcb_configure_window_checked( + self.xconn.c, + self.xwindow, + ffi::XCB_CONFIG_WINDOW_STACK_MODE as u16, + &above as *const _ as *const _, + ) + .into(); + let pending2 = self + .xconn + .xcb + .xcb_map_window_checked(self.xconn.c, self.xwindow) + .into(); + pending1.and_then(pending2) + } + } + + fn unmap(&self) -> XcbPendingCommand { + unsafe { + self.xconn + .xcb + .xcb_unmap_window_checked(self.xconn.c, self.xwindow) + .into() + } + } + fn update_cached_frame_extents(&self) { - let extents = self - .xconn - .get_frame_extents_heuristic(self.xwindow, self.root); + let extents = self.xconn.get_frame_extents_heuristic(self); (*self.shared_state.lock()).frame_extents = Some(extents); } @@ -967,8 +1001,8 @@ impl UnownedWindow { // This should be okay to unwrap since the only error XTranslateCoordinates can return // is BadWindow, and if the window handle is bad we have bigger problems. self.xconn - .translate_coords(self.xwindow, self.root) - .map(|coords| (coords.x_rel_root, coords.y_rel_root)) + .translate_coords(self.xwindow, self.screen.root) + .map(|coords| (coords.x_rel_root as i32, coords.y_rel_root as i32)) .unwrap() } @@ -977,10 +1011,10 @@ impl UnownedWindow { Ok(self.inner_position_physical().into()) } - pub(crate) fn set_position_inner(&self, mut x: i32, mut y: i32) -> util::Flusher<'_> { + pub(crate) fn set_position_inner(&self, mut x: i32, mut y: i32) -> XcbPendingCommand { // There are a few WMs that set client area position rather than window position, so // we'll translate for consistency. - if util::wm_name_is_one_of(&["Enlightenment", "FVWM"]) { + if self.screen.wm_name_is_one_of(&["Enlightenment", "FVWM"]) { let extents = (*self.shared_state.lock()).frame_extents.clone(); if let Some(extents) = extents { x += extents.frame_extents.left as i32; @@ -991,15 +1025,23 @@ impl UnownedWindow { } } unsafe { - (self.xconn.xlib.XMoveWindow)(self.xconn.display, self.xwindow, x as c_int, y as c_int); + self.xconn + .xcb + .xcb_configure_window_checked( + self.xconn.c, + self.xwindow, + (ffi::XCB_CONFIG_WINDOW_X | ffi::XCB_CONFIG_WINDOW_Y) as _, + [x, y].as_ptr() as _, + ) + .into() } - util::Flusher::new(&self.xconn) } pub(crate) fn set_position_physical(&self, x: i32, y: i32) { - self.set_position_inner(x, y) - .flush() - .expect("Failed to call `XMoveWindow`"); + let pending = self.set_position_inner(x, y); + if let Err(e) = self.xconn.check_pending1(pending) { + log::error!("Failed to set position: {}", e); + } } #[inline] @@ -1011,10 +1053,16 @@ impl UnownedWindow { pub(crate) fn inner_size_physical(&self) -> (u32, u32) { // This should be okay to unwrap since the only error XGetGeometry can return // is BadWindow, and if the window handle is bad we have bigger problems. - self.xconn + let res = self + .xconn .get_geometry(self.xwindow) - .map(|geo| (geo.width, geo.height)) - .unwrap() + .map(|geo| (geo.width as u32, geo.height as u32)); + match res { + Ok(r) => r, + Err(e) => { + panic!("Could not retrieve window size: {}", e); + } + } } #[inline] @@ -1035,16 +1083,20 @@ impl UnownedWindow { } pub(crate) fn set_inner_size_physical(&self, width: u32, height: u32) { - unsafe { - (self.xconn.xlib.XResizeWindow)( - self.xconn.display, - self.xwindow, - width as c_uint, - height as c_uint, - ); - self.xconn.flush_requests() + let pending = unsafe { + self.xconn + .xcb + .xcb_configure_window_checked( + self.xconn.c, + self.xwindow, + (ffi::XCB_CONFIG_WINDOW_WIDTH | ffi::XCB_CONFIG_WINDOW_HEIGHT) as _, + [width, height].as_ptr() as _, + ) + .into() + }; + if let Err(e) = self.xconn.check_pending1(pending) { + log::error!("Failed to resize window: {}", e); } - .expect("Failed to call `XResizeWindow`"); } #[inline] @@ -1054,20 +1106,20 @@ impl UnownedWindow { self.set_inner_size_physical(width, height); } - fn update_normal_hints(&self, callback: F) -> Result<(), XError> + fn update_normal_hints(&self, callback: F) -> Result where - F: FnOnce(&mut util::NormalHints<'_>) -> (), + F: FnOnce(&mut XcbSizeHints) -> (), { let mut normal_hints = self.xconn.get_normal_hints(self.xwindow)?; callback(&mut normal_hints); - self.xconn - .set_normal_hints(self.xwindow, normal_hints) - .flush() + Ok(self.xconn.set_normal_hints(self.xwindow, normal_hints)) } - pub(crate) fn set_min_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { + pub(crate) fn set_min_inner_size_physical( + &self, + dimensions: Option<(u32, u32)>, + ) -> Result { self.update_normal_hints(|normal_hints| normal_hints.set_min_size(dimensions)) - .expect("Failed to call `XSetWMNormalHints`"); } #[inline] @@ -1075,12 +1127,17 @@ impl UnownedWindow { self.shared_state.lock().min_inner_size = dimensions; let physical_dimensions = dimensions.map(|dimensions| dimensions.to_physical::(self.scale_factor()).into()); - self.set_min_inner_size_physical(physical_dimensions); + let pending = self.set_min_inner_size_physical(physical_dimensions); + if let Err(e) = pending.and_then(|p| self.xconn.check_pending1(p).map_err(|e| e.into())) { + log::error!("Could not set minimum size: {}", e); + } } - pub(crate) fn set_max_inner_size_physical(&self, dimensions: Option<(u32, u32)>) { + pub(crate) fn set_max_inner_size_physical( + &self, + dimensions: Option<(u32, u32)>, + ) -> Result { self.update_normal_hints(|normal_hints| normal_hints.set_max_size(dimensions)) - .expect("Failed to call `XSetWMNormalHints`"); } #[inline] @@ -1088,7 +1145,10 @@ impl UnownedWindow { self.shared_state.lock().max_inner_size = dimensions; let physical_dimensions = dimensions.map(|dimensions| dimensions.to_physical::(self.scale_factor()).into()); - self.set_max_inner_size_physical(physical_dimensions); + let pending = self.set_max_inner_size_physical(physical_dimensions); + if let Err(e) = pending.and_then(|p| self.xconn.check_pending1(p).map_err(|e| e.into())) { + log::error!("Could not set maximum size: {}", e); + } } pub(crate) fn adjust_for_dpi( @@ -1100,7 +1160,7 @@ impl UnownedWindow { shared_state: &SharedState, ) -> (u32, u32) { let scale_factor = new_scale_factor / old_scale_factor; - self.update_normal_hints(|normal_hints| { + let pending = self.update_normal_hints(|normal_hints| { let dpi_adjuster = |size: Size| -> (u32, u32) { size.to_physical::(new_scale_factor).into() }; let max_size = shared_state.max_inner_size.map(&dpi_adjuster); @@ -1111,8 +1171,10 @@ impl UnownedWindow { normal_hints.set_min_size(min_size); normal_hints.set_resize_increments(resize_increments); normal_hints.set_base_size(base_size); - }) - .expect("Failed to update normal hints"); + }); + if let Err(e) = pending.and_then(|p| self.xconn.check_pending1(p).map_err(|e| e.into())) { + log::error!("Could not update normal hints: {}", e); + } let new_width = (width as f64 * scale_factor).round() as u32; let new_height = (height as f64 * scale_factor).round() as u32; @@ -1121,7 +1183,7 @@ impl UnownedWindow { } pub fn set_resizable(&self, resizable: bool) { - if util::wm_name_is_one_of(&["Xfwm4"]) { + if self.screen.wm_name_is_one_of(&["Xfwm4"]) { // Making the window unresizable on Xfwm prevents further changes to `WM_NORMAL_HINTS` from being detected. // This makes it impossible for resizing to be re-enabled, and also breaks DPI scaling. As such, we choose // the lesser of two evils and do nothing. @@ -1140,7 +1202,7 @@ impl UnownedWindow { (window_size.clone(), window_size) }; - self.set_maximizable_inner(resizable).queue(); + let pending1 = self.set_maximizable_inner(resizable); let scale_factor = self.scale_factor(); let min_inner_size = min_size @@ -1149,43 +1211,32 @@ impl UnownedWindow { let max_inner_size = max_size .map(|size| size.to_physical::(scale_factor)) .map(Into::into); - self.update_normal_hints(|normal_hints| { + let pending2 = self.update_normal_hints(|normal_hints| { normal_hints.set_min_size(min_inner_size); normal_hints.set_max_size(max_inner_size); - }) - .expect("Failed to call `XSetWMNormalHints`"); - } - - #[inline] - pub fn xlib_display(&self) -> *mut c_void { - self.xconn.display as _ - } - - #[inline] - pub fn xlib_screen_id(&self) -> c_int { - self.screen_id - } - - #[inline] - pub fn xlib_xconnection(&self) -> Arc { - Arc::clone(&self.xconn) - } - - #[inline] - pub fn xlib_window(&self) -> c_ulong { - self.xwindow - } + }); + + let res = match pending2 { + Ok(p) => self + .xconn + .check_pending(pending1.and_then(p)) + .map_err(|e| e.into()), + Err(e) => { + self.xconn.discard(pending1); + Err(e) + } + }; - #[inline] - pub fn xcb_connection(&self) -> *mut c_void { - unsafe { (self.xconn.xlib_xcb.XGetXCBConnection)(self.xconn.display) as *mut _ } + if let Err(e) = res { + log::error!("Could not change the resizable property: {}", e); + } } #[inline] pub fn set_cursor_icon(&self, cursor: CursorIcon) { let old_cursor = replace(&mut *self.cursor.lock(), cursor); if cursor != old_cursor && *self.cursor_visible.lock() { - self.xconn.set_cursor_icon(self.xwindow, Some(cursor)); + self.xconn.set_cursor_icon(self.xwindow as _, Some(cursor)); } } @@ -1195,55 +1246,78 @@ impl UnownedWindow { if grab == *grabbed_lock { return Ok(()); } - unsafe { + let ungrab = unsafe { // We ungrab before grabbing to prevent passive grabs from causing `AlreadyGrabbed`. // Therefore, this is common to both codepaths. - (self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime); - } + self.xconn + .xcb + .xcb_ungrab_pointer_checked(self.xconn.c, ffi::XCB_TIME_CURRENT_TIME) + .into() + }; let result = if grab { - let result = unsafe { - (self.xconn.xlib.XGrabPointer)( - self.xconn.display, - self.xwindow, - ffi::True, - (ffi::ButtonPressMask - | ffi::ButtonReleaseMask - | ffi::EnterWindowMask - | ffi::LeaveWindowMask - | ffi::PointerMotionMask - | ffi::PointerMotionHintMask - | ffi::Button1MotionMask - | ffi::Button2MotionMask - | ffi::Button3MotionMask - | ffi::Button4MotionMask - | ffi::Button5MotionMask - | ffi::ButtonMotionMask - | ffi::KeymapStateMask) as c_uint, - ffi::GrabModeAsync, - ffi::GrabModeAsync, - self.xwindow, - 0, - ffi::CurrentTime, - ) - }; + self.xconn.discard(ungrab); + loop { + let result = unsafe { + let cookie = self.xconn.xcb.xcb_grab_pointer( + self.xconn.c, + 1, + self.xwindow, + (ffi::XCB_EVENT_MASK_BUTTON_PRESS + | ffi::XCB_EVENT_MASK_BUTTON_RELEASE + | ffi::XCB_EVENT_MASK_ENTER_WINDOW + | ffi::XCB_EVENT_MASK_LEAVE_WINDOW + | ffi::XCB_EVENT_MASK_POINTER_MOTION + | ffi::XCB_EVENT_MASK_POINTER_MOTION_HINT + | ffi::XCB_EVENT_MASK_BUTTON_1_MOTION + | ffi::XCB_EVENT_MASK_BUTTON_2_MOTION + | ffi::XCB_EVENT_MASK_BUTTON_3_MOTION + | ffi::XCB_EVENT_MASK_BUTTON_4_MOTION + | ffi::XCB_EVENT_MASK_BUTTON_5_MOTION + | ffi::XCB_EVENT_MASK_BUTTON_MOTION + | ffi::XCB_EVENT_MASK_KEYMAP_STATE) as u16, + ffi::XCB_GRAB_MODE_ASYNC as u8, + ffi::XCB_GRAB_MODE_ASYNC as u8, + self.xwindow, + 0, + ffi::XCB_TIME_CURRENT_TIME, + ); + let mut err = ptr::null_mut(); + let reply = + self.xconn + .xcb + .xcb_grab_pointer_reply(self.xconn.c, cookie, &mut err); + self.xconn.check(reply, err) + }; + let reply = match result { + Ok(r) => r, + Err(e) => { + break Err(ExternalError::Os(os_error!(OsError::XError(e.into())))); + } + }; - match result { - ffi::GrabSuccess => Ok(()), - ffi::AlreadyGrabbed => { - Err("Cursor could not be grabbed: already grabbed by another client") - } - ffi::GrabInvalidTime => Err("Cursor could not be grabbed: invalid time"), - ffi::GrabNotViewable => { - Err("Cursor could not be grabbed: grab location not viewable") + let res = match reply.status as ffi::xcb_grab_status_t { + ffi::XCB_GRAB_STATUS_SUCCESS => Ok(()), + ffi::XCB_GRAB_STATUS_ALREADY_GRABBED => { + Err("Cursor could not be grabbed: already grabbed by another client") + } + ffi::XCB_GRAB_STATUS_INVALID_TIME => { + Err("Cursor could not be grabbed: invalid time") + } + ffi::XCB_GRAB_STATUS_NOT_VIEWABLE => { + Err("Cursor could not be grabbed: grab location not viewable") + } + ffi::XCB_GRAB_STATUS_FROZEN => { + Err("Cursor could not be grabbed: frozen by another client") + } + _ => unreachable!(), } - ffi::GrabFrozen => Err("Cursor could not be grabbed: frozen by another client"), - _ => unreachable!(), + .map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err)))); + break res; } - .map_err(|err| ExternalError::Os(os_error!(OsError::XMisc(err)))) } else { self.xconn - .flush_requests() - .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err)))) + .check_pending1(ungrab) + .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into())))) }; if result.is_ok() { *grabbed_lock = grab; @@ -1264,7 +1338,7 @@ impl UnownedWindow { }; *visible_lock = visible; drop(visible_lock); - self.xconn.set_cursor_icon(self.xwindow, cursor); + self.xconn.set_cursor_icon(self.xwindow as _, cursor); } #[inline] @@ -1272,89 +1346,93 @@ impl UnownedWindow { self.current_monitor().scale_factor } - pub fn set_cursor_position_physical(&self, x: i32, y: i32) -> Result<(), ExternalError> { + pub fn set_cursor_position_physical(&self, x: i16, y: i16) -> Result<(), ExternalError> { unsafe { - (self.xconn.xlib.XWarpPointer)(self.xconn.display, 0, self.xwindow, 0, 0, 0, 0, x, y); + let pending = self + .xconn + .xcb + .xcb_warp_pointer_checked(self.xconn.c, 0, self.xwindow, 0, 0, 0, 0, x, y) + .into(); self.xconn - .flush_requests() - .map_err(|e| ExternalError::Os(os_error!(OsError::XError(e)))) + .check_pending1(pending) + .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into())))) } } #[inline] pub fn set_cursor_position(&self, position: Position) -> Result<(), ExternalError> { - let (x, y) = position.to_physical::(self.scale_factor()).into(); + let (x, y) = position.to_physical::(self.scale_factor()).into(); self.set_cursor_position_physical(x, y) } pub fn drag_window(&self) -> Result<(), ExternalError> { let pointer = self .xconn - .query_pointer(self.xwindow, util::VIRTUAL_CORE_POINTER) - .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?; + .query_pointer(self.xwindow as _, util::VIRTUAL_CORE_POINTER) + .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into()))))?; let window = self.inner_position().map_err(ExternalError::NotSupported)?; - let message = unsafe { self.xconn.get_atom_unchecked(b"_NET_WM_MOVERESIZE\0") }; + let message = self.xconn.get_atom("_NET_WM_MOVERESIZE"); // we can't use `set_cursor_grab(false)` here because it doesn't run `XUngrabPointer` // if the cursor isn't currently grabbed let mut grabbed_lock = self.cursor_grabbed.lock(); - unsafe { - (self.xconn.xlib.XUngrabPointer)(self.xconn.display, ffi::CurrentTime); - } + let pending = unsafe { + self.xconn + .xcb + .xcb_ungrab_pointer_checked(self.xconn.c, ffi::XCB_TIME_CURRENT_TIME) + .into() + }; self.xconn - .flush_requests() - .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err))))?; + .check_pending1(pending) + .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into()))))?; *grabbed_lock = false; // we keep the lock until we are done + let pending = self.xconn.send_client_msg( + self.xwindow, + self.screen.root, + message, + Some( + ffi::XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | ffi::XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY, + ), + [ + window.x + util::fp1616_to_f64(pointer.win_x) as i32, + window.y + util::fp1616_to_f64(pointer.win_y) as i32, + 8, // _NET_WM_MOVERESIZE_MOVE + ffi::XCB_BUTTON_INDEX_1 as _, + 1, + ], + ); + self.xconn - .send_client_msg( - self.xwindow, - self.root, - message, - Some(ffi::SubstructureRedirectMask | ffi::SubstructureNotifyMask), - [ - (window.x as c_long + pointer.win_x as c_long), - (window.y as c_long + pointer.win_y as c_long), - 8, // _NET_WM_MOVERESIZE_MOVE - ffi::Button1 as c_long, - 1, - ], - ) - .flush() - .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err)))) + .check_pending1(pending) + .map_err(|err| ExternalError::Os(os_error!(OsError::XError(err.into())))) } - pub(crate) fn set_ime_position_physical(&self, x: i32, y: i32) { - let _ = self - .ime_sender - .lock() - .send((self.xwindow, x as i16, y as i16)); - } + #[inline] + pub fn set_ime_position(&self, _spot: Position) {} #[inline] - pub fn set_ime_position(&self, spot: Position) { - let (x, y) = spot.to_physical::(self.scale_factor()).into(); - self.set_ime_position_physical(x, y); + pub fn reset_dead_keys(&self) { + self.reset_dead_keys.fetch_add(1, Relaxed); } #[inline] pub fn request_user_attention(&self, request_type: Option) { - let mut wm_hints = self - .xconn - .get_wm_hints(self.xwindow) - .expect("`XGetWMHints` failed"); - if request_type.is_some() { - (*wm_hints).flags |= ffi::XUrgencyHint; - } else { - (*wm_hints).flags &= !ffi::XUrgencyHint; + let mut wm_hints = match self.xconn.get_wm_hints(self.xwindow) { + Ok(h) => h, + Err(e) => { + log::error!("Could not retrieve WM hints: {}", e); + return; + } + }; + wm_hints.set_urgency(request_type.is_some()); + let pending = self.xconn.set_wm_hints(self.xwindow, wm_hints); + if let Err(e) = self.xconn.check_pending1(pending) { + log::error!("Could not set WM hints: {}", e); } - self.xconn - .set_wm_hints(self.xwindow, wm_hints) - .flush() - .expect("Failed to set urgency hint"); } #[inline] @@ -1368,11 +1446,11 @@ impl UnownedWindow { } #[inline] - pub fn raw_window_handle(&self) -> XlibHandle { - XlibHandle { + pub fn raw_window_handle(&self) -> XcbHandle { + XcbHandle { window: self.xwindow, - display: self.xconn.display as _, - ..XlibHandle::empty() + connection: self.xconn.c as _, + ..XcbHandle::empty() } } } diff --git a/src/platform_impl/linux/x11/xdisplay.rs b/src/platform_impl/linux/x11/xdisplay.rs index 25065f045d..2a02aed5a9 100644 --- a/src/platform_impl/linux/x11/xdisplay.rs +++ b/src/platform_impl/linux/x11/xdisplay.rs @@ -1,109 +1,326 @@ -use std::{collections::HashMap, error::Error, fmt, os::raw::c_int, ptr}; +use std::sync::Arc; +use std::{collections::HashMap, fmt, mem, os::raw::c_int, ptr}; -use libc; use parking_lot::Mutex; use crate::window::CursorIcon; use super::ffi; +use crate::platform_impl::x11::xlib::Xlib; +use crate::platform_impl::x11::MonitorHandle; +use thiserror::Error; +use xcb_dl::{Xcb, XcbRandr, XcbRender, XcbXfixes, XcbXinput, XcbXkb}; +use xcb_dl_util::cursor::XcbCursorContext; +use xcb_dl_util::error::{XcbError, XcbErrorParser}; /// A connection to an X server. pub struct XConnection { - pub xlib: ffi::Xlib, - /// Exposes XRandR functions from version < 1.5 - pub xrandr: ffi::Xrandr_2_2_0, - /// Exposes XRandR functions from version = 1.5 - pub xrandr_1_5: Option, - pub xcursor: ffi::Xcursor, - pub xinput2: ffi::XInput2, - pub xlib_xcb: ffi::Xlib_xcb, - pub xrender: ffi::Xrender, - pub display: *mut ffi::Display, - pub x11_fd: c_int, - pub latest_error: Mutex>, - pub cursor_cache: Mutex, ffi::Cursor>>, + pub c: *mut ffi::xcb_connection_t, + pub fd: c_int, + + pub atom_cache: Mutex>, + + pub default_screen_id: usize, + pub screens: Vec>, + + pub errors: XcbErrorParser, + + pub xcb: Box, + + pub xkb: Box, + pub xkb_first_event: u8, + + pub xfixes: Box, + pub xfixes_first_event: u8, + + pub xinput: Box, + pub xinput_extension: u8, + + pub render: Box, + + pub randr: Box, + pub randr_version: (u32, u32), + pub randr_first_event: u8, + + pub cursors: XcbCursorContext, + pub cursor_cache: Mutex, ffi::xcb_cursor_t>>, + + pub monitors: Mutex>>, + + pub xlib: Option, } unsafe impl Send for XConnection {} unsafe impl Sync for XConnection {} -pub type XErrorHandler = - Option libc::c_int>; - impl XConnection { - pub fn new(error_handler: XErrorHandler) -> Result { - // opening the libraries - let xlib = ffi::Xlib::open()?; - let xcursor = ffi::Xcursor::open()?; - let xrandr = ffi::Xrandr_2_2_0::open()?; - let xrandr_1_5 = ffi::Xrandr::open().ok(); - let xinput2 = ffi::XInput2::open()?; - let xlib_xcb = ffi::Xlib_xcb::open()?; - let xrender = ffi::Xrender::open()?; - - unsafe { (xlib.XInitThreads)() }; - unsafe { (xlib.XSetErrorHandler)(error_handler) }; - - // calling XOpenDisplay - let display = unsafe { - let display = (xlib.XOpenDisplay)(ptr::null()); - if display.is_null() { - return Err(XNotSupported::XOpenDisplayFailed); - } - display + pub fn new() -> Result { + unsafe { Self::new_unsafe() } + } + + unsafe fn new_unsafe() -> Result { + macro_rules! load { + ($id:ident, $name:expr) => { + match $id::load_loose() { + Ok(l) => Box::new(l), + Err(e) => { + return Err(XNotSupported::LibraryOpenError { + library: $name.to_string(), + error: e.to_string(), + }) + } + } + }; + } + + let xcb = load!(Xcb, "libxcb"); + let xfixes = load!(XcbXfixes, "libxcb_xfixes"); + let xinput = load!(XcbXinput, "libxcb_xinput"); + let render = load!(XcbRender, "libxcb_render"); + let xkb = load!(XcbXkb, "libxcb_xkb"); + let randr = load!(XcbRandr, "libxcb_randr"); + + let (c, default_screen_id, xlib) = if super::xlib::use_xlib() { + let xlib = super::xlib::connect()?; + (xlib.c, xlib.default_screen_id, Some(xlib)) + } else { + let mut default_screen_id = 0; + let c = xcb.xcb_connect(ptr::null(), &mut default_screen_id); + (c, default_screen_id, None) }; - // Get X11 socket file descriptor - let fd = unsafe { (xlib.XConnectionNumber)(display) }; + let errors = XcbErrorParser::new(&xcb, c); - Ok(XConnection { - xlib, - xrandr, - xrandr_1_5, - xcursor, - xinput2, - xlib_xcb, - xrender, - display, - x11_fd: fd, - latest_error: Mutex::new(None), - cursor_cache: Default::default(), - }) - } + if let Err(e) = errors.check_connection(&xcb) { + return Err(XNotSupported::ConnectFailed { + error: e.to_string(), + }); + } - /// Checks whether an error has been triggered by the previous function calls. - #[inline] - pub fn check_errors(&self) -> Result<(), XError> { - let error = self.latest_error.lock().take(); - if let Some(error) = error { - Err(error) + let close = if xlib.is_some() { + None } else { - Ok(()) + Some(CloseConnection { c, xcb: &xcb }) + }; + + xcb_dl_util::log::log_connection(log::Level::Trace, &xcb, c); + + macro_rules! check_ext { + ($ext:expr, $name:expr) => {{ + let data = xcb.xcb_get_extension_data(c, $ext); + if data.is_null() || (*data).present == 0 { + return Err(XNotSupported::MissingExtension { + extension: $name.to_string(), + }); + } + data + }}; } - } - /// Ignores any previous error. - #[inline] - pub fn ignore_error(&self) { - *self.latest_error.lock() = None; + let xinput_data = check_ext!(xinput.xcb_input_id(), ffi::XCB_INPUT_NAME_STR); + let xfixes_data = check_ext!(xfixes.xcb_xfixes_id(), ffi::XCB_XFIXES_NAME_STR); + let _render_data = check_ext!(render.xcb_render_id(), ffi::XCB_RENDER_NAME_STR); + let xkb_data = check_ext!(xkb.xcb_xkb_id(), ffi::XCB_XKB_NAME_STR); + let randr_data = check_ext!(randr.xcb_randr_id(), ffi::XCB_RANDR_NAME_STR); + + macro_rules! enable_extension { + ($so:expr, $query:ident, $reply:ident, $major:expr, $minor:expr, $name:expr) => { + enable_extension!( + $so, + $query, + $reply, + major_version, + minor_version, + $major, + $minor, + $name + ) + }; + ($so:expr, $query:ident, $reply:ident, $server_major:ident, $server_minor:ident, $major:expr, $minor:expr, $name:expr) => {{ + let mut err = ptr::null_mut(); + let res = $so.$reply(c, $so.$query(c, $major, $minor), &mut err); + let res = match errors.check(&xcb, res, err) { + Ok(r) => r, + Err(e) => { + log::error!("Could not enable `{}` extension: {}", $name, e); + return Err(XNotSupported::MissingExtension { + extension: $name.to_string(), + }); + } + }; + let version = (res.$server_major, res.$server_major); + if version < ($major, $minor) { + log::warn!( + "winit uses the `{}` extension in version {:?} but the X server only \ + provides version {:?}, some features might be unavailable.", + $name, + ($major, $minor), + version + ); + } + version + }}; + } + + enable_extension!( + xfixes, + xcb_xfixes_query_version, + xcb_xfixes_query_version_reply, + 1, + 0, + ffi::XCB_XFIXES_NAME_STR + ); + enable_extension!( + xinput, + xcb_input_xi_query_version, + xcb_input_xi_query_version_reply, + 2, + 2, + ffi::XCB_INPUT_NAME_STR + ); + enable_extension!( + render, + xcb_render_query_version, + xcb_render_query_version_reply, + 0, + 8, + ffi::XCB_RENDER_NAME_STR + ); + enable_extension!( + xkb, + xcb_xkb_use_extension, + xcb_xkb_use_extension_reply, + server_major, + server_minor, + 1, + 0, + ffi::XCB_XKB_NAME_STR + ); + let randr_version = enable_extension!( + randr, + xcb_randr_query_version, + xcb_randr_query_version_reply, + 1, + 3, + ffi::XCB_RANDR_NAME_STR + ); + + let cursors = XcbCursorContext::new(&xcb, &render, c); + + let fd = xcb.xcb_get_file_descriptor(c); + + let screens = { + let mut screen_iter = xcb.xcb_setup_roots_iterator(xcb.xcb_get_setup(c)); + let mut screens = vec![]; + let mut i = 0; + while screen_iter.rem > 0 { + let screen = &*screen_iter.data; + screens.push(Arc::new(Screen { + screen_id: i, + root: screen.root, + root_depth: screen.root_depth, + supported_hints: Default::default(), + wm_name: Default::default(), + })); + xcb.xcb_screen_next(&mut screen_iter); + i += 1; + } + screens + }; + let default_screen_id = default_screen_id.max(0) as usize; + if default_screen_id > screens.len() { + return Err(XNotSupported::ConnectFailed { + error: format!("Default screen id out of bounds"), + }); + } + + for screen in &screens { + let screen = &*screen; + let mask = ffi::XCB_RANDR_NOTIFY_MASK_CRTC_CHANGE + | ffi::XCB_RANDR_NOTIFY_MASK_OUTPUT_PROPERTY + | ffi::XCB_RANDR_NOTIFY_MASK_SCREEN_CHANGE; + let cookie = randr.xcb_randr_select_input_checked(c, screen.root, mask as _); + if let Err(e) = errors.check_cookie(&xcb, cookie) { + return Err(XNotSupported::ConnectFailed { + error: format!("Cannot listen for RandR events: {}", e), + }); + } + } + + mem::forget(close); + + Ok(XConnection { + atom_cache: Default::default(), + c, + fd, + default_screen_id, + screens, + cursors, + errors, + xcb, + xkb, + xkb_first_event: (*xkb_data).first_event, + xfixes, + xfixes_first_event: (*xfixes_data).first_event, + xinput, + xinput_extension: (*xinput_data).major_opcode, + render, + randr, + randr_version, + randr_first_event: (*randr_data).first_event, + cursor_cache: Default::default(), + monitors: Default::default(), + xlib, + }) } } impl fmt::Debug for XConnection { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.display.fmt(f) + f.debug_struct("XConnection").finish_non_exhaustive() } } impl Drop for XConnection { #[inline] fn drop(&mut self) { - unsafe { (self.xlib.XCloseDisplay)(self.display) }; + if self.xlib.is_none() { + unsafe { + self.xcb.xcb_disconnect(self.c); + } + } } } -/// Error triggered by xlib. -#[derive(Debug, Clone)] +#[derive(Debug)] +pub struct Screen { + pub screen_id: usize, + pub root: ffi::xcb_window_t, + pub root_depth: u8, + pub supported_hints: Mutex>, + pub wm_name: Mutex>, +} + +impl Screen { + pub fn hint_is_supported(&self, hint: ffi::xcb_atom_t) -> bool { + self.supported_hints.lock().contains(&hint) + } + + pub fn wm_name_is_one_of(&self, names: &[&str]) -> bool { + if let Some(ref name) = *self.wm_name.lock() { + names.contains(&name.as_str()) + } else { + false + } + } +} + +unsafe impl Sync for Screen {} +unsafe impl Send for Screen {} + +/// Error triggered by xcb. +#[derive(Clone, Debug, Error)] +#[error("X error: {description} (code: {error_code}, request code: {request_code}, minor code: {minor_code})")] pub struct XError { pub description: String, pub error_code: u8, @@ -111,55 +328,44 @@ pub struct XError { pub minor_code: u8, } -impl Error for XError {} - -impl fmt::Display for XError { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!( - formatter, - "X error: {} (code: {}, request code: {}, minor code: {})", - self.description, self.error_code, self.request_code, self.minor_code - ) +impl From for XError { + fn from(e: XcbError) -> Self { + Self { + description: e.to_string(), + error_code: e.error_code, + request_code: e.major, + minor_code: e.minor as _, + } } } -/// Error returned if this system doesn't have XLib or can't create an X connection. -#[derive(Clone, Debug)] +/// Error returned if this system doesn't have libxcb or can't create an X connection. +#[derive(Clone, Debug, Error)] +#[non_exhaustive] pub enum XNotSupported { /// Failed to load one or several shared libraries. - LibraryOpenError(ffi::OpenError), - /// Connecting to the X server with `XOpenDisplay` failed. - XOpenDisplayFailed, // TODO: add better message -} - -impl From for XNotSupported { - #[inline] - fn from(err: ffi::OpenError) -> XNotSupported { - XNotSupported::LibraryOpenError(err) - } + #[error("Could not open {library}: {error}")] + LibraryOpenError { library: String, error: String }, + /// A required extension is not available. + #[error("The required extension {extension} is not available")] + MissingExtension { extension: String }, + /// Connecting to the X server with `xcb_connect` failed. + #[error("Cannot connect to the X server: {error}")] + ConnectFailed { error: String }, + /// The X server has no attached screens. + #[error("The X server has no attached screens")] + NoScreens, } -impl XNotSupported { - fn description(&self) -> &'static str { - match self { - XNotSupported::LibraryOpenError(_) => "Failed to load one of xlib's shared libraries", - XNotSupported::XOpenDisplayFailed => "Failed to open connection to X server", - } - } +struct CloseConnection<'a> { + c: *mut ffi::xcb_connection_t, + xcb: &'a Xcb, } -impl Error for XNotSupported { - #[inline] - fn source(&self) -> Option<&(dyn Error + 'static)> { - match *self { - XNotSupported::LibraryOpenError(ref err) => Some(err), - _ => None, +impl<'a> Drop for CloseConnection<'a> { + fn drop(&mut self) { + unsafe { + self.xcb.xcb_disconnect(self.c); } } } - -impl fmt::Display for XNotSupported { - fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - formatter.write_str(self.description()) - } -} diff --git a/src/platform_impl/linux/x11/xlib.rs b/src/platform_impl/linux/x11/xlib.rs new file mode 100644 index 0000000000..e62cebcb75 --- /dev/null +++ b/src/platform_impl/linux/x11/xlib.rs @@ -0,0 +1,147 @@ +pub use imp::*; + +#[cfg(not(feature = "xlib"))] +mod imp { + use crate::platform::unix::XNotSupported; + use std::os::raw::c_int; + use xcb_dl::ffi; + + pub struct Xlib { + pub dpy: usize, + pub c: *mut ffi::xcb_connection_t, + pub default_screen_id: c_int, + } + + pub fn connect() -> Result { + unreachable!(); + } + + pub fn use_xlib() -> bool { + false + } +} + +#[cfg(feature = "xlib")] +mod imp { + use crate::platform::unix::XNotSupported; + use crate::platform_impl::x11::XError; + use parking_lot::Mutex; + use std::ffi::CStr; + use std::mem::{ManuallyDrop, MaybeUninit}; + use std::os::raw::{c_char, c_int}; + use std::ptr; + use std::sync::{Arc, Weak}; + use x11_dl::xlib_xcb::XEventQueueOwner; + use xcb_dl::ffi; + + pub struct Xlib { + pub dpy: usize, + xlib: Arc, + pub c: *mut ffi::xcb_connection_t, + pub default_screen_id: c_int, + } + + static LIB: Mutex>>> = + parking_lot::const_mutex(None); + + pub fn connect() -> Result { + unsafe { + let mut lib = LIB.lock(); + let xlib = loop { + if let Some(lib) = &*lib { + if let Some(lib) = lib.upgrade() { + break lib; + } + } + match x11_dl::xlib::Xlib::open() { + Ok(l) => { + let xlib = Arc::new(l); + *lib = Some(ManuallyDrop::new(Arc::downgrade(&xlib))); + break xlib; + } + Err(e) => { + return Err(XNotSupported::LibraryOpenError { + library: "libX11".to_string(), + error: e.to_string(), + }) + } + } + }; + let xlib_xcb = match x11_dl::xlib_xcb::Xlib_xcb::open() { + Ok(l) => l, + Err(e) => { + return Err(XNotSupported::LibraryOpenError { + library: "libX11-xcb".to_string(), + error: e.to_string(), + }) + } + }; + (xlib.XInitThreads)(); + let dpy = (xlib.XOpenDisplay)(ptr::null()); + if dpy.is_null() { + return Err(XNotSupported::ConnectFailed { + error: "Unknown xlib error".to_string(), + }); + } + let default_screen_id = (xlib.XDefaultScreen)(dpy); + let c = (xlib_xcb.XGetXCBConnection)(dpy) as _; + (xlib_xcb.XSetEventQueueOwner)(dpy, XEventQueueOwner::XCBOwnsEventQueue); + (xlib.XSetErrorHandler)(Some(x_error_callback)); + Ok(Xlib { + dpy: dpy as _, + xlib, + c, + default_screen_id, + }) + } + } + + impl Drop for Xlib { + fn drop(&mut self) { + unsafe { + (self.xlib.XCloseDisplay)(self.dpy as _); + } + } + } + + unsafe extern "C" fn x_error_callback( + display: *mut x11_dl::xlib::Display, + event: *mut x11_dl::xlib::XErrorEvent, + ) -> c_int { + let lib = LIB.lock(); + if let Some(lib) = &*lib { + if let Some(lib) = lib.upgrade() { + let error = translate_error(&lib, display, &*event); + log::error!("X11 error: {:#?}", error); + } + } + 0 + } + + unsafe fn translate_error( + lib: &x11_dl::xlib::Xlib, + display: *mut x11_dl::xlib::Display, + event: &x11_dl::xlib::XErrorEvent, + ) -> XError { + // `assume_init` is safe here because the array consists of `MaybeUninit` values, + // which do not require initialization. + let mut buf: [MaybeUninit; 1024] = MaybeUninit::uninit().assume_init(); + (lib.XGetErrorText)( + display, + event.error_code as c_int, + buf.as_mut_ptr() as *mut c_char, + buf.len() as c_int, + ); + let description = CStr::from_ptr(buf.as_ptr() as *const c_char).to_string_lossy(); + XError { + description: description.into_owned(), + error_code: event.error_code, + request_code: event.request_code, + minor_code: event.minor_code, + } + } + + pub fn use_xlib() -> bool { + std::env::var_os("WINIT_DISABLE_XLIB").is_none() + } +} diff --git a/src/platform_impl/windows/event.rs b/src/platform_impl/windows/event.rs deleted file mode 100644 index 161cf6b2de..0000000000 --- a/src/platform_impl/windows/event.rs +++ /dev/null @@ -1,417 +0,0 @@ -use std::{ - char, - os::raw::c_int, - ptr, - sync::atomic::{AtomicBool, AtomicPtr, Ordering}, -}; - -use crate::event::{ModifiersState, ScanCode, VirtualKeyCode}; - -use winapi::{ - shared::minwindef::{HKL, HKL__, LPARAM, UINT, WPARAM}, - um::winuser, -}; - -fn key_pressed(vkey: c_int) -> bool { - unsafe { (winuser::GetKeyState(vkey) & (1 << 15)) == (1 << 15) } -} - -pub fn get_key_mods() -> ModifiersState { - let filter_out_altgr = layout_uses_altgr() && key_pressed(winuser::VK_RMENU); - - let mut mods = ModifiersState::empty(); - mods.set(ModifiersState::SHIFT, key_pressed(winuser::VK_SHIFT)); - mods.set( - ModifiersState::CTRL, - key_pressed(winuser::VK_CONTROL) && !filter_out_altgr, - ); - mods.set( - ModifiersState::ALT, - key_pressed(winuser::VK_MENU) && !filter_out_altgr, - ); - mods.set( - ModifiersState::LOGO, - key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN), - ); - mods -} - -bitflags! { - #[derive(Default)] - pub struct ModifiersStateSide: u32 { - const LSHIFT = 0b010 << 0; - const RSHIFT = 0b001 << 0; - - const LCTRL = 0b010 << 3; - const RCTRL = 0b001 << 3; - - const LALT = 0b010 << 6; - const RALT = 0b001 << 6; - - const LLOGO = 0b010 << 9; - const RLOGO = 0b001 << 9; - } -} - -impl ModifiersStateSide { - pub fn filter_out_altgr(&self) -> ModifiersStateSide { - match layout_uses_altgr() && self.contains(Self::RALT) { - false => *self, - true => *self & !(Self::LCTRL | Self::RCTRL | Self::LALT | Self::RALT), - } - } -} - -impl From for ModifiersState { - fn from(side: ModifiersStateSide) -> Self { - let mut state = ModifiersState::default(); - state.set( - Self::SHIFT, - side.intersects(ModifiersStateSide::LSHIFT | ModifiersStateSide::RSHIFT), - ); - state.set( - Self::CTRL, - side.intersects(ModifiersStateSide::LCTRL | ModifiersStateSide::RCTRL), - ); - state.set( - Self::ALT, - side.intersects(ModifiersStateSide::LALT | ModifiersStateSide::RALT), - ); - state.set( - Self::LOGO, - side.intersects(ModifiersStateSide::LLOGO | ModifiersStateSide::RLOGO), - ); - state - } -} - -pub fn get_pressed_keys() -> impl Iterator { - let mut keyboard_state = vec![0u8; 256]; - unsafe { winuser::GetKeyboardState(keyboard_state.as_mut_ptr()) }; - keyboard_state - .into_iter() - .enumerate() - .filter(|(_, p)| (*p & (1 << 7)) != 0) // whether or not a key is pressed is communicated via the high-order bit - .map(|(i, _)| i as c_int) -} - -unsafe fn get_char(keyboard_state: &[u8; 256], v_key: u32, hkl: HKL) -> Option { - let mut unicode_bytes = [0u16; 5]; - let len = winuser::ToUnicodeEx( - v_key, - 0, - keyboard_state.as_ptr(), - unicode_bytes.as_mut_ptr(), - unicode_bytes.len() as _, - 0, - hkl, - ); - if len >= 1 { - char::decode_utf16(unicode_bytes.iter().cloned()) - .next() - .and_then(|c| c.ok()) - } else { - None - } -} - -/// Figures out if the keyboard layout has an AltGr key instead of an Alt key. -/// -/// Unfortunately, the Windows API doesn't give a way for us to conveniently figure that out. So, -/// we use a technique blatantly stolen from [the Firefox source code][source]: iterate over every -/// possible virtual key and compare the `char` output when AltGr is pressed vs when it isn't. If -/// pressing AltGr outputs characters that are different from the standard characters, the layout -/// uses AltGr. Otherwise, it doesn't. -/// -/// [source]: https://github.com/mozilla/gecko-dev/blob/265e6721798a455604328ed5262f430cfcc37c2f/widget/windows/KeyboardLayout.cpp#L4356-L4416 -fn layout_uses_altgr() -> bool { - unsafe { - static ACTIVE_LAYOUT: AtomicPtr = AtomicPtr::new(ptr::null_mut()); - static USES_ALTGR: AtomicBool = AtomicBool::new(false); - - let hkl = winuser::GetKeyboardLayout(0); - let old_hkl = ACTIVE_LAYOUT.swap(hkl, Ordering::SeqCst); - - if hkl == old_hkl { - return USES_ALTGR.load(Ordering::SeqCst); - } - - let mut keyboard_state_altgr = [0u8; 256]; - // AltGr is an alias for Ctrl+Alt for... some reason. Whatever it is, those are the keypresses - // we have to emulate to do an AltGr test. - keyboard_state_altgr[winuser::VK_MENU as usize] = 0x80; - keyboard_state_altgr[winuser::VK_CONTROL as usize] = 0x80; - - let keyboard_state_empty = [0u8; 256]; - - for v_key in 0..=255 { - let key_noaltgr = get_char(&keyboard_state_empty, v_key, hkl); - let key_altgr = get_char(&keyboard_state_altgr, v_key, hkl); - if let (Some(noaltgr), Some(altgr)) = (key_noaltgr, key_altgr) { - if noaltgr != altgr { - USES_ALTGR.store(true, Ordering::SeqCst); - return true; - } - } - } - - USES_ALTGR.store(false, Ordering::SeqCst); - false - } -} - -pub fn vkey_to_winit_vkey(vkey: c_int) -> Option { - // VK_* codes are documented here https://msdn.microsoft.com/en-us/library/windows/desktop/dd375731(v=vs.85).aspx - match vkey { - //winuser::VK_LBUTTON => Some(VirtualKeyCode::Lbutton), - //winuser::VK_RBUTTON => Some(VirtualKeyCode::Rbutton), - //winuser::VK_CANCEL => Some(VirtualKeyCode::Cancel), - //winuser::VK_MBUTTON => Some(VirtualKeyCode::Mbutton), - //winuser::VK_XBUTTON1 => Some(VirtualKeyCode::Xbutton1), - //winuser::VK_XBUTTON2 => Some(VirtualKeyCode::Xbutton2), - winuser::VK_BACK => Some(VirtualKeyCode::Back), - winuser::VK_TAB => Some(VirtualKeyCode::Tab), - //winuser::VK_CLEAR => Some(VirtualKeyCode::Clear), - winuser::VK_RETURN => Some(VirtualKeyCode::Return), - winuser::VK_LSHIFT => Some(VirtualKeyCode::LShift), - winuser::VK_RSHIFT => Some(VirtualKeyCode::RShift), - winuser::VK_LCONTROL => Some(VirtualKeyCode::LControl), - winuser::VK_RCONTROL => Some(VirtualKeyCode::RControl), - winuser::VK_LMENU => Some(VirtualKeyCode::LAlt), - winuser::VK_RMENU => Some(VirtualKeyCode::RAlt), - winuser::VK_PAUSE => Some(VirtualKeyCode::Pause), - winuser::VK_CAPITAL => Some(VirtualKeyCode::Capital), - winuser::VK_KANA => Some(VirtualKeyCode::Kana), - //winuser::VK_HANGUEL => Some(VirtualKeyCode::Hanguel), - //winuser::VK_HANGUL => Some(VirtualKeyCode::Hangul), - //winuser::VK_JUNJA => Some(VirtualKeyCode::Junja), - //winuser::VK_FINAL => Some(VirtualKeyCode::Final), - //winuser::VK_HANJA => Some(VirtualKeyCode::Hanja), - winuser::VK_KANJI => Some(VirtualKeyCode::Kanji), - winuser::VK_ESCAPE => Some(VirtualKeyCode::Escape), - winuser::VK_CONVERT => Some(VirtualKeyCode::Convert), - winuser::VK_NONCONVERT => Some(VirtualKeyCode::NoConvert), - //winuser::VK_ACCEPT => Some(VirtualKeyCode::Accept), - //winuser::VK_MODECHANGE => Some(VirtualKeyCode::Modechange), - winuser::VK_SPACE => Some(VirtualKeyCode::Space), - winuser::VK_PRIOR => Some(VirtualKeyCode::PageUp), - winuser::VK_NEXT => Some(VirtualKeyCode::PageDown), - winuser::VK_END => Some(VirtualKeyCode::End), - winuser::VK_HOME => Some(VirtualKeyCode::Home), - winuser::VK_LEFT => Some(VirtualKeyCode::Left), - winuser::VK_UP => Some(VirtualKeyCode::Up), - winuser::VK_RIGHT => Some(VirtualKeyCode::Right), - winuser::VK_DOWN => Some(VirtualKeyCode::Down), - //winuser::VK_SELECT => Some(VirtualKeyCode::Select), - //winuser::VK_PRINT => Some(VirtualKeyCode::Print), - //winuser::VK_EXECUTE => Some(VirtualKeyCode::Execute), - winuser::VK_SNAPSHOT => Some(VirtualKeyCode::Snapshot), - winuser::VK_INSERT => Some(VirtualKeyCode::Insert), - winuser::VK_DELETE => Some(VirtualKeyCode::Delete), - //winuser::VK_HELP => Some(VirtualKeyCode::Help), - 0x30 => Some(VirtualKeyCode::Key0), - 0x31 => Some(VirtualKeyCode::Key1), - 0x32 => Some(VirtualKeyCode::Key2), - 0x33 => Some(VirtualKeyCode::Key3), - 0x34 => Some(VirtualKeyCode::Key4), - 0x35 => Some(VirtualKeyCode::Key5), - 0x36 => Some(VirtualKeyCode::Key6), - 0x37 => Some(VirtualKeyCode::Key7), - 0x38 => Some(VirtualKeyCode::Key8), - 0x39 => Some(VirtualKeyCode::Key9), - 0x41 => Some(VirtualKeyCode::A), - 0x42 => Some(VirtualKeyCode::B), - 0x43 => Some(VirtualKeyCode::C), - 0x44 => Some(VirtualKeyCode::D), - 0x45 => Some(VirtualKeyCode::E), - 0x46 => Some(VirtualKeyCode::F), - 0x47 => Some(VirtualKeyCode::G), - 0x48 => Some(VirtualKeyCode::H), - 0x49 => Some(VirtualKeyCode::I), - 0x4A => Some(VirtualKeyCode::J), - 0x4B => Some(VirtualKeyCode::K), - 0x4C => Some(VirtualKeyCode::L), - 0x4D => Some(VirtualKeyCode::M), - 0x4E => Some(VirtualKeyCode::N), - 0x4F => Some(VirtualKeyCode::O), - 0x50 => Some(VirtualKeyCode::P), - 0x51 => Some(VirtualKeyCode::Q), - 0x52 => Some(VirtualKeyCode::R), - 0x53 => Some(VirtualKeyCode::S), - 0x54 => Some(VirtualKeyCode::T), - 0x55 => Some(VirtualKeyCode::U), - 0x56 => Some(VirtualKeyCode::V), - 0x57 => Some(VirtualKeyCode::W), - 0x58 => Some(VirtualKeyCode::X), - 0x59 => Some(VirtualKeyCode::Y), - 0x5A => Some(VirtualKeyCode::Z), - winuser::VK_LWIN => Some(VirtualKeyCode::LWin), - winuser::VK_RWIN => Some(VirtualKeyCode::RWin), - winuser::VK_APPS => Some(VirtualKeyCode::Apps), - winuser::VK_SLEEP => Some(VirtualKeyCode::Sleep), - winuser::VK_NUMPAD0 => Some(VirtualKeyCode::Numpad0), - winuser::VK_NUMPAD1 => Some(VirtualKeyCode::Numpad1), - winuser::VK_NUMPAD2 => Some(VirtualKeyCode::Numpad2), - winuser::VK_NUMPAD3 => Some(VirtualKeyCode::Numpad3), - winuser::VK_NUMPAD4 => Some(VirtualKeyCode::Numpad4), - winuser::VK_NUMPAD5 => Some(VirtualKeyCode::Numpad5), - winuser::VK_NUMPAD6 => Some(VirtualKeyCode::Numpad6), - winuser::VK_NUMPAD7 => Some(VirtualKeyCode::Numpad7), - winuser::VK_NUMPAD8 => Some(VirtualKeyCode::Numpad8), - winuser::VK_NUMPAD9 => Some(VirtualKeyCode::Numpad9), - winuser::VK_MULTIPLY => Some(VirtualKeyCode::NumpadMultiply), - winuser::VK_ADD => Some(VirtualKeyCode::NumpadAdd), - //winuser::VK_SEPARATOR => Some(VirtualKeyCode::Separator), - winuser::VK_SUBTRACT => Some(VirtualKeyCode::NumpadSubtract), - winuser::VK_DECIMAL => Some(VirtualKeyCode::NumpadDecimal), - winuser::VK_DIVIDE => Some(VirtualKeyCode::NumpadDivide), - winuser::VK_F1 => Some(VirtualKeyCode::F1), - winuser::VK_F2 => Some(VirtualKeyCode::F2), - winuser::VK_F3 => Some(VirtualKeyCode::F3), - winuser::VK_F4 => Some(VirtualKeyCode::F4), - winuser::VK_F5 => Some(VirtualKeyCode::F5), - winuser::VK_F6 => Some(VirtualKeyCode::F6), - winuser::VK_F7 => Some(VirtualKeyCode::F7), - winuser::VK_F8 => Some(VirtualKeyCode::F8), - winuser::VK_F9 => Some(VirtualKeyCode::F9), - winuser::VK_F10 => Some(VirtualKeyCode::F10), - winuser::VK_F11 => Some(VirtualKeyCode::F11), - winuser::VK_F12 => Some(VirtualKeyCode::F12), - winuser::VK_F13 => Some(VirtualKeyCode::F13), - winuser::VK_F14 => Some(VirtualKeyCode::F14), - winuser::VK_F15 => Some(VirtualKeyCode::F15), - winuser::VK_F16 => Some(VirtualKeyCode::F16), - winuser::VK_F17 => Some(VirtualKeyCode::F17), - winuser::VK_F18 => Some(VirtualKeyCode::F18), - winuser::VK_F19 => Some(VirtualKeyCode::F19), - winuser::VK_F20 => Some(VirtualKeyCode::F20), - winuser::VK_F21 => Some(VirtualKeyCode::F21), - winuser::VK_F22 => Some(VirtualKeyCode::F22), - winuser::VK_F23 => Some(VirtualKeyCode::F23), - winuser::VK_F24 => Some(VirtualKeyCode::F24), - winuser::VK_NUMLOCK => Some(VirtualKeyCode::Numlock), - winuser::VK_SCROLL => Some(VirtualKeyCode::Scroll), - winuser::VK_BROWSER_BACK => Some(VirtualKeyCode::NavigateBackward), - winuser::VK_BROWSER_FORWARD => Some(VirtualKeyCode::NavigateForward), - winuser::VK_BROWSER_REFRESH => Some(VirtualKeyCode::WebRefresh), - winuser::VK_BROWSER_STOP => Some(VirtualKeyCode::WebStop), - winuser::VK_BROWSER_SEARCH => Some(VirtualKeyCode::WebSearch), - winuser::VK_BROWSER_FAVORITES => Some(VirtualKeyCode::WebFavorites), - winuser::VK_BROWSER_HOME => Some(VirtualKeyCode::WebHome), - winuser::VK_VOLUME_MUTE => Some(VirtualKeyCode::Mute), - winuser::VK_VOLUME_DOWN => Some(VirtualKeyCode::VolumeDown), - winuser::VK_VOLUME_UP => Some(VirtualKeyCode::VolumeUp), - winuser::VK_MEDIA_NEXT_TRACK => Some(VirtualKeyCode::NextTrack), - winuser::VK_MEDIA_PREV_TRACK => Some(VirtualKeyCode::PrevTrack), - winuser::VK_MEDIA_STOP => Some(VirtualKeyCode::MediaStop), - winuser::VK_MEDIA_PLAY_PAUSE => Some(VirtualKeyCode::PlayPause), - winuser::VK_LAUNCH_MAIL => Some(VirtualKeyCode::Mail), - winuser::VK_LAUNCH_MEDIA_SELECT => Some(VirtualKeyCode::MediaSelect), - /*winuser::VK_LAUNCH_APP1 => Some(VirtualKeyCode::Launch_app1), - winuser::VK_LAUNCH_APP2 => Some(VirtualKeyCode::Launch_app2),*/ - winuser::VK_OEM_PLUS => Some(VirtualKeyCode::Equals), - winuser::VK_OEM_COMMA => Some(VirtualKeyCode::Comma), - winuser::VK_OEM_MINUS => Some(VirtualKeyCode::Minus), - winuser::VK_OEM_PERIOD => Some(VirtualKeyCode::Period), - winuser::VK_OEM_1 => map_text_keys(vkey), - winuser::VK_OEM_2 => map_text_keys(vkey), - winuser::VK_OEM_3 => map_text_keys(vkey), - winuser::VK_OEM_4 => map_text_keys(vkey), - winuser::VK_OEM_5 => map_text_keys(vkey), - winuser::VK_OEM_6 => map_text_keys(vkey), - winuser::VK_OEM_7 => map_text_keys(vkey), - /* winuser::VK_OEM_8 => Some(VirtualKeyCode::Oem_8), */ - winuser::VK_OEM_102 => Some(VirtualKeyCode::OEM102), - /*winuser::VK_PROCESSKEY => Some(VirtualKeyCode::Processkey), - winuser::VK_PACKET => Some(VirtualKeyCode::Packet), - winuser::VK_ATTN => Some(VirtualKeyCode::Attn), - winuser::VK_CRSEL => Some(VirtualKeyCode::Crsel), - winuser::VK_EXSEL => Some(VirtualKeyCode::Exsel), - winuser::VK_EREOF => Some(VirtualKeyCode::Ereof), - winuser::VK_PLAY => Some(VirtualKeyCode::Play), - winuser::VK_ZOOM => Some(VirtualKeyCode::Zoom), - winuser::VK_NONAME => Some(VirtualKeyCode::Noname), - winuser::VK_PA1 => Some(VirtualKeyCode::Pa1), - winuser::VK_OEM_CLEAR => Some(VirtualKeyCode::Oem_clear),*/ - _ => None, - } -} - -pub fn handle_extended_keys( - vkey: c_int, - mut scancode: UINT, - extended: bool, -) -> Option<(c_int, UINT)> { - // Welcome to hell https://blog.molecular-matters.com/2011/09/05/properly-handling-keyboard-input/ - scancode = if extended { 0xE000 } else { 0x0000 } | scancode; - let vkey = match vkey { - winuser::VK_SHIFT => unsafe { - winuser::MapVirtualKeyA(scancode, winuser::MAPVK_VSC_TO_VK_EX) as _ - }, - winuser::VK_CONTROL => { - if extended { - winuser::VK_RCONTROL - } else { - winuser::VK_LCONTROL - } - } - winuser::VK_MENU => { - if extended { - winuser::VK_RMENU - } else { - winuser::VK_LMENU - } - } - _ => { - match scancode { - // When VK_PAUSE is pressed it emits a LeftControl + NumLock scancode event sequence, but reports VK_PAUSE - // as the virtual key on both events, or VK_PAUSE on the first event or 0xFF when using raw input. - // Don't emit anything for the LeftControl event in the pair... - 0xE01D if vkey == winuser::VK_PAUSE => return None, - // ...and emit the Pause event for the second event in the pair. - 0x45 if vkey == winuser::VK_PAUSE || vkey == 0xFF as _ => { - scancode = 0xE059; - winuser::VK_PAUSE - } - // VK_PAUSE has an incorrect vkey value when used with modifiers. VK_PAUSE also reports a different - // scancode when used with modifiers than when used without - 0xE046 => { - scancode = 0xE059; - winuser::VK_PAUSE - } - // VK_SCROLL has an incorrect vkey value when used with modifiers. - 0x46 => winuser::VK_SCROLL, - _ => vkey, - } - } - }; - Some((vkey, scancode)) -} - -pub fn process_key_params( - wparam: WPARAM, - lparam: LPARAM, -) -> Option<(ScanCode, Option)> { - let scancode = ((lparam >> 16) & 0xff) as UINT; - let extended = (lparam & 0x01000000) != 0; - handle_extended_keys(wparam as _, scancode, extended) - .map(|(vkey, scancode)| (scancode, vkey_to_winit_vkey(vkey))) -} - -// This is needed as windows doesn't properly distinguish -// some virtual key codes for different keyboard layouts -fn map_text_keys(win_virtual_key: i32) -> Option { - let char_key = - unsafe { winuser::MapVirtualKeyA(win_virtual_key as u32, winuser::MAPVK_VK_TO_CHAR) } - & 0x7FFF; - match char::from_u32(char_key) { - Some(';') => Some(VirtualKeyCode::Semicolon), - Some('/') => Some(VirtualKeyCode::Slash), - Some('`') => Some(VirtualKeyCode::Grave), - Some('[') => Some(VirtualKeyCode::LBracket), - Some(']') => Some(VirtualKeyCode::RBracket), - Some('\'') => Some(VirtualKeyCode::Apostrophe), - Some('\\') => Some(VirtualKeyCode::Backslash), - _ => None, - } -} diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 795d840dd9..b9726a4e2c 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -19,6 +19,7 @@ use std::{ use winapi::shared::basetsd::{DWORD_PTR, UINT_PTR}; use winapi::{ + ctypes::c_int, shared::{ minwindef::{BOOL, DWORD, HIWORD, INT, LOWORD, LPARAM, LRESULT, UINT, WORD, WPARAM}, windef::{HWND, POINT, RECT}, @@ -27,20 +28,24 @@ use winapi::{ um::{ commctrl, libloaderapi, ole2, processthreadsapi, winbase, winnt::{HANDLE, LONG, LPCSTR, SHORT}, - winuser, + winuser::{self, RAWINPUT}, }, }; use crate::{ dpi::{PhysicalPosition, PhysicalSize}, - event::{DeviceEvent, Event, Force, KeyboardInput, Touch, TouchPhase, WindowEvent}, + event::{DeviceEvent, Event, Force, RawKeyEvent, Touch, TouchPhase, WindowEvent}, event_loop::{ControlFlow, EventLoopClosed, EventLoopWindowTarget as RootELW}, + keyboard::{KeyCode, ModifiersState}, monitor::MonitorHandle as RootMonitorHandle, + platform::scancode::KeyCodeExtScancode, platform_impl::platform::{ dark_mode::try_theme, dpi::{become_dpi_aware, dpi_to_scale_factor, enable_non_client_dpi_scaling}, drop_handler::FileDropHandler, - event::{self, handle_extended_keys, process_key_params, vkey_to_winit_vkey}, + keyboard::is_msg_keyboard_related, + keyboard_layout::LAYOUT_CACHE, + minimal_ime::is_msg_ime_related, monitor::{self, MonitorHandle}, raw_input, util, window_state::{CursorFlags, WindowFlags, WindowState}, @@ -108,6 +113,13 @@ impl ThreadMsgTargetSubclassInput { } } +/// The result of a subclass procedure (the message handling callback) +pub(crate) enum ProcResult { + DefSubclassProc, // <- this should be the default value + DefWindowProc, + Value(isize), +} + pub struct EventLoop { thread_msg_sender: Sender, window_target: RootELW, @@ -745,10 +757,15 @@ unsafe fn process_control_flow(runner: &EventLoopRunner) { } /// Emit a `ModifiersChanged` event whenever modifiers have changed. -fn update_modifiers(window: HWND, subclass_input: &SubclassInput) { +/// Returns the current modifier state +fn update_modifiers(window: HWND, subclass_input: &SubclassInput) -> ModifiersState { use crate::event::WindowEvent::ModifiersChanged; - let modifiers = event::get_key_mods(); + let modifiers = { + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + layouts.get_agnostic_mods() + }; + let mut window_state = subclass_input.window_state.lock(); if window_state.modifiers_state != modifiers { window_state.modifiers_state = modifiers; @@ -763,6 +780,7 @@ fn update_modifiers(window: HWND, subclass_input: &SubclassInput) { }); } } + modifiers } /// Any window whose callback is configured to this function will have its events propagated @@ -819,6 +837,75 @@ unsafe fn public_window_callback_inner( winuser::RDW_INTERNALPAINT, ); + let mut result = ProcResult::DefSubclassProc; + + // Send new modifiers before sending key events. + let mods_changed_callback = || match msg { + winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN | winuser::WM_KEYUP | winuser::WM_SYSKEYUP => { + update_modifiers(window, subclass_input); + result = ProcResult::Value(0); + } + _ => (), + }; + subclass_input + .event_loop_runner + .catch_unwind(mods_changed_callback) + .unwrap_or_else(|| result = ProcResult::Value(-1)); + + let keyboard_callback = || { + use crate::event::WindowEvent::KeyboardInput; + let is_keyboard_related = is_msg_keyboard_related(msg); + if !is_keyboard_related { + // We return early to avoid a deadlock from locking the window state + // when not appropriate. + return; + } + let events = { + let mut window_state = subclass_input.window_state.lock(); + window_state + .key_event_builder + .process_message(window, msg, wparam, lparam, &mut result) + }; + for event in events { + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: KeyboardInput { + device_id: DEVICE_ID, + event: event.event, + is_synthetic: event.is_synthetic, + }, + }); + } + }; + subclass_input + .event_loop_runner + .catch_unwind(keyboard_callback) + .unwrap_or_else(|| result = ProcResult::Value(-1)); + + let ime_callback = || { + use crate::event::WindowEvent::ReceivedImeText; + let is_ime_related = is_msg_ime_related(msg); + if !is_ime_related { + return; + } + let text = { + let mut window_state = subclass_input.window_state.lock(); + window_state + .ime_handler + .process_message(window, msg, wparam, lparam, &mut result) + }; + if let Some(str) = text { + subclass_input.send_event(Event::WindowEvent { + window_id: RootWindowId(WindowId(window)), + event: ReceivedImeText(str), + }); + } + }; + subclass_input + .event_loop_runner + .catch_unwind(ime_callback) + .unwrap_or_else(|| result = ProcResult::Value(-1)); + // I decided to bind the closure to `callback` and pass it to catch_unwind rather than passing // the closure to catch_unwind directly so that the match body indendation wouldn't change and // the git blame and history would be preserved. @@ -828,7 +915,7 @@ unsafe fn public_window_callback_inner( .window_state .lock() .set_window_flags_in_place(|f| f.insert(WindowFlags::MARKER_IN_SIZE_MOVE)); - 0 + result = ProcResult::Value(0); } winuser::WM_EXITSIZEMOVE => { @@ -836,18 +923,16 @@ unsafe fn public_window_callback_inner( .window_state .lock() .set_window_flags_in_place(|f| f.remove(WindowFlags::MARKER_IN_SIZE_MOVE)); - 0 + result = ProcResult::Value(0); } winuser::WM_NCCREATE => { enable_non_client_dpi_scaling(window); - commctrl::DefSubclassProc(window, msg, wparam, lparam) } winuser::WM_NCLBUTTONDOWN => { if wparam == winuser::HTCAPTION as _ { winuser::PostMessageW(window, winuser::WM_MOUSEMOVE, 0, lparam); } - commctrl::DefSubclassProc(window, msg, wparam, lparam) } winuser::WM_CLOSE => { @@ -856,7 +941,7 @@ unsafe fn public_window_callback_inner( window_id: RootWindowId(WindowId(window)), event: CloseRequested, }); - 0 + result = ProcResult::Value(0); } winuser::WM_DESTROY => { @@ -867,13 +952,13 @@ unsafe fn public_window_callback_inner( event: Destroyed, }); subclass_input.event_loop_runner.remove_window(window); - 0 + result = ProcResult::Value(0); } winuser::WM_NCDESTROY => { remove_window_subclass::(window); subclass_input.subclass_removed.set(true); - 0 + result = ProcResult::Value(0); } winuser::WM_PAINT => { @@ -895,8 +980,6 @@ unsafe fn public_window_callback_inner( process_control_flow(&subclass_input.event_loop_runner); } } - - commctrl::DefSubclassProc(window, msg, wparam, lparam) } winuser::WM_WINDOWPOSCHANGING => { @@ -944,7 +1027,7 @@ unsafe fn public_window_callback_inner( } } - 0 + result = ProcResult::Value(0); } // WM_MOVE supplies client area positions, so we send Moved here instead. @@ -962,7 +1045,7 @@ unsafe fn public_window_callback_inner( } // This is necessary for us to still get sent WM_SIZE. - commctrl::DefSubclassProc(window, msg, wparam, lparam) + result = ProcResult::DefSubclassProc; } winuser::WM_SIZE => { @@ -989,40 +1072,7 @@ unsafe fn public_window_callback_inner( } subclass_input.send_event(event); - 0 - } - - winuser::WM_CHAR | winuser::WM_SYSCHAR => { - use crate::event::WindowEvent::ReceivedCharacter; - use std::char; - let is_high_surrogate = 0xD800 <= wparam && wparam <= 0xDBFF; - let is_low_surrogate = 0xDC00 <= wparam && wparam <= 0xDFFF; - - if is_high_surrogate { - subclass_input.window_state.lock().high_surrogate = Some(wparam as u16); - } else if is_low_surrogate { - let high_surrogate = subclass_input.window_state.lock().high_surrogate.take(); - - if let Some(high_surrogate) = high_surrogate { - let pair = [high_surrogate, wparam as u16]; - if let Some(Ok(chr)) = char::decode_utf16(pair.iter().copied()).next() { - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: ReceivedCharacter(chr), - }); - } - } - } else { - subclass_input.window_state.lock().high_surrogate = None; - - if let Some(chr) = char::from_u32(wparam as u32) { - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: ReceivedCharacter(chr), - }); - } - } - 0 + result = ProcResult::Value(0); } // this is necessary for us to maintain minimize/restore state @@ -1040,11 +1090,12 @@ unsafe fn public_window_callback_inner( if wparam == winuser::SC_SCREENSAVE { let window_state = subclass_input.window_state.lock(); if window_state.fullscreen.is_some() { - return 0; + result = ProcResult::Value(0); + return; } } - winuser::DefWindowProcW(window, msg, wparam, lparam) + result = ProcResult::DefWindowProc; } winuser::WM_MOUSEMOVE => { @@ -1089,19 +1140,18 @@ unsafe fn public_window_callback_inner( w.mouse.last_position = Some(position); } if cursor_moved { - update_modifiers(window, subclass_input); - + let modifiers = update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: CursorMoved { device_id: DEVICE_ID, position, - modifiers: event::get_key_mods(), + modifiers, }, }); } - 0 + result = ProcResult::Value(0); } winuser::WM_MOUSELEAVE => { @@ -1120,7 +1170,7 @@ unsafe fn public_window_callback_inner( }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_MOUSEWHEEL => { @@ -1130,7 +1180,7 @@ unsafe fn public_window_callback_inner( let value = value as i32; let value = value as f32 / winuser::WHEEL_DELTA as f32; - update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1138,11 +1188,11 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, delta: LineDelta(0.0, value), phase: TouchPhase::Moved, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_MOUSEHWHEEL => { @@ -1152,7 +1202,7 @@ unsafe fn public_window_callback_inner( let value = value as i32; let value = value as f32 / winuser::WHEEL_DELTA as f32; - update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1160,77 +1210,25 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, delta: LineDelta(value, 0.0), phase: TouchPhase::Moved, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => { - use crate::event::{ElementState::Pressed, VirtualKeyCode}; if msg == winuser::WM_SYSKEYDOWN && wparam as i32 == winuser::VK_F4 { - commctrl::DefSubclassProc(window, msg, wparam, lparam) - } else { - if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { - update_modifiers(window, subclass_input); - - #[allow(deprecated)] - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Pressed, - scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - is_synthetic: false, - }, - }); - // Windows doesn't emit a delete character by default, but in order to make it - // consistent with the other platforms we'll emit a delete character here. - if vkey == Some(VirtualKeyCode::Delete) { - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::ReceivedCharacter('\u{7F}'), - }); - } - } - 0 + result = ProcResult::DefSubclassProc; } } - winuser::WM_KEYUP | winuser::WM_SYSKEYUP => { - use crate::event::ElementState::Released; - if let Some((scancode, vkey)) = process_key_params(wparam, lparam) { - update_modifiers(window, subclass_input); - - #[allow(deprecated)] - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - state: Released, - scancode, - virtual_keycode: vkey, - modifiers: event::get_key_mods(), - }, - is_synthetic: false, - }, - }); - } - 0 - } - winuser::WM_LBUTTONDOWN => { use crate::event::{ElementState::Pressed, MouseButton::Left, WindowEvent::MouseInput}; capture_mouse(window, &mut *subclass_input.window_state.lock()); - update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1238,10 +1236,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Left, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_LBUTTONUP => { @@ -1251,7 +1249,7 @@ unsafe fn public_window_callback_inner( release_mouse(subclass_input.window_state.lock()); - update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1259,10 +1257,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Left, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_RBUTTONDOWN => { @@ -1272,7 +1270,7 @@ unsafe fn public_window_callback_inner( capture_mouse(window, &mut *subclass_input.window_state.lock()); - update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1280,10 +1278,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Right, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_RBUTTONUP => { @@ -1293,7 +1291,7 @@ unsafe fn public_window_callback_inner( release_mouse(subclass_input.window_state.lock()); - update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1301,10 +1299,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Right, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_MBUTTONDOWN => { @@ -1314,7 +1312,7 @@ unsafe fn public_window_callback_inner( capture_mouse(window, &mut *subclass_input.window_state.lock()); - update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1322,10 +1320,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Middle, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_MBUTTONUP => { @@ -1335,7 +1333,7 @@ unsafe fn public_window_callback_inner( release_mouse(subclass_input.window_state.lock()); - update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1343,10 +1341,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Middle, - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_XBUTTONDOWN => { @@ -1357,7 +1355,7 @@ unsafe fn public_window_callback_inner( capture_mouse(window, &mut *subclass_input.window_state.lock()); - update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1365,10 +1363,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Pressed, button: Other(xbutton), - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_XBUTTONUP => { @@ -1379,7 +1377,7 @@ unsafe fn public_window_callback_inner( release_mouse(subclass_input.window_state.lock()); - update_modifiers(window, subclass_input); + let modifiers = update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), @@ -1387,10 +1385,10 @@ unsafe fn public_window_callback_inner( device_id: DEVICE_ID, state: Released, button: Other(xbutton), - modifiers: event::get_key_mods(), + modifiers, }, }); - 0 + result = ProcResult::Value(0); } winuser::WM_CAPTURECHANGED => { @@ -1401,7 +1399,7 @@ unsafe fn public_window_callback_inner( if lparam != window as isize { subclass_input.window_state.lock().mouse.capture_count = 0; } - 0 + result = ProcResult::Value(0); } winuser::WM_TOUCH => { @@ -1450,7 +1448,7 @@ unsafe fn public_window_callback_inner( } } winuser::CloseTouchInputHandle(htouch); - 0 + result = ProcResult::Value(0); } winuser::WM_POINTERDOWN | winuser::WM_POINTERUPDATE | winuser::WM_POINTERUP => { @@ -1473,7 +1471,8 @@ unsafe fn public_window_callback_inner( std::ptr::null_mut(), ) == 0 { - return 0; + result = ProcResult::Value(0); + return; } let pointer_info_count = (entries_count * pointers_count) as usize; @@ -1486,7 +1485,8 @@ unsafe fn public_window_callback_inner( pointer_infos.as_mut_ptr(), ) == 0 { - return 0; + result = ProcResult::Value(0); + return; } // https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-getpointerframeinfohistory @@ -1590,68 +1590,23 @@ unsafe fn public_window_callback_inner( SkipPointerFrameMessages(pointer_id); } - 0 + result = ProcResult::Value(0); } winuser::WM_SETFOCUS => { - use crate::event::{ElementState::Released, WindowEvent::Focused}; - for windows_keycode in event::get_pressed_keys() { - let scancode = - winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC); - let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); - - update_modifiers(window, subclass_input); - - #[allow(deprecated)] - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - scancode, - virtual_keycode, - state: Released, - modifiers: event::get_key_mods(), - }, - is_synthetic: true, - }, - }) - } + use crate::event::WindowEvent::Focused; + update_modifiers(window, subclass_input); subclass_input.send_event(Event::WindowEvent { window_id: RootWindowId(WindowId(window)), event: Focused(true), }); - 0 + result = ProcResult::Value(0); } winuser::WM_KILLFOCUS => { - use crate::event::{ - ElementState::Released, - ModifiersState, - WindowEvent::{Focused, ModifiersChanged}, - }; - for windows_keycode in event::get_pressed_keys() { - let scancode = - winuser::MapVirtualKeyA(windows_keycode as _, winuser::MAPVK_VK_TO_VSC); - let virtual_keycode = event::vkey_to_winit_vkey(windows_keycode); - - #[allow(deprecated)] - subclass_input.send_event(Event::WindowEvent { - window_id: RootWindowId(WindowId(window)), - event: WindowEvent::KeyboardInput { - device_id: DEVICE_ID, - input: KeyboardInput { - scancode, - virtual_keycode, - state: Released, - modifiers: event::get_key_mods(), - }, - is_synthetic: true, - }, - }) - } + use crate::event::WindowEvent::{Focused, ModifiersChanged}; subclass_input.window_state.lock().modifiers_state = ModifiersState::empty(); subclass_input.send_event(Event::WindowEvent { @@ -1663,7 +1618,7 @@ unsafe fn public_window_callback_inner( window_id: RootWindowId(WindowId(window)), event: Focused(false), }); - 0 + result = ProcResult::Value(0); } winuser::WM_SETCURSOR => { @@ -1684,17 +1639,12 @@ unsafe fn public_window_callback_inner( Some(cursor) => { let cursor = winuser::LoadCursorW(ptr::null_mut(), cursor.to_windows_cursor()); winuser::SetCursor(cursor); - 0 + result = ProcResult::Value(0); } - None => winuser::DefWindowProcW(window, msg, wparam, lparam), + None => result = ProcResult::DefWindowProc, } } - winuser::WM_DROPFILES => { - // See `FileDropHandler` for implementation. - 0 - } - winuser::WM_GETMINMAXINFO => { let mmi = lparam as *mut winuser::MINMAXINFO; @@ -1719,7 +1669,7 @@ unsafe fn public_window_callback_inner( } } - 0 + result = ProcResult::Value(0); } // Only sent on Windows 8.1 or newer. On Windows 7 and older user has to log out to change @@ -1741,7 +1691,8 @@ unsafe fn public_window_callback_inner( window_state.scale_factor = new_scale_factor; if new_scale_factor == old_scale_factor { - return 0; + result = ProcResult::Value(0); + return; } window_state.fullscreen.is_none() @@ -1936,7 +1887,7 @@ unsafe fn public_window_callback_inner( winuser::SWP_NOZORDER | winuser::SWP_NOACTIVATE, ); - 0 + result = ProcResult::Value(0); } winuser::WM_SETTINGCHANGE => { @@ -1957,22 +1908,18 @@ unsafe fn public_window_callback_inner( }); } } - - commctrl::DefSubclassProc(window, msg, wparam, lparam) } _ => { if msg == *DESTROY_MSG_ID { winuser::DestroyWindow(window); - 0 + result = ProcResult::Value(0); } else if msg == *SET_RETAIN_STATE_ON_SIZE_MSG_ID { let mut window_state = subclass_input.window_state.lock(); window_state.set_window_flags_in_place(|f| { f.set(WindowFlags::MARKER_RETAIN_STATE_ON_SIZE, wparam != 0) }); - 0 - } else { - commctrl::DefSubclassProc(window, msg, wparam, lparam) + result = ProcResult::Value(0); } } }; @@ -1980,7 +1927,13 @@ unsafe fn public_window_callback_inner( subclass_input .event_loop_runner .catch_unwind(callback) - .unwrap_or(-1) + .unwrap_or_else(|| result = ProcResult::Value(-1)); + + match result { + ProcResult::DefSubclassProc => commctrl::DefSubclassProc(window, msg, wparam, lparam), + ProcResult::DefWindowProc => winuser::DefWindowProcW(window, msg, wparam, lparam), + ProcResult::Value(val) => val, + } } unsafe extern "system" fn thread_event_target_callback( @@ -2062,102 +2015,8 @@ unsafe extern "system" fn thread_event_target_callback( } winuser::WM_INPUT => { - use crate::event::{ - DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, - ElementState::{Pressed, Released}, - MouseScrollDelta::LineDelta, - }; - if let Some(data) = raw_input::get_raw_input_data(lparam as _) { - let device_id = wrap_device_id(data.header.hDevice as _); - - if data.header.dwType == winuser::RIM_TYPEMOUSE { - let mouse = data.data.mouse(); - - if util::has_flag(mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) { - let x = mouse.lLastX as f64; - let y = mouse.lLastY as f64; - - if x != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 0, value: x }, - }); - } - - if y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Motion { axis: 1, value: y }, - }); - } - - if x != 0.0 || y != 0.0 { - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseMotion { delta: (x, y) }, - }); - } - } - - if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { - let delta = - mouse.usButtonData as SHORT as f32 / winuser::WHEEL_DELTA as f32; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: MouseWheel { - delta: LineDelta(0.0, delta), - }, - }); - } - - let button_state = raw_input::get_raw_mouse_button_state(mouse.usButtonFlags); - // Left, middle, and right, respectively. - for (index, state) in button_state.iter().enumerate() { - if let Some(state) = *state { - // This gives us consistency with X11, since there doesn't - // seem to be anything else reasonable to do for a mouse - // button ID. - let button = (index + 1) as _; - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Button { button, state }, - }); - } - } - } else if data.header.dwType == winuser::RIM_TYPEKEYBOARD { - let keyboard = data.data.keyboard(); - - let pressed = keyboard.Message == winuser::WM_KEYDOWN - || keyboard.Message == winuser::WM_SYSKEYDOWN; - let released = keyboard.Message == winuser::WM_KEYUP - || keyboard.Message == winuser::WM_SYSKEYUP; - - if pressed || released { - let state = if pressed { Pressed } else { Released }; - - let scancode = keyboard.MakeCode as _; - let extended = util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _) - | util::has_flag(keyboard.Flags, winuser::RI_KEY_E1 as _); - - if let Some((vkey, scancode)) = - handle_extended_keys(keyboard.VKey as _, scancode, extended) - { - let virtual_keycode = vkey_to_winit_vkey(vkey); - - #[allow(deprecated)] - subclass_input.send_event(Event::DeviceEvent { - device_id, - event: Key(KeyboardInput { - scancode, - state, - virtual_keycode, - modifiers: event::get_key_mods(), - }), - }); - } - } - } + handle_raw_input(&subclass_input, data); } commctrl::DefSubclassProc(window, msg, wparam, lparam) @@ -2229,3 +2088,184 @@ unsafe extern "system" fn thread_event_target_callback( } result } + +unsafe fn handle_raw_input( + subclass_input: &Box>, + data: RAWINPUT, +) { + use crate::event::{ + DeviceEvent::{Button, Key, Motion, MouseMotion, MouseWheel}, + ElementState::{Pressed, Released}, + MouseScrollDelta::LineDelta, + }; + + let device_id = wrap_device_id(data.header.hDevice as _); + + if data.header.dwType == winuser::RIM_TYPEMOUSE { + let mouse = data.data.mouse(); + + if util::has_flag(mouse.usFlags, winuser::MOUSE_MOVE_RELATIVE) { + let x = mouse.lLastX as f64; + let y = mouse.lLastY as f64; + + if x != 0.0 { + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: Motion { axis: 0, value: x }, + }); + } + + if y != 0.0 { + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: Motion { axis: 1, value: y }, + }); + } + + if x != 0.0 || y != 0.0 { + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: MouseMotion { delta: (x, y) }, + }); + } + } + + if util::has_flag(mouse.usButtonFlags, winuser::RI_MOUSE_WHEEL) { + // We must cast to SHORT first, becaues `usButtonData` must be interpreted as signed. + let delta = mouse.usButtonData as SHORT as f32 / winuser::WHEEL_DELTA as f32; + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: MouseWheel { + delta: LineDelta(0.0, delta), + }, + }); + } + + let button_state = raw_input::get_raw_mouse_button_state(mouse.usButtonFlags); + // Left, middle, and right, respectively. + for (index, state) in button_state.iter().enumerate() { + if let Some(state) = *state { + // This gives us consistency with X11, since there doesn't + // seem to be anything else reasonable to do for a mouse + // button ID. + let button = (index + 1) as _; + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: Button { button, state }, + }); + } + } + } else if data.header.dwType == winuser::RIM_TYPEKEYBOARD { + let keyboard = data.data.keyboard(); + + let pressed = + keyboard.Message == winuser::WM_KEYDOWN || keyboard.Message == winuser::WM_SYSKEYDOWN; + let released = + keyboard.Message == winuser::WM_KEYUP || keyboard.Message == winuser::WM_SYSKEYUP; + + if !pressed && !released { + return; + } + + let state = if pressed { Pressed } else { Released }; + let extension = { + if util::has_flag(keyboard.Flags, winuser::RI_KEY_E0 as _) { + 0xE000 + } else if util::has_flag(keyboard.Flags, winuser::RI_KEY_E1 as _) { + 0xE100 + } else { + 0x0000 + } + }; + let scancode; + if keyboard.MakeCode == 0 { + // In some cases (often with media keys) the device reports a scancode of 0 but a + // valid virtual key. In these cases we obtain the scancode from the virtual key. + scancode = + winuser::MapVirtualKeyW(keyboard.VKey as u32, winuser::MAPVK_VK_TO_VSC_EX) as u16; + } else { + scancode = keyboard.MakeCode | extension; + } + if scancode == 0xE11D || scancode == 0xE02A { + // At the hardware (or driver?) level, pressing the Pause key is equivalent to pressing + // Ctrl+NumLock. + // This equvalence means that if the user presses Pause, the keyboard will emit two + // subsequent keypresses: + // 1, 0xE11D - Which is a left Ctrl (0x1D) with an extension flag (0xE100) + // 2, 0x0045 - Which on its own can be interpreted as Pause + // + // There's another combination which isn't quite an equivalence: + // PrtSc used to be Shift+Asterisk. This means that on some keyboards, presssing + // PrtSc (print screen) produces the following sequence: + // 1, 0xE02A - Which is a left shift (0x2A) with an extension flag (0xE000) + // 2, 0xE037 - Which is a numpad multiply (0x37) with an exteion flag (0xE000). This on + // its own it can be interpreted as PrtSc + // + // For this reason, if we encounter the first keypress, we simply ignore it, trusting + // that there's going to be another event coming, from which we can extract the + // appropriate key. + // For more on this, read the article by Raymond Chen, titled: + // "Why does Ctrl+ScrollLock cancel dialogs?" + // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 + return; + } + let code; + if keyboard.VKey as c_int == winuser::VK_NUMLOCK { + // Historically, the NumLock and the Pause key were one and the same physical key. + // The user could trigger Pause by pressing Ctrl+NumLock. + // Now these are often physically separate and the two keys can be differentiated by + // checking the extension flag of the scancode. NumLock is 0xE045, Pause is 0x0045. + // + // However in this event, both keys are reported as 0x0045 even on modern hardware. + // Therefore we use the virtual key instead to determine whether it's a NumLock and + // set the KeyCode accordingly. + // + // For more on this, read the article by Raymond Chen, titled: + // "Why does Ctrl+ScrollLock cancel dialogs?" + // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 + code = KeyCode::NumLock; + } else { + code = KeyCode::from_scancode(scancode as u32); + } + if keyboard.VKey as c_int == winuser::VK_SHIFT { + match code { + KeyCode::NumpadDecimal + | KeyCode::Numpad0 + | KeyCode::Numpad1 + | KeyCode::Numpad2 + | KeyCode::Numpad3 + | KeyCode::Numpad4 + | KeyCode::Numpad5 + | KeyCode::Numpad6 + | KeyCode::Numpad7 + | KeyCode::Numpad8 + | KeyCode::Numpad9 => { + // On Windows, holding the Shift key makes numpad keys behave as if NumLock + // wasn't active. The way this is exposed to applications by the system is that + // the application receives a fake key release event for the shift key at the + // moment when the numpad key is pressed, just before receiving the numpad key + // as well. + // + // The issue is that in the raw device event (here), the fake shift release + // event reports the numpad key as the scancode. Unfortunately, the event doesn't + // have any information to tell whether it's the left shift or the right shift + // that needs to get the fake release (or press) event so we don't forward this + // event to the application at all. + // + // For more on this, read the article by Raymond Chen, titled: + // "The shift key overrides NumLock" + // https://devblogs.microsoft.com/oldnewthing/20040906-00/?p=37953 + return; + } + _ => (), + } + } + subclass_input.send_event(Event::DeviceEvent { + device_id, + event: Key(RawKeyEvent { + physical_key: code, + state, + }), + }); + } +} diff --git a/src/platform_impl/windows/keyboard.rs b/src/platform_impl/windows/keyboard.rs new file mode 100644 index 0000000000..00235ed018 --- /dev/null +++ b/src/platform_impl/windows/keyboard.rs @@ -0,0 +1,802 @@ +use std::{ + char, collections::HashSet, ffi::OsString, mem::MaybeUninit, os::raw::c_int, + os::windows::ffi::OsStringExt, sync::MutexGuard, +}; + +use winapi::{ + shared::{ + minwindef::{HKL, LPARAM, UINT, WPARAM}, + windef::HWND, + }, + um::winuser, +}; + +use unicode_segmentation::UnicodeSegmentation; + +use crate::{ + event::{ElementState, KeyEvent}, + keyboard::{Key, KeyCode, KeyLocation, NativeKeyCode}, + platform::scancode::KeyCodeExtScancode, + platform_impl::platform::{ + event_loop::ProcResult, + keyboard_layout::{get_or_insert_str, Layout, LayoutCache, WindowsModifiers, LAYOUT_CACHE}, + KeyEventExtra, + }, +}; + +pub fn is_msg_keyboard_related(msg: u32) -> bool { + use winuser::{WM_KEYFIRST, WM_KEYLAST, WM_KILLFOCUS, WM_SETFOCUS}; + let is_keyboard_msg = WM_KEYFIRST <= msg && msg <= WM_KEYLAST; + + is_keyboard_msg || msg == WM_SETFOCUS || msg == WM_KILLFOCUS +} + +pub type ExScancode = u16; + +pub struct MessageAsKeyEvent { + pub event: KeyEvent, + pub is_synthetic: bool, +} + +/// Stores information required to make `KeyEvent`s. +/// +/// A single Winit `KeyEvent` contains information which the Windows API passes to the application +/// in multiple window messages. In other words: a Winit `KeyEvent` cannot be built from a single +/// window message. Therefore, this type keeps track of certain information from previous events so +/// that a `KeyEvent` can be constructed when the last event related to a keypress is received. +/// +/// `PeekMessage` is sometimes used to determine whether the next window message still belongs to the +/// current keypress. If it doesn't and the current state represents a key event waiting to be +/// dispatched, then said event is considered complete and is dispatched. +/// +/// The sequence of window messages for a key press event is the following: +/// - Exactly one WM_KEYDOWN / WM_SYSKEYDOWN +/// - Zero or one WM_DEADCHAR / WM_SYSDEADCHAR +/// - Zero or more WM_CHAR / WM_SYSCHAR. These messages each come with a UTF-16 code unit which when +/// put together in the sequence they arrived in, forms the text which is the result of pressing the +/// key. +/// +/// Key release messages are a bit different due to the fact that they don't contribute to +/// text input. The "sequence" only consists of one WM_KEYUP / WM_SYSKEYUP event. +pub struct KeyEventBuilder { + event_info: Option, +} +impl Default for KeyEventBuilder { + fn default() -> Self { + KeyEventBuilder { event_info: None } + } +} +impl KeyEventBuilder { + /// Call this function for every window message. + /// Returns Some() if this window message completes a KeyEvent. + /// Returns None otherwise. + pub(crate) fn process_message( + &mut self, + hwnd: HWND, + msg_kind: u32, + wparam: WPARAM, + lparam: LPARAM, + result: &mut ProcResult, + ) -> Vec { + match msg_kind { + winuser::WM_SETFOCUS => { + // synthesize keydown events + let kbd_state = get_async_kbd_state(); + let key_events = self.synthesize_kbd_state(ElementState::Pressed, &kbd_state); + if !key_events.is_empty() { + return key_events; + } + } + winuser::WM_KILLFOCUS => { + // sythesize keyup events + let kbd_state = get_kbd_state(); + let key_events = self.synthesize_kbd_state(ElementState::Released, &kbd_state); + if !key_events.is_empty() { + return key_events; + } + } + winuser::WM_KEYDOWN | winuser::WM_SYSKEYDOWN => { + if msg_kind == winuser::WM_SYSKEYDOWN && wparam as i32 == winuser::VK_F4 { + // Don't dispatch Alt+F4 to the application. + // This is handled in `event_loop.rs` + return vec![]; + } + *result = ProcResult::Value(0); + + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + let event_info = PartialKeyEventInfo::from_message( + wparam, + lparam, + ElementState::Pressed, + &mut layouts, + ); + + let mut next_msg = MaybeUninit::uninit(); + let peek_retval = unsafe { + winuser::PeekMessageW( + next_msg.as_mut_ptr(), + hwnd, + winuser::WM_KEYFIRST, + winuser::WM_KEYLAST, + winuser::PM_NOREMOVE, + ) + }; + let has_next_key_message = peek_retval != 0; + self.event_info = None; + let mut finished_event_info = Some(event_info); + if has_next_key_message { + let next_msg = unsafe { next_msg.assume_init() }; + let next_msg_kind = next_msg.message; + let next_belongs_to_this = !matches!( + next_msg_kind, + winuser::WM_KEYDOWN + | winuser::WM_SYSKEYDOWN + | winuser::WM_KEYUP + | winuser::WM_SYSKEYUP + ); + if next_belongs_to_this { + self.event_info = finished_event_info.take(); + } else { + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let curr_event = finished_event_info.as_ref().unwrap(); + is_current_fake(curr_event, next_msg, layout) + }; + if is_fake { + finished_event_info = None; + } + } + } + if let Some(event_info) = finished_event_info { + let ev = event_info.finalize(&mut layouts.strings); + return vec![MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }]; + } + } + winuser::WM_DEADCHAR | winuser::WM_SYSDEADCHAR => { + *result = ProcResult::Value(0); + // At this point, we know that there isn't going to be any more events related to + // this key press + let event_info = self.event_info.take().unwrap(); + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + let ev = event_info.finalize(&mut layouts.strings); + return vec![MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }]; + } + winuser::WM_CHAR | winuser::WM_SYSCHAR => { + if self.event_info.is_none() { + trace!("Received a CHAR message but no `event_info` was available. The message is probably IME, returning."); + return vec![]; + } + *result = ProcResult::Value(0); + let is_high_surrogate = 0xD800 <= wparam && wparam <= 0xDBFF; + let is_low_surrogate = 0xDC00 <= wparam && wparam <= 0xDFFF; + + let is_utf16 = is_high_surrogate || is_low_surrogate; + + let more_char_coming; + unsafe { + let mut next_msg = MaybeUninit::uninit(); + let has_message = winuser::PeekMessageW( + next_msg.as_mut_ptr(), + hwnd, + winuser::WM_KEYFIRST, + winuser::WM_KEYLAST, + winuser::PM_NOREMOVE, + ); + let has_message = has_message != 0; + if !has_message { + more_char_coming = false; + } else { + let next_msg = next_msg.assume_init().message; + if next_msg == winuser::WM_CHAR || next_msg == winuser::WM_SYSCHAR { + more_char_coming = true; + } else { + more_char_coming = false; + } + } + } + + if is_utf16 { + if let Some(ev_info) = self.event_info.as_mut() { + ev_info.utf16parts.push(wparam as u16); + } + } else { + // In this case, wparam holds a UTF-32 character. + // Let's encode it as UTF-16 and append it to the end of `utf16parts` + let utf16parts = match self.event_info.as_mut() { + Some(ev_info) => &mut ev_info.utf16parts, + None => { + warn!("The event_info was None when it was expected to be some"); + return vec![]; + } + }; + let start_offset = utf16parts.len(); + let new_size = utf16parts.len() + 2; + utf16parts.resize(new_size, 0); + if let Some(ch) = char::from_u32(wparam as u32) { + let encode_len = ch.encode_utf16(&mut utf16parts[start_offset..]).len(); + let new_size = start_offset + encode_len; + utf16parts.resize(new_size, 0); + } + } + if !more_char_coming { + let mut event_info = match self.event_info.take() { + Some(ev_info) => ev_info, + None => { + warn!("The event_info was None when it was expected to be some"); + return vec![]; + } + }; + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + // It's okay to call `ToUnicode` here, because at this point the dead key + // is already consumed by the character. + let kbd_state = get_kbd_state(); + let mod_state = WindowsModifiers::active_modifiers(&kbd_state); + + let (_, layout) = layouts.get_current_layout(); + let ctrl_on; + if layout.has_alt_graph { + let alt_on = mod_state.contains(WindowsModifiers::ALT); + ctrl_on = !alt_on && mod_state.contains(WindowsModifiers::CONTROL) + } else { + ctrl_on = mod_state.contains(WindowsModifiers::CONTROL) + } + + // If Ctrl is not pressed, just use the text with all + // modifiers because that already consumed the dead key. Otherwise, + // we would interpret the character incorrectly, missing the dead key. + if !ctrl_on { + event_info.text = PartialText::System(event_info.utf16parts.clone()); + } else { + let mod_no_ctrl = mod_state.remove_only_ctrl(); + let num_lock_on = kbd_state[winuser::VK_NUMLOCK as usize] & 1 != 0; + let vkey = event_info.vkey; + let scancode = event_info.scancode; + let keycode = event_info.code; + let key = layout.get_key(mod_no_ctrl, num_lock_on, vkey, scancode, keycode); + event_info.text = PartialText::Text(key.to_text()); + } + let ev = event_info.finalize(&mut layouts.strings); + return vec![MessageAsKeyEvent { + event: ev, + is_synthetic: false, + }]; + } + } + winuser::WM_KEYUP | winuser::WM_SYSKEYUP => { + *result = ProcResult::Value(0); + + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + let event_info = PartialKeyEventInfo::from_message( + wparam, + lparam, + ElementState::Released, + &mut layouts, + ); + let mut next_msg = MaybeUninit::uninit(); + let peek_retval = unsafe { + winuser::PeekMessageW( + next_msg.as_mut_ptr(), + hwnd, + winuser::WM_KEYFIRST, + winuser::WM_KEYLAST, + winuser::PM_NOREMOVE, + ) + }; + let has_next_key_message = peek_retval != 0; + let mut valid_event_info = Some(event_info); + if has_next_key_message { + let next_msg = unsafe { next_msg.assume_init() }; + let (_, layout) = layouts.get_current_layout(); + let is_fake = { + let event_info = valid_event_info.as_ref().unwrap(); + is_current_fake(&event_info, next_msg, layout) + }; + if is_fake { + valid_event_info = None; + } + } + if let Some(event_info) = valid_event_info { + let event = event_info.finalize(&mut layouts.strings); + return vec![MessageAsKeyEvent { + event, + is_synthetic: false, + }]; + } + } + _ => (), + } + + Vec::new() + } + + fn synthesize_kbd_state( + &mut self, + key_state: ElementState, + kbd_state: &[u8; 256], + ) -> Vec { + let mut key_events = Vec::new(); + + let mut layouts = LAYOUT_CACHE.lock().unwrap(); + let (locale_id, _) = layouts.get_current_layout(); + + macro_rules! is_key_pressed { + ($vk:expr) => { + kbd_state[$vk as usize] & 0x80 != 0 + }; + } + + // Is caps-lock active? Note that this is different from caps-lock + // being held down. + let caps_lock_on = kbd_state[winuser::VK_CAPITAL as usize] & 1 != 0; + let num_lock_on = kbd_state[winuser::VK_NUMLOCK as usize] & 1 != 0; + + // We are synthesizing the press event for caps-lock first for the following reasons: + // 1. If caps-lock is *not* held down but *is* active, then we have to + // synthesize all printable keys, respecting the caps-lock state. + // 2. If caps-lock is held down, we could choose to sythesize its + // keypress after every other key, in which case all other keys *must* + // be sythesized as if the caps-lock state was be the opposite + // of what it currently is. + // -- + // For the sake of simplicity we are choosing to always sythesize + // caps-lock first, and always use the current caps-lock state + // to determine the produced text + if is_key_pressed!(winuser::VK_CAPITAL) { + let event = self.create_synthetic( + winuser::VK_CAPITAL, + key_state, + caps_lock_on, + num_lock_on, + locale_id as HKL, + &mut layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + let do_non_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { + for vk in 0..256 { + match vk { + winuser::VK_CONTROL + | winuser::VK_LCONTROL + | winuser::VK_RCONTROL + | winuser::VK_SHIFT + | winuser::VK_LSHIFT + | winuser::VK_RSHIFT + | winuser::VK_MENU + | winuser::VK_LMENU + | winuser::VK_RMENU + | winuser::VK_CAPITAL => continue, + _ => (), + } + if !is_key_pressed!(vk) { + continue; + } + let event = self.create_synthetic( + vk, + key_state, + caps_lock_on, + num_lock_on, + locale_id as HKL, + layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + }; + let do_modifier = |key_events: &mut Vec<_>, layouts: &mut _| { + const CLEAR_MODIFIER_VKS: [i32; 6] = [ + winuser::VK_LCONTROL, + winuser::VK_LSHIFT, + winuser::VK_LMENU, + winuser::VK_RCONTROL, + winuser::VK_RSHIFT, + winuser::VK_RMENU, + ]; + for vk in CLEAR_MODIFIER_VKS.iter() { + if is_key_pressed!(*vk) { + let event = self.create_synthetic( + *vk, + key_state, + caps_lock_on, + num_lock_on, + locale_id as HKL, + layouts, + ); + if let Some(event) = event { + key_events.push(event); + } + } + } + }; + + // Be cheeky and sequence modifier and non-modifier + // key events such that non-modifier keys are not affected + // by modifiers (except for caps-lock) + match key_state { + ElementState::Pressed => { + do_non_modifier(&mut key_events, &mut layouts); + do_modifier(&mut key_events, &mut layouts); + } + ElementState::Released => { + do_modifier(&mut key_events, &mut layouts); + do_non_modifier(&mut key_events, &mut layouts); + } + } + + key_events + } + + fn create_synthetic( + &self, + vk: i32, + key_state: ElementState, + caps_lock_on: bool, + num_lock_on: bool, + locale_id: HKL, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Option { + let scancode = unsafe { + winuser::MapVirtualKeyExW(vk as UINT, winuser::MAPVK_VK_TO_VSC_EX, locale_id) + }; + if scancode == 0 { + return None; + } + let scancode = scancode as ExScancode; + let code = KeyCode::from_scancode(scancode as u32); + let mods = if caps_lock_on { + WindowsModifiers::CAPS_LOCK + } else { + WindowsModifiers::empty() + }; + let layout = layouts.layouts.get(&(locale_id as u64)).unwrap(); + let logical_key = layout.get_key(mods, num_lock_on, vk, scancode, code); + let key_without_modifiers = + layout.get_key(WindowsModifiers::empty(), false, vk, scancode, code); + let text; + if key_state == ElementState::Pressed { + text = logical_key.to_text(); + } else { + text = None; + } + let event_info = PartialKeyEventInfo { + vkey: vk, + logical_key: PartialLogicalKey::This(logical_key), + key_without_modifiers, + key_state, + scancode, + is_repeat: false, + code, + location: get_location(scancode, locale_id), + utf16parts: Vec::with_capacity(8), + text: PartialText::Text(text), + }; + + let mut event = event_info.finalize(&mut layouts.strings); + event.logical_key = logical_key; + event.platform_specific.text_with_all_modifers = text; + Some(MessageAsKeyEvent { + event, + is_synthetic: true, + }) + } +} + +enum PartialText { + // Unicode + System(Vec), + Text(Option<&'static str>), +} + +enum PartialLogicalKey { + /// Use the text provided by the WM_CHAR messages and report that as a `Character` variant. If + /// the text consists of multiple grapheme clusters (user-precieved characters) that means that + /// dead key could not be combined with the second input, and in that case we should fall back + /// to using what would have without a dead-key input. + TextOr(Key<'static>), + + /// Use the value directly provided by this variant + This(Key<'static>), +} + +struct PartialKeyEventInfo { + vkey: c_int, + scancode: ExScancode, + key_state: ElementState, + is_repeat: bool, + code: KeyCode, + location: KeyLocation, + logical_key: PartialLogicalKey, + + key_without_modifiers: Key<'static>, + + /// The UTF-16 code units of the text that was produced by the keypress event. + /// This take all modifiers into account. Including CTRL + utf16parts: Vec, + + text: PartialText, +} + +impl PartialKeyEventInfo { + fn from_message( + wparam: WPARAM, + lparam: LPARAM, + state: ElementState, + layouts: &mut MutexGuard<'_, LayoutCache>, + ) -> Self { + const NO_MODS: WindowsModifiers = WindowsModifiers::empty(); + + let (_, layout) = layouts.get_current_layout(); + let lparam_struct = destructure_key_lparam(lparam); + let scancode; + let vkey = wparam as c_int; + if lparam_struct.scancode == 0 { + // In some cases (often with media keys) the device reports a scancode of 0 but a + // valid virtual key. In these cases we obtain the scancode from the virtual key. + scancode = unsafe { + winuser::MapVirtualKeyExW( + vkey as u32, + winuser::MAPVK_VK_TO_VSC_EX, + layout.hkl as HKL, + ) as u16 + }; + } else { + scancode = new_ex_scancode(lparam_struct.scancode, lparam_struct.extended); + } + let code = KeyCode::from_scancode(scancode as u32); + let location = get_location(scancode, layout.hkl as HKL); + + let kbd_state = get_kbd_state(); + let mods = WindowsModifiers::active_modifiers(&kbd_state); + let mods_without_ctrl = mods.remove_only_ctrl(); + let num_lock_on = kbd_state[winuser::VK_NUMLOCK as usize] & 1 != 0; + + // On Windows Ctrl+NumLock = Pause (and apparently Ctrl+Pause -> NumLock). In these cases + // the KeyCode still stores the real key, so in the name of consistency across platforms, we + // circumvent this mapping and force the key values to match the keycode. + // For more on this, read the article by Raymond Chen, titled: + // "Why does Ctrl+ScrollLock cancel dialogs?" + // https://devblogs.microsoft.com/oldnewthing/20080211-00/?p=23503 + let code_as_key = if mods.contains(WindowsModifiers::CONTROL) { + match code { + KeyCode::NumLock => Some(Key::NumLock), + KeyCode::Pause => Some(Key::Pause), + _ => None, + } + } else { + None + }; + + let preliminary_logical_key = + layout.get_key(mods_without_ctrl, num_lock_on, vkey, scancode, code); + let key_is_char = matches!(preliminary_logical_key, Key::Character(_)); + let is_pressed = state == ElementState::Pressed; + + let logical_key = if let Some(key) = code_as_key { + PartialLogicalKey::This(key) + } else if is_pressed && key_is_char && !mods.contains(WindowsModifiers::CONTROL) { + // In some cases we want to use the UNICHAR text for logical_key in order to allow + // dead keys to have an effect on the character reported by `logical_key`. + PartialLogicalKey::TextOr(preliminary_logical_key) + } else { + PartialLogicalKey::This(preliminary_logical_key) + }; + let key_without_modifiers = if let Some(key) = code_as_key { + key + } else { + match layout.get_key(NO_MODS, false, vkey, scancode, code) { + // We convert dead keys into their character. + // The reason for this is that `key_without_modifiers` is designed for key-bindings, + // but the US International layout treats `'` (apostrophe) as a dead key and the + // reguar US layout treats it a character. In order for a single binding + // configuration to work with both layouts, we forward each dead key as a character. + Key::Dead(k) => { + if let Some(ch) = k { + // I'm avoiding the heap allocation. I don't want to talk about it :( + let mut utf8 = [0; 4]; + let s = ch.encode_utf8(&mut utf8); + let static_str = get_or_insert_str(&mut layouts.strings, s); + Key::Character(static_str) + } else { + Key::Unidentified(NativeKeyCode::Unidentified) + } + } + key => key, + } + }; + + PartialKeyEventInfo { + vkey, + scancode, + key_state: state, + logical_key, + key_without_modifiers, + is_repeat: lparam_struct.is_repeat, + code, + location, + utf16parts: Vec::with_capacity(8), + text: PartialText::System(Vec::new()), + } + } + + fn finalize(self, strings: &mut HashSet<&'static str>) -> KeyEvent { + let mut char_with_all_modifiers = None; + if !self.utf16parts.is_empty() { + let os_string = OsString::from_wide(&self.utf16parts); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + char_with_all_modifiers = Some(static_str); + } + } + + // The text without Ctrl + let mut text = None; + match self.text { + PartialText::System(wide) => { + if !wide.is_empty() { + let os_string = OsString::from_wide(&wide); + if let Ok(string) = os_string.into_string() { + let static_str = get_or_insert_str(strings, string); + text = Some(static_str); + } + } + } + PartialText::Text(s) => { + text = s; + } + } + + let logical_key = match self.logical_key { + PartialLogicalKey::TextOr(fallback) => match text { + Some(s) => { + if s.grapheme_indices(true).count() > 1 { + fallback + } else { + Key::Character(s) + } + } + None => Key::Unidentified(NativeKeyCode::Windows(self.scancode)), + }, + PartialLogicalKey::This(v) => v, + }; + + KeyEvent { + physical_key: self.code, + logical_key, + text, + location: self.location, + state: self.key_state, + repeat: self.is_repeat, + platform_specific: KeyEventExtra { + text_with_all_modifers: char_with_all_modifiers, + key_without_modifiers: self.key_without_modifiers, + }, + } + } +} + +#[derive(Debug, Copy, Clone)] +struct KeyLParam { + pub scancode: u8, + pub extended: bool, + + /// This is `previous_state XOR transition_state`. See the lParam for WM_KEYDOWN and WM_KEYUP for further details. + pub is_repeat: bool, +} + +fn destructure_key_lparam(lparam: LPARAM) -> KeyLParam { + let previous_state = (lparam >> 30) & 0x01; + let transition_state = (lparam >> 31) & 0x01; + KeyLParam { + scancode: ((lparam >> 16) & 0xFF) as u8, + extended: ((lparam >> 24) & 0x01) != 0, + is_repeat: (previous_state ^ transition_state) != 0, + } +} + +#[inline] +fn new_ex_scancode(scancode: u8, extended: bool) -> ExScancode { + (scancode as u16) | (if extended { 0xE000 } else { 0 }) +} + +#[inline] +fn ex_scancode_from_lparam(lparam: LPARAM) -> ExScancode { + let lparam = destructure_key_lparam(lparam); + new_ex_scancode(lparam.scancode, lparam.extended) +} + +/// Gets the keyboard state as reported by messages that have been removed from the event queue. +/// See also: get_async_kbd_state +fn get_kbd_state() -> [u8; 256] { + unsafe { + let mut kbd_state: MaybeUninit<[u8; 256]> = MaybeUninit::uninit(); + winuser::GetKeyboardState(kbd_state.as_mut_ptr() as *mut u8); + kbd_state.assume_init() + } +} + +/// Gets the current keyboard state regardless of whether the corresponding keyboard events have +/// been removed from the event queue. See also: get_kbd_state +fn get_async_kbd_state() -> [u8; 256] { + unsafe { + let mut kbd_state: [u8; 256] = MaybeUninit::uninit().assume_init(); + for (vk, state) in kbd_state.iter_mut().enumerate() { + let vk = vk as c_int; + let async_state = winuser::GetAsyncKeyState(vk as c_int); + let is_down = (async_state & (1 << 15)) != 0; + *state = if is_down { 0x80 } else { 0 }; + + if matches!( + vk, + winuser::VK_CAPITAL | winuser::VK_NUMLOCK | winuser::VK_SCROLL + ) { + // Toggle states aren't reported by `GetAsyncKeyState` + let toggle_state = winuser::GetKeyState(vk); + let is_active = (toggle_state & 1) != 0; + *state |= if is_active { 1 } else { 0 }; + } + } + kbd_state + } +} + +/// On windows, AltGr == Ctrl + Alt +/// +/// Due to this equivalence, the system generates a fake Ctrl key-press (and key-release) preceeding +/// every AltGr key-press (and key-release). We check if the current event is a Ctrl event and if +/// the next event is a right Alt (AltGr) event. If this is the case, the current event must be the +/// fake Ctrl event. +fn is_current_fake( + curr_info: &PartialKeyEventInfo, + next_msg: winuser::MSG, + layout: &Layout, +) -> bool { + let curr_is_ctrl = matches!(curr_info.logical_key, PartialLogicalKey::This(Key::Control)); + if layout.has_alt_graph { + let next_code = ex_scancode_from_lparam(next_msg.lParam); + let next_is_altgr = next_code == 0xE038; // 0xE038 is right alt + if curr_is_ctrl && next_is_altgr { + return true; + } + } + false +} + +fn get_location(scancode: ExScancode, hkl: HKL) -> KeyLocation { + use winuser::*; + const VK_ABNT_C2: c_int = 0xc2; + + let extension = 0xE000; + let extended = (scancode & extension) == extension; + let vkey = unsafe { + winuser::MapVirtualKeyExW(scancode as u32, winuser::MAPVK_VSC_TO_VK_EX, hkl) as i32 + }; + + // Use the native VKEY and the extended flag to cover most cases + // This is taken from the `druid` GUI library, specifically + // druid-shell/src/platform/windows/keyboard.rs + match vkey { + VK_LSHIFT | VK_LCONTROL | VK_LMENU | VK_LWIN => KeyLocation::Left, + VK_RSHIFT | VK_RCONTROL | VK_RMENU | VK_RWIN => KeyLocation::Right, + VK_RETURN if extended => KeyLocation::Numpad, + VK_INSERT | VK_DELETE | VK_END | VK_DOWN | VK_NEXT | VK_LEFT | VK_CLEAR | VK_RIGHT + | VK_HOME | VK_UP | VK_PRIOR => { + if extended { + KeyLocation::Standard + } else { + KeyLocation::Numpad + } + } + VK_NUMPAD0 | VK_NUMPAD1 | VK_NUMPAD2 | VK_NUMPAD3 | VK_NUMPAD4 | VK_NUMPAD5 + | VK_NUMPAD6 | VK_NUMPAD7 | VK_NUMPAD8 | VK_NUMPAD9 | VK_DECIMAL | VK_DIVIDE + | VK_MULTIPLY | VK_SUBTRACT | VK_ADD | VK_ABNT_C2 => KeyLocation::Numpad, + _ => KeyLocation::Standard, + } +} diff --git a/src/platform_impl/windows/keyboard_layout.rs b/src/platform_impl/windows/keyboard_layout.rs new file mode 100644 index 0000000000..3f1100b9b4 --- /dev/null +++ b/src/platform_impl/windows/keyboard_layout.rs @@ -0,0 +1,993 @@ +use std::{ + collections::{hash_map::Entry, HashMap, HashSet}, + ffi::OsString, + os::windows::ffi::OsStringExt, + sync::Mutex, +}; + +use lazy_static::lazy_static; + +use winapi::{ + ctypes::c_int, + shared::minwindef::{HKL, LOWORD}, + um::{ + winnt::{LANG_JAPANESE, LANG_KOREAN, PRIMARYLANGID}, + winuser, + }, +}; + +use crate::{ + keyboard::{Key, KeyCode, ModifiersState, NativeKeyCode}, + platform::scancode::KeyCodeExtScancode, + platform_impl::platform::keyboard::ExScancode, +}; + +lazy_static! { + pub(crate) static ref LAYOUT_CACHE: Mutex = Mutex::new(LayoutCache::default()); +} + +fn key_pressed(vkey: c_int) -> bool { + unsafe { (winuser::GetKeyState(vkey) & (1 << 15)) == (1 << 15) } +} + +const NUMPAD_VKEYS: [c_int; 16] = [ + winuser::VK_NUMPAD0, + winuser::VK_NUMPAD1, + winuser::VK_NUMPAD2, + winuser::VK_NUMPAD3, + winuser::VK_NUMPAD4, + winuser::VK_NUMPAD5, + winuser::VK_NUMPAD6, + winuser::VK_NUMPAD7, + winuser::VK_NUMPAD8, + winuser::VK_NUMPAD9, + winuser::VK_MULTIPLY, + winuser::VK_ADD, + winuser::VK_SEPARATOR, + winuser::VK_SUBTRACT, + winuser::VK_DECIMAL, + winuser::VK_DIVIDE, +]; + +lazy_static! { + static ref NUMPAD_KEYCODES: HashSet = { + let mut keycodes = HashSet::new(); + keycodes.insert(KeyCode::Numpad0); + keycodes.insert(KeyCode::Numpad1); + keycodes.insert(KeyCode::Numpad2); + keycodes.insert(KeyCode::Numpad3); + keycodes.insert(KeyCode::Numpad4); + keycodes.insert(KeyCode::Numpad5); + keycodes.insert(KeyCode::Numpad6); + keycodes.insert(KeyCode::Numpad7); + keycodes.insert(KeyCode::Numpad8); + keycodes.insert(KeyCode::Numpad9); + keycodes.insert(KeyCode::NumpadMultiply); + keycodes.insert(KeyCode::NumpadAdd); + keycodes.insert(KeyCode::NumpadComma); + keycodes.insert(KeyCode::NumpadSubtract); + keycodes.insert(KeyCode::NumpadDecimal); + keycodes.insert(KeyCode::NumpadDivide); + keycodes + }; +} + +bitflags! { + pub struct WindowsModifiers : u8 { + const SHIFT = 1 << 0; + const CONTROL = 1 << 1; + const ALT = 1 << 2; + const CAPS_LOCK = 1 << 3; + const FLAGS_END = 1 << 4; + } +} + +impl WindowsModifiers { + pub fn active_modifiers(key_state: &[u8; 256]) -> WindowsModifiers { + let shift = key_state[winuser::VK_SHIFT as usize] & 0x80 != 0; + let lshift = key_state[winuser::VK_LSHIFT as usize] & 0x80 != 0; + let rshift = key_state[winuser::VK_RSHIFT as usize] & 0x80 != 0; + + let control = key_state[winuser::VK_CONTROL as usize] & 0x80 != 0; + let lcontrol = key_state[winuser::VK_LCONTROL as usize] & 0x80 != 0; + let rcontrol = key_state[winuser::VK_RCONTROL as usize] & 0x80 != 0; + + let alt = key_state[winuser::VK_MENU as usize] & 0x80 != 0; + let lalt = key_state[winuser::VK_LMENU as usize] & 0x80 != 0; + let ralt = key_state[winuser::VK_RMENU as usize] & 0x80 != 0; + + let caps = key_state[winuser::VK_CAPITAL as usize] & 0x01 != 0; + + let mut result = WindowsModifiers::empty(); + if shift || lshift || rshift { + result.insert(WindowsModifiers::SHIFT); + } + if control || lcontrol || rcontrol { + result.insert(WindowsModifiers::CONTROL); + } + if alt || lalt || ralt { + result.insert(WindowsModifiers::ALT); + } + if caps { + result.insert(WindowsModifiers::CAPS_LOCK); + } + + result + } + + pub fn apply_to_kbd_state(self, key_state: &mut [u8; 256]) { + if self.intersects(Self::SHIFT) { + key_state[winuser::VK_SHIFT as usize] |= 0x80; + } else { + key_state[winuser::VK_SHIFT as usize] &= !0x80; + key_state[winuser::VK_LSHIFT as usize] &= !0x80; + key_state[winuser::VK_RSHIFT as usize] &= !0x80; + } + if self.intersects(Self::CONTROL) { + key_state[winuser::VK_CONTROL as usize] |= 0x80; + } else { + key_state[winuser::VK_CONTROL as usize] &= !0x80; + key_state[winuser::VK_LCONTROL as usize] &= !0x80; + key_state[winuser::VK_RCONTROL as usize] &= !0x80; + } + if self.intersects(Self::ALT) { + key_state[winuser::VK_MENU as usize] |= 0x80; + } else { + key_state[winuser::VK_MENU as usize] &= !0x80; + key_state[winuser::VK_LMENU as usize] &= !0x80; + key_state[winuser::VK_RMENU as usize] &= !0x80; + } + if self.intersects(Self::CAPS_LOCK) { + key_state[winuser::VK_CAPITAL as usize] |= 0x01; + } else { + key_state[winuser::VK_CAPITAL as usize] &= !0x01; + } + } + + /// Removes the control modifier if the alt modifier is not present. + /// This is useful because on Windows: (Control + Alt) == AltGr + /// but we don't want to interfere with the AltGr state. + pub fn remove_only_ctrl(mut self) -> WindowsModifiers { + if !self.contains(WindowsModifiers::ALT) { + self.remove(WindowsModifiers::CONTROL); + } + self + } +} + +pub(crate) struct Layout { + pub hkl: u64, + + /// Maps numpad keys from Windows virtual key to a `Key`. + /// + /// This is useful because some numpad keys generate different charcaters based on the locale. + /// For example `VK_DECIMAL` is sometimes "." and sometimes ",". Note: numpad-specific virtual + /// keys are only produced by Windows when the NumLock is active. + /// + /// Making this field separate from the `keys` field saves having to add NumLock as a modifier + /// to `WindowsModifiers`, which would double the number of items in keys. + pub numlock_on_keys: HashMap>, + /// Like `numlock_on_keys` but this will map to the key that would be produced if numlock was + /// off. The keys of this map are identical to the keys of `numlock_on_keys`. + pub numlock_off_keys: HashMap>, + + /// Maps a modifier state to group of key strings + /// We're not using `ModifiersState` here because that object cannot express caps lock, + /// but we need to handle caps lock too. + /// + /// This map shouldn't need to exist. + /// However currently this seems to be the only good way + /// of getting the label for the pressed key. Note that calling `ToUnicode` + /// just when the key is pressed/released would be enough if `ToUnicode` wouldn't + /// change the keyboard state (it clears the dead key). There is a flag to prevent + /// changing the state, but that flag requires Windows 10, version 1607 or newer) + pub keys: HashMap>>, + pub has_alt_graph: bool, +} + +impl Layout { + pub fn get_key( + &self, + mods: WindowsModifiers, + num_lock_on: bool, + vkey: c_int, + scancode: ExScancode, + keycode: KeyCode, + ) -> Key<'static> { + let native_code = NativeKeyCode::Windows(scancode); + + let unknown_alt = vkey == winuser::VK_MENU; + if !unknown_alt { + // Here we try using the virtual key directly but if the virtual key doesn't distinguish + // between left and right alt, we can't report AltGr. Therefore, we only do this if the + // key is not the "unknown alt" key. + // + // The reason for using the virtual key directly is that `MapVirtualKeyExW` (used when + // building the keys map) sometimes maps virtual keys to odd scancodes that don't match + // the scancode coming from the KEYDOWN message for the same key. For example: `VK_LEFT` + // is mapped to `0x004B`, but the scancode for the left arrow is `0xE04B`. + let key_from_vkey = + vkey_to_non_char_key(vkey, native_code, self.hkl, self.has_alt_graph); + + if !matches!(key_from_vkey, Key::Unidentified(_)) { + return key_from_vkey; + } + } + if num_lock_on { + if let Some(key) = self.numlock_on_keys.get(&vkey) { + return *key; + } + } else { + if let Some(key) = self.numlock_off_keys.get(&vkey) { + return *key; + } + } + if let Some(keys) = self.keys.get(&mods) { + if let Some(key) = keys.get(&keycode) { + return *key; + } + } + Key::Unidentified(native_code) + } +} + +#[derive(Default)] +pub(crate) struct LayoutCache { + /// Maps locale identifiers (HKL) to layouts + pub layouts: HashMap, + pub strings: HashSet<&'static str>, +} + +impl LayoutCache { + /// Checks whether the current layout is already known and + /// prepares the layout if it isn't known. + /// The current layout is then returned. + pub fn get_current_layout<'a>(&'a mut self) -> (u64, &'a Layout) { + let locale_id = unsafe { winuser::GetKeyboardLayout(0) } as u64; + match self.layouts.entry(locale_id) { + Entry::Occupied(entry) => (locale_id, entry.into_mut()), + Entry::Vacant(entry) => { + let layout = Self::prepare_layout(&mut self.strings, locale_id); + (locale_id, entry.insert(layout)) + } + } + } + + pub fn get_agnostic_mods(&mut self) -> ModifiersState { + let (_, layout) = self.get_current_layout(); + let filter_out_altgr = layout.has_alt_graph && key_pressed(winuser::VK_RMENU); + let mut mods = ModifiersState::empty(); + mods.set(ModifiersState::SHIFT, key_pressed(winuser::VK_SHIFT)); + mods.set( + ModifiersState::CONTROL, + key_pressed(winuser::VK_CONTROL) && !filter_out_altgr, + ); + mods.set( + ModifiersState::ALT, + key_pressed(winuser::VK_MENU) && !filter_out_altgr, + ); + mods.set( + ModifiersState::SUPER, + key_pressed(winuser::VK_LWIN) || key_pressed(winuser::VK_RWIN), + ); + mods + } + + fn prepare_layout(strings: &mut HashSet<&'static str>, locale_id: u64) -> Layout { + let mut layout = Layout { + hkl: locale_id, + numlock_on_keys: Default::default(), + numlock_off_keys: Default::default(), + keys: Default::default(), + has_alt_graph: false, + }; + + // We initialize the keyboard state with all zeros to + // simulate a scenario when no modifier is active. + let mut key_state = [0u8; 256]; + + // `MapVirtualKeyExW` maps (non-numpad-specific) virtual keys to scancodes as if numlock + // was off. We rely on this behavior to find all virtual keys which are not numpad-specific + // but map to the numpad. + // + // src_vkey: VK ==> scancode: u16 (on the numpad) + // + // Then we convert the source virtual key into a `Key` and the scancode into a virtual key + // to get the reverse mapping. + // + // src_vkey: VK ==> scancode: u16 (on the numpad) + // || || + // \/ \/ + // map_value: Key <- map_vkey: VK + layout.numlock_off_keys.reserve(NUMPAD_KEYCODES.len()); + for vk in 0..256 { + let scancode = unsafe { + winuser::MapVirtualKeyExW(vk, winuser::MAPVK_VK_TO_VSC_EX, locale_id as HKL) + }; + if scancode == 0 { + continue; + } + let keycode = KeyCode::from_scancode(scancode); + if !is_numpad_specific(vk as i32) && NUMPAD_KEYCODES.contains(&keycode) { + let native_code = NativeKeyCode::Windows(scancode as u16); + let map_vkey = keycode_to_vkey(keycode, locale_id); + if map_vkey == 0 { + continue; + } + let map_value = vkey_to_non_char_key(vk as i32, native_code, locale_id, false); + if matches!(map_value, Key::Unidentified(_)) { + continue; + } + layout.numlock_off_keys.insert(map_vkey, map_value); + } + } + + layout.numlock_on_keys.reserve(NUMPAD_VKEYS.len()); + for vk in NUMPAD_VKEYS.iter() { + let vk = (*vk) as u32; + let scancode = unsafe { + winuser::MapVirtualKeyExW(vk, winuser::MAPVK_VK_TO_VSC_EX, locale_id as HKL) + }; + let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id); + if let ToUnicodeResult::Str(s) = unicode { + let static_str = get_or_insert_str(strings, s); + layout + .numlock_on_keys + .insert(vk as i32, Key::Character(static_str)); + } + } + + // Iterate through every combination of modifiers + let mods_end = WindowsModifiers::FLAGS_END.bits; + for mod_state in 0..mods_end { + let mut keys_for_this_mod = HashMap::with_capacity(256); + + let mod_state = unsafe { WindowsModifiers::from_bits_unchecked(mod_state) }; + mod_state.apply_to_kbd_state(&mut key_state); + + // Virtual key values are in the domain [0, 255]. + // This is reinforced by the fact that the keyboard state array has 256 + // elements. This array is allowed to be indexed by virtual key values + // giving the key state for the virtual key used for indexing. + for vk in 0..256 { + let scancode = unsafe { + winuser::MapVirtualKeyExW(vk, winuser::MAPVK_VK_TO_VSC_EX, locale_id as HKL) + }; + if scancode == 0 { + continue; + } + + let native_code = NativeKeyCode::Windows(scancode as ExScancode); + let key_code = KeyCode::from_scancode(scancode); + // Let's try to get the key from just the scancode and vk + // We don't necessarily know yet if AltGraph is present on this layout so we'll + // assume it isn't. Then we'll do a second pass where we set the "AltRight" keys to + // "AltGr" in case we find out that there's an AltGraph. + let preliminary_key = + vkey_to_non_char_key(vk as i32, native_code, locale_id, false); + match preliminary_key { + Key::Unidentified(_) => (), + _ => { + keys_for_this_mod.insert(key_code, preliminary_key); + continue; + } + } + + let unicode = Self::to_unicode_string(&key_state, vk, scancode, locale_id); + let key = match unicode { + ToUnicodeResult::Str(str) => { + let static_str = get_or_insert_str(strings, str); + Key::Character(static_str) + } + ToUnicodeResult::Dead(dead_char) => { + //println!("{:?} - {:?} produced dead {:?}", key_code, mod_state, dead_char); + Key::Dead(dead_char) + } + ToUnicodeResult::None => { + let has_alt = mod_state.contains(WindowsModifiers::ALT); + let has_ctrl = mod_state.contains(WindowsModifiers::CONTROL); + // HACK: `ToUnicodeEx` seems to fail getting the string for the numpad + // divide key, so we handle that explicitly here + if !has_alt && !has_ctrl && key_code == KeyCode::NumpadDivide { + Key::Character("/") + } else { + // Just use the unidentified key, we got earlier + preliminary_key + } + } + }; + + // Check for alt graph. + // The logic is that if a key pressed with no modifier produces + // a different `Character` from when it's pressed with CTRL+ALT then the layout + // has AltGr. + let ctrl_alt: WindowsModifiers = WindowsModifiers::CONTROL | WindowsModifiers::ALT; + let is_in_ctrl_alt = mod_state == ctrl_alt; + if !layout.has_alt_graph && is_in_ctrl_alt { + // Unwrapping here because if we are in the ctrl+alt modifier state + // then the alt modifier state must have come before. + let simple_keys = layout.keys.get(&WindowsModifiers::empty()).unwrap(); + if let Some(Key::Character(key_no_altgr)) = simple_keys.get(&key_code) { + if let Key::Character(key) = key { + layout.has_alt_graph = key != *key_no_altgr; + } + } + } + + keys_for_this_mod.insert(key_code, key); + } + layout.keys.insert(mod_state, keys_for_this_mod); + } + + // Second pass: replace right alt keys with AltGr if the layout has alt graph + if layout.has_alt_graph { + for mod_state in 0..mods_end { + let mod_state = unsafe { WindowsModifiers::from_bits_unchecked(mod_state) }; + if let Some(keys) = layout.keys.get_mut(&mod_state) { + if let Some(key) = keys.get_mut(&KeyCode::AltRight) { + *key = Key::AltGraph; + } + } + } + } + + layout + } + + fn to_unicode_string( + key_state: &[u8; 256], + vkey: u32, + scancode: u32, + locale_id: u64, + ) -> ToUnicodeResult { + unsafe { + let mut label_wide = [0u16; 8]; + let mut wide_len = winuser::ToUnicodeEx( + vkey, + scancode, + (&key_state[0]) as *const _, + (&mut label_wide[0]) as *mut _, + label_wide.len() as i32, + 0, + locale_id as HKL, + ); + if wide_len < 0 { + // If it's dead, we run `ToUnicode` again to consume the dead-key + wide_len = winuser::ToUnicodeEx( + vkey, + scancode, + (&key_state[0]) as *const _, + (&mut label_wide[0]) as *mut _, + label_wide.len() as i32, + 0, + locale_id as HKL, + ); + if wide_len > 0 { + let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); + if let Ok(label_str) = os_string.into_string() { + if let Some(ch) = label_str.chars().next() { + return ToUnicodeResult::Dead(Some(ch)); + } + } + } + return ToUnicodeResult::Dead(None); + } + if wide_len > 0 { + let os_string = OsString::from_wide(&label_wide[0..wide_len as usize]); + if let Ok(label_str) = os_string.into_string() { + return ToUnicodeResult::Str(label_str); + } + } + } + ToUnicodeResult::None + } +} + +pub fn get_or_insert_str(strings: &mut HashSet<&'static str>, string: T) -> &'static str +where + T: AsRef, + String: From, +{ + { + let str_ref = string.as_ref(); + if let Some(&existing) = strings.get(str_ref) { + return existing; + } + } + let leaked = Box::leak(Box::from(String::from(string))); + strings.insert(leaked); + leaked +} + +#[derive(Debug, Clone, Eq, PartialEq)] +enum ToUnicodeResult { + Str(String), + Dead(Option), + None, +} + +fn is_numpad_specific(vk: i32) -> bool { + match vk { + winuser::VK_NUMPAD0 => true, + winuser::VK_NUMPAD1 => true, + winuser::VK_NUMPAD2 => true, + winuser::VK_NUMPAD3 => true, + winuser::VK_NUMPAD4 => true, + winuser::VK_NUMPAD5 => true, + winuser::VK_NUMPAD6 => true, + winuser::VK_NUMPAD7 => true, + winuser::VK_NUMPAD8 => true, + winuser::VK_NUMPAD9 => true, + winuser::VK_ADD => true, + winuser::VK_SUBTRACT => true, + winuser::VK_DIVIDE => true, + winuser::VK_DECIMAL => true, + winuser::VK_SEPARATOR => true, + _ => false, + } +} + +fn keycode_to_vkey(keycode: KeyCode, hkl: u64) -> i32 { + let primary_lang_id = PRIMARYLANGID(LOWORD(hkl as u32)); + let is_korean = primary_lang_id == LANG_KOREAN; + let is_japanese = primary_lang_id == LANG_JAPANESE; + + match keycode { + KeyCode::Backquote => 0, + KeyCode::Backslash => 0, + KeyCode::BracketLeft => 0, + KeyCode::BracketRight => 0, + KeyCode::Comma => 0, + KeyCode::Digit0 => 0, + KeyCode::Digit1 => 0, + KeyCode::Digit2 => 0, + KeyCode::Digit3 => 0, + KeyCode::Digit4 => 0, + KeyCode::Digit5 => 0, + KeyCode::Digit6 => 0, + KeyCode::Digit7 => 0, + KeyCode::Digit8 => 0, + KeyCode::Digit9 => 0, + KeyCode::Equal => 0, + KeyCode::IntlBackslash => 0, + KeyCode::IntlRo => 0, + KeyCode::IntlYen => 0, + KeyCode::KeyA => 0, + KeyCode::KeyB => 0, + KeyCode::KeyC => 0, + KeyCode::KeyD => 0, + KeyCode::KeyE => 0, + KeyCode::KeyF => 0, + KeyCode::KeyG => 0, + KeyCode::KeyH => 0, + KeyCode::KeyI => 0, + KeyCode::KeyJ => 0, + KeyCode::KeyK => 0, + KeyCode::KeyL => 0, + KeyCode::KeyM => 0, + KeyCode::KeyN => 0, + KeyCode::KeyO => 0, + KeyCode::KeyP => 0, + KeyCode::KeyQ => 0, + KeyCode::KeyR => 0, + KeyCode::KeyS => 0, + KeyCode::KeyT => 0, + KeyCode::KeyU => 0, + KeyCode::KeyV => 0, + KeyCode::KeyW => 0, + KeyCode::KeyX => 0, + KeyCode::KeyY => 0, + KeyCode::KeyZ => 0, + KeyCode::Minus => 0, + KeyCode::Period => 0, + KeyCode::Quote => 0, + KeyCode::Semicolon => 0, + KeyCode::Slash => 0, + KeyCode::AltLeft => winuser::VK_LMENU, + KeyCode::AltRight => winuser::VK_RMENU, + KeyCode::Backspace => winuser::VK_BACK, + KeyCode::CapsLock => winuser::VK_CAPITAL, + KeyCode::ContextMenu => winuser::VK_APPS, + KeyCode::ControlLeft => winuser::VK_LCONTROL, + KeyCode::ControlRight => winuser::VK_RCONTROL, + KeyCode::Enter => winuser::VK_RETURN, + KeyCode::SuperLeft => winuser::VK_LWIN, + KeyCode::SuperRight => winuser::VK_RWIN, + KeyCode::ShiftLeft => winuser::VK_RSHIFT, + KeyCode::ShiftRight => winuser::VK_LSHIFT, + KeyCode::Space => winuser::VK_SPACE, + KeyCode::Tab => winuser::VK_TAB, + KeyCode::Convert => winuser::VK_CONVERT, + KeyCode::KanaMode => winuser::VK_KANA, + KeyCode::Lang1 if is_korean => winuser::VK_HANGUL, + KeyCode::Lang1 if is_japanese => winuser::VK_KANA, + KeyCode::Lang2 if is_korean => winuser::VK_HANJA, + KeyCode::Lang2 if is_japanese => 0, + KeyCode::Lang3 if is_japanese => winuser::VK_OEM_FINISH, + KeyCode::Lang4 if is_japanese => 0, + KeyCode::Lang5 if is_japanese => 0, + KeyCode::NonConvert => winuser::VK_NONCONVERT, + KeyCode::Delete => winuser::VK_DELETE, + KeyCode::End => winuser::VK_END, + KeyCode::Help => winuser::VK_HELP, + KeyCode::Home => winuser::VK_HOME, + KeyCode::Insert => winuser::VK_INSERT, + KeyCode::PageDown => winuser::VK_NEXT, + KeyCode::PageUp => winuser::VK_PRIOR, + KeyCode::ArrowDown => winuser::VK_DOWN, + KeyCode::ArrowLeft => winuser::VK_LEFT, + KeyCode::ArrowRight => winuser::VK_RIGHT, + KeyCode::ArrowUp => winuser::VK_UP, + KeyCode::NumLock => winuser::VK_NUMLOCK, + KeyCode::Numpad0 => winuser::VK_NUMPAD0, + KeyCode::Numpad1 => winuser::VK_NUMPAD1, + KeyCode::Numpad2 => winuser::VK_NUMPAD2, + KeyCode::Numpad3 => winuser::VK_NUMPAD3, + KeyCode::Numpad4 => winuser::VK_NUMPAD4, + KeyCode::Numpad5 => winuser::VK_NUMPAD5, + KeyCode::Numpad6 => winuser::VK_NUMPAD6, + KeyCode::Numpad7 => winuser::VK_NUMPAD7, + KeyCode::Numpad8 => winuser::VK_NUMPAD8, + KeyCode::Numpad9 => winuser::VK_NUMPAD9, + KeyCode::NumpadAdd => winuser::VK_ADD, + KeyCode::NumpadBackspace => winuser::VK_BACK, + KeyCode::NumpadClear => winuser::VK_CLEAR, + KeyCode::NumpadClearEntry => 0, + KeyCode::NumpadComma => winuser::VK_SEPARATOR, + KeyCode::NumpadDecimal => winuser::VK_DECIMAL, + KeyCode::NumpadDivide => winuser::VK_DIVIDE, + KeyCode::NumpadEnter => winuser::VK_RETURN, + KeyCode::NumpadEqual => 0, + KeyCode::NumpadHash => 0, + KeyCode::NumpadMemoryAdd => 0, + KeyCode::NumpadMemoryClear => 0, + KeyCode::NumpadMemoryRecall => 0, + KeyCode::NumpadMemoryStore => 0, + KeyCode::NumpadMemorySubtract => 0, + KeyCode::NumpadMultiply => winuser::VK_MULTIPLY, + KeyCode::NumpadParenLeft => 0, + KeyCode::NumpadParenRight => 0, + KeyCode::NumpadStar => 0, + KeyCode::NumpadSubtract => winuser::VK_SUBTRACT, + KeyCode::Escape => winuser::VK_ESCAPE, + KeyCode::Fn => 0, + KeyCode::FnLock => 0, + KeyCode::PrintScreen => winuser::VK_SNAPSHOT, + KeyCode::ScrollLock => winuser::VK_SCROLL, + KeyCode::Pause => winuser::VK_PAUSE, + KeyCode::BrowserBack => winuser::VK_BROWSER_BACK, + KeyCode::BrowserFavorites => winuser::VK_BROWSER_FAVORITES, + KeyCode::BrowserForward => winuser::VK_BROWSER_FORWARD, + KeyCode::BrowserHome => winuser::VK_BROWSER_HOME, + KeyCode::BrowserRefresh => winuser::VK_BROWSER_REFRESH, + KeyCode::BrowserSearch => winuser::VK_BROWSER_SEARCH, + KeyCode::BrowserStop => winuser::VK_BROWSER_STOP, + KeyCode::Eject => 0, + KeyCode::LaunchApp1 => winuser::VK_LAUNCH_APP1, + KeyCode::LaunchApp2 => winuser::VK_LAUNCH_APP2, + KeyCode::LaunchMail => winuser::VK_LAUNCH_MAIL, + KeyCode::MediaPlayPause => winuser::VK_MEDIA_PLAY_PAUSE, + KeyCode::MediaSelect => winuser::VK_LAUNCH_MEDIA_SELECT, + KeyCode::MediaStop => winuser::VK_MEDIA_STOP, + KeyCode::MediaTrackNext => winuser::VK_MEDIA_NEXT_TRACK, + KeyCode::MediaTrackPrevious => winuser::VK_MEDIA_PREV_TRACK, + KeyCode::Power => 0, + KeyCode::Sleep => 0, + KeyCode::AudioVolumeDown => winuser::VK_VOLUME_DOWN, + KeyCode::AudioVolumeMute => winuser::VK_VOLUME_MUTE, + KeyCode::AudioVolumeUp => winuser::VK_VOLUME_UP, + KeyCode::WakeUp => 0, + KeyCode::Hyper => 0, + KeyCode::Turbo => 0, + KeyCode::Abort => 0, + KeyCode::Resume => 0, + KeyCode::Suspend => 0, + KeyCode::Again => 0, + KeyCode::Copy => 0, + KeyCode::Cut => 0, + KeyCode::Find => 0, + KeyCode::Open => 0, + KeyCode::Paste => 0, + KeyCode::Props => 0, + KeyCode::Select => winuser::VK_SELECT, + KeyCode::Undo => 0, + KeyCode::Hiragana => 0, + KeyCode::Katakana => 0, + KeyCode::F1 => winuser::VK_F1, + KeyCode::F2 => winuser::VK_F2, + KeyCode::F3 => winuser::VK_F3, + KeyCode::F4 => winuser::VK_F4, + KeyCode::F5 => winuser::VK_F5, + KeyCode::F6 => winuser::VK_F6, + KeyCode::F7 => winuser::VK_F7, + KeyCode::F8 => winuser::VK_F8, + KeyCode::F9 => winuser::VK_F9, + KeyCode::F10 => winuser::VK_F10, + KeyCode::F11 => winuser::VK_F11, + KeyCode::F12 => winuser::VK_F12, + KeyCode::F13 => winuser::VK_F13, + KeyCode::F14 => winuser::VK_F14, + KeyCode::F15 => winuser::VK_F15, + KeyCode::F16 => winuser::VK_F16, + KeyCode::F17 => winuser::VK_F17, + KeyCode::F18 => winuser::VK_F18, + KeyCode::F19 => winuser::VK_F19, + KeyCode::F20 => winuser::VK_F20, + KeyCode::F21 => winuser::VK_F21, + KeyCode::F22 => winuser::VK_F22, + KeyCode::F23 => winuser::VK_F23, + KeyCode::F24 => winuser::VK_F24, + KeyCode::F25 => 0, + KeyCode::F26 => 0, + KeyCode::F27 => 0, + KeyCode::F28 => 0, + KeyCode::F29 => 0, + KeyCode::F30 => 0, + KeyCode::F31 => 0, + KeyCode::F32 => 0, + KeyCode::F33 => 0, + KeyCode::F34 => 0, + KeyCode::F35 => 0, + KeyCode::Unidentified(_) => 0, + _ => 0, + } +} + +/// This converts virtual keys to `Key`s. Only virtual keys which can be unambiguously converted to +/// a `Key`, with only the information passed in as arguments, are converted. +/// +/// In other words: this function does not need to "prepare" the current layout in order to do +/// the conversion, but as such it cannot convert certain keys, like language-specific character keys. +/// +/// The result includes all non-character keys defined within `Key` plus characters from numpad keys. +/// For example, backspace and tab are included. +fn vkey_to_non_char_key( + vkey: i32, + native_code: NativeKeyCode, + hkl: u64, + has_alt_graph: bool, +) -> Key<'static> { + // List of the Web key names and their corresponding platform-native key names: + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + + let primary_lang_id = PRIMARYLANGID(LOWORD(hkl as u32)); + let is_korean = primary_lang_id == LANG_KOREAN; + let is_japanese = primary_lang_id == LANG_JAPANESE; + + match vkey { + winuser::VK_LBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + winuser::VK_RBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + + // I don't think this can be represented with a Key + winuser::VK_CANCEL => Key::Unidentified(native_code), + + winuser::VK_MBUTTON => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + winuser::VK_XBUTTON1 => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + winuser::VK_XBUTTON2 => Key::Unidentified(NativeKeyCode::Unidentified), // Mouse + winuser::VK_BACK => Key::Backspace, + winuser::VK_TAB => Key::Tab, + winuser::VK_CLEAR => Key::Clear, + winuser::VK_RETURN => Key::Enter, + winuser::VK_SHIFT => Key::Shift, + winuser::VK_CONTROL => Key::Control, + winuser::VK_MENU => Key::Alt, + winuser::VK_PAUSE => Key::Pause, + winuser::VK_CAPITAL => Key::CapsLock, + + //winuser::VK_HANGEUL => Key::HangulMode, // Deprecated in favour of VK_HANGUL + + // VK_HANGUL and VK_KANA are defined as the same constant, therefore + // we use appropriate conditions to differentate between them + winuser::VK_HANGUL if is_korean => Key::HangulMode, + winuser::VK_KANA if is_japanese => Key::KanaMode, + + winuser::VK_JUNJA => Key::JunjaMode, + winuser::VK_FINAL => Key::FinalMode, + + // VK_HANJA and VK_KANJI are defined as the same constant, therefore + // we use appropriate conditions to differentate between them + winuser::VK_HANJA if is_korean => Key::HanjaMode, + winuser::VK_KANJI if is_japanese => Key::KanjiMode, + + winuser::VK_ESCAPE => Key::Escape, + winuser::VK_CONVERT => Key::Convert, + winuser::VK_NONCONVERT => Key::NonConvert, + winuser::VK_ACCEPT => Key::Accept, + winuser::VK_MODECHANGE => Key::ModeChange, + winuser::VK_SPACE => Key::Space, + winuser::VK_PRIOR => Key::PageUp, + winuser::VK_NEXT => Key::PageDown, + winuser::VK_END => Key::End, + winuser::VK_HOME => Key::Home, + winuser::VK_LEFT => Key::ArrowLeft, + winuser::VK_UP => Key::ArrowUp, + winuser::VK_RIGHT => Key::ArrowRight, + winuser::VK_DOWN => Key::ArrowDown, + winuser::VK_SELECT => Key::Select, + winuser::VK_PRINT => Key::Print, + winuser::VK_EXECUTE => Key::Execute, + winuser::VK_SNAPSHOT => Key::PrintScreen, + winuser::VK_INSERT => Key::Insert, + winuser::VK_DELETE => Key::Delete, + winuser::VK_HELP => Key::Help, + winuser::VK_LWIN => Key::Super, + winuser::VK_RWIN => Key::Super, + winuser::VK_APPS => Key::ContextMenu, + winuser::VK_SLEEP => Key::Standby, + + // Numpad keys produce characters + winuser::VK_NUMPAD0 => Key::Unidentified(native_code), + winuser::VK_NUMPAD1 => Key::Unidentified(native_code), + winuser::VK_NUMPAD2 => Key::Unidentified(native_code), + winuser::VK_NUMPAD3 => Key::Unidentified(native_code), + winuser::VK_NUMPAD4 => Key::Unidentified(native_code), + winuser::VK_NUMPAD5 => Key::Unidentified(native_code), + winuser::VK_NUMPAD6 => Key::Unidentified(native_code), + winuser::VK_NUMPAD7 => Key::Unidentified(native_code), + winuser::VK_NUMPAD8 => Key::Unidentified(native_code), + winuser::VK_NUMPAD9 => Key::Unidentified(native_code), + winuser::VK_MULTIPLY => Key::Unidentified(native_code), + winuser::VK_ADD => Key::Unidentified(native_code), + winuser::VK_SEPARATOR => Key::Unidentified(native_code), + winuser::VK_SUBTRACT => Key::Unidentified(native_code), + winuser::VK_DECIMAL => Key::Unidentified(native_code), + winuser::VK_DIVIDE => Key::Unidentified(native_code), + + winuser::VK_F1 => Key::F1, + winuser::VK_F2 => Key::F2, + winuser::VK_F3 => Key::F3, + winuser::VK_F4 => Key::F4, + winuser::VK_F5 => Key::F5, + winuser::VK_F6 => Key::F6, + winuser::VK_F7 => Key::F7, + winuser::VK_F8 => Key::F8, + winuser::VK_F9 => Key::F9, + winuser::VK_F10 => Key::F10, + winuser::VK_F11 => Key::F11, + winuser::VK_F12 => Key::F12, + winuser::VK_F13 => Key::F13, + winuser::VK_F14 => Key::F14, + winuser::VK_F15 => Key::F15, + winuser::VK_F16 => Key::F16, + winuser::VK_F17 => Key::F17, + winuser::VK_F18 => Key::F18, + winuser::VK_F19 => Key::F19, + winuser::VK_F20 => Key::F20, + winuser::VK_F21 => Key::F21, + winuser::VK_F22 => Key::F22, + winuser::VK_F23 => Key::F23, + winuser::VK_F24 => Key::F24, + winuser::VK_NAVIGATION_VIEW => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_MENU => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_UP => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_DOWN => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_LEFT => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_RIGHT => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_ACCEPT => Key::Unidentified(native_code), + winuser::VK_NAVIGATION_CANCEL => Key::Unidentified(native_code), + winuser::VK_NUMLOCK => Key::NumLock, + winuser::VK_SCROLL => Key::ScrollLock, + winuser::VK_OEM_NEC_EQUAL => Key::Unidentified(native_code), + //winuser::VK_OEM_FJ_JISHO => Key::Unidentified(native_code), // Conflicts with `VK_OEM_NEC_EQUAL` + winuser::VK_OEM_FJ_MASSHOU => Key::Unidentified(native_code), + winuser::VK_OEM_FJ_TOUROKU => Key::Unidentified(native_code), + winuser::VK_OEM_FJ_LOYA => Key::Unidentified(native_code), + winuser::VK_OEM_FJ_ROYA => Key::Unidentified(native_code), + winuser::VK_LSHIFT => Key::Shift, + winuser::VK_RSHIFT => Key::Shift, + winuser::VK_LCONTROL => Key::Control, + winuser::VK_RCONTROL => Key::Control, + winuser::VK_LMENU => Key::Alt, + winuser::VK_RMENU => { + if has_alt_graph { + Key::AltGraph + } else { + Key::Alt + } + } + winuser::VK_BROWSER_BACK => Key::BrowserBack, + winuser::VK_BROWSER_FORWARD => Key::BrowserForward, + winuser::VK_BROWSER_REFRESH => Key::BrowserRefresh, + winuser::VK_BROWSER_STOP => Key::BrowserStop, + winuser::VK_BROWSER_SEARCH => Key::BrowserSearch, + winuser::VK_BROWSER_FAVORITES => Key::BrowserFavorites, + winuser::VK_BROWSER_HOME => Key::BrowserHome, + winuser::VK_VOLUME_MUTE => Key::AudioVolumeMute, + winuser::VK_VOLUME_DOWN => Key::AudioVolumeDown, + winuser::VK_VOLUME_UP => Key::AudioVolumeUp, + winuser::VK_MEDIA_NEXT_TRACK => Key::MediaTrackNext, + winuser::VK_MEDIA_PREV_TRACK => Key::MediaTrackPrevious, + winuser::VK_MEDIA_STOP => Key::MediaStop, + winuser::VK_MEDIA_PLAY_PAUSE => Key::MediaPlayPause, + winuser::VK_LAUNCH_MAIL => Key::LaunchMail, + winuser::VK_LAUNCH_MEDIA_SELECT => Key::LaunchMediaPlayer, + winuser::VK_LAUNCH_APP1 => Key::LaunchApplication1, + winuser::VK_LAUNCH_APP2 => Key::LaunchApplication2, + + // This function only converts "non-printable" + winuser::VK_OEM_1 => Key::Unidentified(native_code), + winuser::VK_OEM_PLUS => Key::Unidentified(native_code), + winuser::VK_OEM_COMMA => Key::Unidentified(native_code), + winuser::VK_OEM_MINUS => Key::Unidentified(native_code), + winuser::VK_OEM_PERIOD => Key::Unidentified(native_code), + winuser::VK_OEM_2 => Key::Unidentified(native_code), + winuser::VK_OEM_3 => Key::Unidentified(native_code), + + winuser::VK_GAMEPAD_A => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_B => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_X => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_Y => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_SHOULDER => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_SHOULDER => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_TRIGGER => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_TRIGGER => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_DPAD_UP => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_DPAD_DOWN => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_DPAD_LEFT => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_DPAD_RIGHT => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_MENU => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_VIEW => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_THUMBSTICK_BUTTON => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_THUMBSTICK_UP => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_THUMBSTICK_DOWN => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_LEFT_THUMBSTICK_LEFT => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_THUMBSTICK_UP => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_THUMBSTICK_DOWN => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_THUMBSTICK_RIGHT => Key::Unidentified(native_code), + winuser::VK_GAMEPAD_RIGHT_THUMBSTICK_LEFT => Key::Unidentified(native_code), + + // This function only converts "non-printable" + winuser::VK_OEM_4 => Key::Unidentified(native_code), + winuser::VK_OEM_5 => Key::Unidentified(native_code), + winuser::VK_OEM_6 => Key::Unidentified(native_code), + winuser::VK_OEM_7 => Key::Unidentified(native_code), + winuser::VK_OEM_8 => Key::Unidentified(native_code), + winuser::VK_OEM_AX => Key::Unidentified(native_code), + winuser::VK_OEM_102 => Key::Unidentified(native_code), + + winuser::VK_ICO_HELP => Key::Unidentified(native_code), + winuser::VK_ICO_00 => Key::Unidentified(native_code), + + winuser::VK_PROCESSKEY => Key::Process, + + winuser::VK_ICO_CLEAR => Key::Unidentified(native_code), + winuser::VK_PACKET => Key::Unidentified(native_code), + winuser::VK_OEM_RESET => Key::Unidentified(native_code), + winuser::VK_OEM_JUMP => Key::Unidentified(native_code), + winuser::VK_OEM_PA1 => Key::Unidentified(native_code), + winuser::VK_OEM_PA2 => Key::Unidentified(native_code), + winuser::VK_OEM_PA3 => Key::Unidentified(native_code), + winuser::VK_OEM_WSCTRL => Key::Unidentified(native_code), + winuser::VK_OEM_CUSEL => Key::Unidentified(native_code), + + winuser::VK_OEM_ATTN => Key::Attn, + winuser::VK_OEM_FINISH => { + if is_japanese { + Key::Katakana + } else { + // This matches IE and Firefox behaviour according to + // https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values + // At the time of writing, there is no `Key::Finish` variant as + // Finish is not mentionned at https://w3c.github.io/uievents-key/ + // Also see: https://github.com/pyfisch/keyboard-types/issues/9 + Key::Unidentified(native_code) + } + } + winuser::VK_OEM_COPY => Key::Copy, + winuser::VK_OEM_AUTO => Key::Hankaku, + winuser::VK_OEM_ENLW => Key::Zenkaku, + winuser::VK_OEM_BACKTAB => Key::Romaji, + winuser::VK_ATTN => Key::KanaMode, + winuser::VK_CRSEL => Key::CrSel, + winuser::VK_EXSEL => Key::ExSel, + winuser::VK_EREOF => Key::EraseEof, + winuser::VK_PLAY => Key::Play, + winuser::VK_ZOOM => Key::ZoomToggle, + winuser::VK_NONAME => Key::Unidentified(native_code), + winuser::VK_PA1 => Key::Unidentified(native_code), + winuser::VK_OEM_CLEAR => Key::Clear, + _ => Key::Unidentified(native_code), + } +} diff --git a/src/platform_impl/windows/minimal_ime.rs b/src/platform_impl/windows/minimal_ime.rs new file mode 100644 index 0000000000..cbf754eecc --- /dev/null +++ b/src/platform_impl/windows/minimal_ime.rs @@ -0,0 +1,93 @@ +use std::mem::MaybeUninit; + +use winapi::{ + shared::{ + minwindef::{LPARAM, WPARAM}, + windef::HWND, + }, + um::winuser, +}; + +use crate::platform_impl::platform::event_loop::ProcResult; + +pub fn is_msg_ime_related(msg_kind: u32) -> bool { + match msg_kind { + winuser::WM_IME_COMPOSITION + | winuser::WM_IME_COMPOSITIONFULL + | winuser::WM_IME_STARTCOMPOSITION + | winuser::WM_IME_ENDCOMPOSITION + | winuser::WM_IME_CHAR + | winuser::WM_CHAR + | winuser::WM_SYSCHAR => true, + _ => false, + } +} + +pub struct MinimalIme { + // True if we're currently receiving messages belonging to a finished IME session. + getting_ime_text: bool, + + utf16parts: Vec, +} +impl Default for MinimalIme { + fn default() -> Self { + MinimalIme { + getting_ime_text: false, + utf16parts: Vec::with_capacity(16), + } + } +} +impl MinimalIme { + pub(crate) fn process_message( + &mut self, + hwnd: HWND, + msg_kind: u32, + wparam: WPARAM, + _lparam: LPARAM, + result: &mut ProcResult, + ) -> Option { + match msg_kind { + winuser::WM_IME_ENDCOMPOSITION => { + self.getting_ime_text = true; + } + winuser::WM_CHAR | winuser::WM_SYSCHAR => { + if self.getting_ime_text { + *result = ProcResult::Value(0); + self.utf16parts.push(wparam as u16); + + let more_char_coming; + unsafe { + let mut next_msg = MaybeUninit::uninit(); + let has_message = winuser::PeekMessageW( + next_msg.as_mut_ptr(), + hwnd, + winuser::WM_KEYFIRST, + winuser::WM_KEYLAST, + winuser::PM_NOREMOVE, + ); + let has_message = has_message != 0; + if !has_message { + more_char_coming = false; + } else { + let next_msg = next_msg.assume_init().message; + if next_msg == winuser::WM_CHAR || next_msg == winuser::WM_SYSCHAR { + more_char_coming = true; + } else { + more_char_coming = false; + } + } + } + if !more_char_coming { + let result = String::from_utf16(&self.utf16parts).ok(); + self.utf16parts.clear(); + self.getting_ime_text = false; + return result; + } + } + } + _ => (), + } + + None + } +} diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 07629e6dfb..a378070171 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -13,6 +13,7 @@ pub use self::icon::WinIcon as PlatformIcon; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; +use crate::keyboard::Key; use crate::window::Theme; #[derive(Clone)] @@ -82,6 +83,12 @@ fn wrap_device_id(id: u32) -> RootDeviceId { pub type OsError = std::io::Error; +#[derive(Debug, Clone, Eq, PartialEq, Hash)] +pub struct KeyEventExtra { + pub text_with_all_modifers: Option<&'static str>, + pub key_without_modifiers: Key<'static>, +} + #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct WindowId(HWND); unsafe impl Send for WindowId {} @@ -100,9 +107,11 @@ mod util; mod dark_mode; mod dpi; mod drop_handler; -mod event; mod event_loop; mod icon; +mod keyboard; +mod keyboard_layout; +mod minimal_ime; mod monitor; mod raw_input; mod window; diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 2e5e8e5727..1b03c4ffb4 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -1,5 +1,6 @@ #![cfg(target_os = "windows")] +use mem::MaybeUninit; use parking_lot::Mutex; use raw_window_handle::{windows::WindowsHandle, RawWindowHandle}; use std::{ @@ -685,6 +686,26 @@ impl Window { pub fn theme(&self) -> Theme { self.window_state.lock().current_theme } + + #[inline] + pub fn reset_dead_keys(&self) { + // `ToUnicode` consumes the dead-key by default, so we are constructing a fake (but valid) + // key input which we can call `ToUnicode` with. + unsafe { + let vk = winuser::VK_SPACE as u32; + let scancode = winuser::MapVirtualKeyW(vk, winuser::MAPVK_VK_TO_VSC); + let kbd_state = [0; 256]; + let mut char_buff = [MaybeUninit::uninit(); 8]; + winuser::ToUnicode( + vk, + scancode, + kbd_state.as_ptr(), + char_buff[0].as_mut_ptr(), + char_buff.len() as i32, + 0, + ); + } + } } impl Drop for Window { diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index 9e41bd7925..bca61f05e0 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -1,8 +1,10 @@ use crate::{ dpi::{PhysicalPosition, Size}, - event::ModifiersState, icon::Icon, - platform_impl::platform::{event_loop, util}, + keyboard::ModifiersState, + platform_impl::platform::{ + event_loop, keyboard::KeyEventBuilder, minimal_ime::MinimalIme, util, + }, window::{CursorIcon, Fullscreen, Theme, WindowAttributes}, }; use parking_lot::MutexGuard; @@ -34,6 +36,10 @@ pub struct WindowState { pub current_theme: Theme, pub preferred_theme: Option, pub high_surrogate: Option, + + pub key_event_builder: KeyEventBuilder, + pub ime_handler: MinimalIme, + pub window_flags: WindowFlags, } @@ -122,6 +128,8 @@ impl WindowState { current_theme, preferred_theme, high_surrogate: None, + key_event_builder: KeyEventBuilder::default(), + ime_handler: MinimalIme::default(), window_flags: WindowFlags::empty(), } } diff --git a/src/window.rs b/src/window.rs index 079c711240..b8c15b3a83 100644 --- a/src/window.rs +++ b/src/window.rs @@ -443,6 +443,22 @@ impl Window { pub fn request_redraw(&self) { self.window.request_redraw() } + + /// Reset the dead key state of the keyboard. + /// + /// This is useful when a dead key is bound to trigger an action. Then + /// this function can be called to reset the dead key state so that + /// follow-up text input won't be affected by the dead key. + /// + /// ## Platform-specific + /// - **Web:** Does nothing + // --------------------------- + // Developers' Note: If this cannot be implemented on every desktop platform + // at least, then this function should be provided through a platform specific + // extension trait + pub fn reset_dead_keys(&self) { + self.window.reset_dead_keys(); + } } /// Position and size functions. diff --git a/tests/serde_objects.rs b/tests/serde_objects.rs index ad729dcd1b..b0333fa410 100644 --- a/tests/serde_objects.rs +++ b/tests/serde_objects.rs @@ -3,10 +3,8 @@ use serde::{Deserialize, Serialize}; use winit::{ dpi::{LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize}, - event::{ - ElementState, KeyboardInput, ModifiersState, MouseButton, MouseScrollDelta, TouchPhase, - VirtualKeyCode, - }, + event::{ElementState, MouseButton, MouseScrollDelta, TouchPhase}, + keyboard::{Key, KeyCode, KeyLocation, ModifiersState}, window::CursorIcon, }; @@ -20,12 +18,13 @@ fn window_serde() { #[test] fn events_serde() { - needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); needs_serde::(); - needs_serde::(); + needs_serde::>(); + needs_serde::(); + needs_serde::(); needs_serde::(); }