diff --git a/examples/kbd_ev_print.rs b/examples/kbd_ev_print.rs new file mode 100644 index 0000000000..015814442a --- /dev/null +++ b/examples/kbd_ev_print.rs @@ -0,0 +1,253 @@ +//! Simple winit window example that prints keyboard events: +//! [KeyboardInput](https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html#variant.KeyboardInput) +//! [ModifiersChanged](https://docs.rs/winit/latest/winit/event/enum.WindowEvent.html#variant.ModifiersChanged).) + +use std::error::Error; + +use winit::application::ApplicationHandler; +use winit::event::WindowEvent; +use winit::event_loop::{ActiveEventLoop, EventLoop}; +#[cfg(web_platform)] +use winit::platform::web::WindowAttributesWeb; +use winit::window::{Window, WindowAttributes, WindowId}; + +#[path = "util/fill.rs"] +mod fill; +#[path = "util/tracing.rs"] +mod tracing; + +#[derive(Default, Debug)] +struct App { + window: Option>, +} + +use winit::event::{KeyEvent, Modifiers}; +// struct Modifiers +// state : ModifiersState, +// pressed_mods: ModifiersKeys , +// https://docs.rs/winit/latest/winit/keyboard/struct.ModifiersState.html +pub fn mod_state_side_agnostic_s(state: &ModifiersState) -> String { + let mut s = String::new(); + if state.contains(ModifiersState::SHIFT) { + s.push_str(" ⇧ ") + } else { + s.push_str(" ") + }; + if state.contains(ModifiersState::CONTROL) { + s.push_str(" ⎈ ") + } else { + s.push_str(" ") + }; + if state.contains(ModifiersState::META) { + s.push_str(" ◆ ") + } else { + s.push_str(" ") + }; + if state.contains(ModifiersState::ALT) { + s.push_str(" ⎇ ") + } else { + s.push_str(" ") + }; + s +} +// https://docs.rs/winit/latest/winit/event/struct.Modifiers.html +pub fn mod_state_side_aware_s(mods: &Modifiers) -> String { + let mut s = String::new(); + if let ModifiersKeyState::Pressed = mods.lshift_state() { + s.push_str("‹⇧"); + if let ModifiersKeyState::Pressed = mods.rshift_state() { + s.push('›') + } else { + s.push(' ') + }; + } else if let ModifiersKeyState::Pressed = mods.rshift_state() { + s.push_str(" ⇧›") + } else { + s.push_str(" ") + } + if let ModifiersKeyState::Pressed = mods.lcontrol_state() { + s.push_str("‹⎈"); + if let ModifiersKeyState::Pressed = mods.rcontrol_state() { + s.push('›') + } else { + s.push(' ') + }; + } else if let ModifiersKeyState::Pressed = mods.rcontrol_state() { + s.push_str(" ⎈›") + } else { + s.push_str(" ") + } + if let ModifiersKeyState::Pressed = mods.lsuper_state() { + s.push_str("‹◆"); + if let ModifiersKeyState::Pressed = mods.rsuper_state() { + s.push('›') + } else { + s.push(' ') + }; + } else if let ModifiersKeyState::Pressed = mods.rsuper_state() { + s.push_str(" ◆›") + } else { + s.push_str(" ") + } + if let ModifiersKeyState::Pressed = mods.lalt_state() { + s.push_str("‹⎇"); + if let ModifiersKeyState::Pressed = mods.ralt_state() { + s.push('›') + } else { + s.push(' ') + }; + } else if let ModifiersKeyState::Pressed = mods.ralt_state() { + s.push_str(" ⎇›") + } else { + s.push_str(" ") + } + s +} +// pub struct KeyEvent +// physical_key: PhysicalKey, enum PhysicalKey +// Code ( KeyCode) +// �Unidentified(NativeKeyCode) +// logical_key: Key, enum Key +// Named(NamedKey) +// Character(Str) +// �Unidentified(NativeKey) +// 🕱Dead(Option) +// text : Option +// location: KeyLocation, enum KeyLocation Standard,Left,Right,Numpad +// state : ElementState, pressed/released +//🔁repeat : bool +use winit::event::ElementState; +use winit::keyboard::{Key, KeyLocation, ModifiersKeyState, ModifiersState, PhysicalKey}; +pub fn ev_key_s(key: &KeyEvent) -> String { + let mut s = String::new(); + match &key.state { + ElementState::Pressed => s.push('↓'), + ElementState::Released => s.push('↑'), + } + if key.repeat { + s.push('🔁') + } else { + s.push(' ') + }; //𜱣⚛ + s.push(' '); + match &key.physical_key { + PhysicalKey::Code(key_code) => s.push_str(&format!("{:?}", key_code)), + PhysicalKey::Unidentified(key_code_native) => { + s.push_str(&format!("�{:?}", key_code_native)) + }, + }; + s.push(' '); + match &key.logical_key { + Key::Named(key_named) => s.push_str(&format!("{:?}", key_named)), + Key::Character(key_char) => s.push_str(&format!("{}", key_char)), + Key::Unidentified(key_native) => s.push_str(&format!("�{:?}", key_native)), + Key::Dead(maybe_char) => s.push_str(&format!("🕱{:?}", maybe_char)), + }; + s.push_str(" "); + if let Some(txt) = &key.text { + s.push_str(&format!("{}", txt)); + } else { + s.push(' '); + } + s.push(' '); + if let Some(txt) = &key.text_with_all_modifiers { + s.push_str(&format!("{}", txt)); + } else { + s.push(' '); + } + s.push(' '); + match &key.key_without_modifiers { + Key::Named(key_named) => s.push_str(&format!("{:?}", key_named)), + Key::Character(key_char) => s.push_str(&format!("{}", key_char)), + Key::Unidentified(key_native) => s.push_str(&format!("�{:?}", key_native)), + Key::Dead(maybe_char) => s.push_str(&format!("🕱{:?}", maybe_char)), + }; + s.push_str(" "); + match &key.location { + KeyLocation::Standard => s.push('≝'), + KeyLocation::Left => s.push('←'), + KeyLocation::Right => s.push('→'), + KeyLocation::Numpad => s.push('🔢'), + } + s +} + +impl ApplicationHandler for App { + fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { + #[cfg(not(web_platform))] + let window_attributes = WindowAttributes::default(); + #[cfg(web_platform)] + let window_attributes = WindowAttributes::default() + .with_platform_attributes(Box::new(WindowAttributesWeb::default().with_append(true))); + self.window = match event_loop.create_window(window_attributes) { + Ok(window) => Some(window), + Err(err) => { + eprintln!("error creating window: {err}"); + event_loop.exit(); + return; + }, + } + } + + fn window_event(&mut self, event_loop: &dyn ActiveEventLoop, _: WindowId, event: WindowEvent) { + match event { + WindowEvent::ModifiersChanged(mods) => { + let state = mods.state(); + let state_s = mod_state_side_agnostic_s(&state); + let pressed_mods_s = mod_state_side_aware_s(&mods); + println!("Δ {}\tside-agnostic\n {}\tside-aware", state_s, pressed_mods_s); + }, + WindowEvent::KeyboardInput { event, is_synthetic, .. } => { + let is_synthetic_s = if is_synthetic { "⚗" } else { " " }; + let key_event_s = ev_key_s(&event); + println!("🖮 {}{}", is_synthetic_s, key_event_s); + }, + WindowEvent::CloseRequested => { + event_loop.exit(); + }, + WindowEvent::SurfaceResized(_) => { + self.window.as_ref().expect("resize event without a window").request_redraw(); + }, + WindowEvent::RedrawRequested => { + // Redraw the application. + // + // It's preferable for applications that do not render continuously to render in + // this event rather than in AboutToWait, since rendering in here allows + // the program to gracefully handle redraws requested by the OS. + + let window = self.window.as_ref().expect("redraw request without a window"); + + // Notify that you're about to draw. + window.pre_present_notify(); + + // Draw. + fill::fill_window(window.as_ref()); + + // For contiguous redraw loop you can request a redraw from here. + // window.request_redraw(); + }, + _ => (), + } + } +} + +fn main() -> Result<(), Box> { + #[cfg(web_platform)] + console_error_panic_hook::set_once(); + + tracing::init(); + + let event_loop = EventLoop::new()?; + + println!( + "Δ is ModifiersChanged event, showing (line #1) side-agnostic modifier state as well as \ + (#2) side-aware one." + ); + println!("🖮 is KeyboardInput: ⚗=synthetic, ↓↑=pressed/released 🔁=repeat"); + println!(" phys logic txt +mod −mod location"); + + // For alternative loop run options see `pump_events` and `run_on_demand` examples. + event_loop.run_app(App::default())?; + + Ok(()) +} diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 67ea934912..3df56f7002 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -81,6 +81,7 @@ changelog entry. - `keyboard::ModifiersKey` to track which modifier is exactly pressed. - `ActivationToken::as_raw` to get a ref to raw token. - Each platform now has corresponding `WindowAttributes` struct instead of trait extension. +- On Windows, update side-aware `event::Modifiers` information on state change. ### Changed diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 6d0648174c..2e05e3c2bc 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -934,17 +934,24 @@ fn update_modifiers(window: HWND, userdata: &WindowData) { let modifiers = { let mut layouts = LAYOUT_CACHE.lock().unwrap(); - layouts.get_agnostic_mods() + layouts.get_mods() }; + let mut send_event = false; let mut window_state = userdata.window_state.lock().unwrap(); - if window_state.modifiers_state != modifiers { - window_state.modifiers_state = modifiers; - + if window_state.modifiers_keys != modifiers.pressed_mods() { + window_state.modifiers_keys = modifiers.pressed_mods(); + send_event = true; + } + if window_state.modifiers_state != modifiers.state() { + window_state.modifiers_state = modifiers.state(); + send_event = true; + } + if send_event { // Drop lock drop(window_state); - userdata.send_window_event(window, ModifiersChanged(modifiers.into())); + userdata.send_window_event(window, ModifiersChanged(modifiers)); } } diff --git a/src/platform_impl/windows/keyboard_layout.rs b/src/platform_impl/windows/keyboard_layout.rs index a31362f386..b929d34c6b 100644 --- a/src/platform_impl/windows/keyboard_layout.rs +++ b/src/platform_impl/windows/keyboard_layout.rs @@ -40,7 +40,10 @@ use windows_sys::Win32::UI::Input::KeyboardAndMouse::{ VK_SCROLL, VK_SELECT, VK_SEPARATOR, VK_SHIFT, VK_SLEEP, VK_SNAPSHOT, VK_SPACE, VK_SUBTRACT, VK_TAB, VK_UP, VK_VOLUME_DOWN, VK_VOLUME_MUTE, VK_VOLUME_UP, VK_XBUTTON1, VK_XBUTTON2, VK_ZOOM, }; -use winit_core::keyboard::{Key, KeyCode, ModifiersState, NamedKey, NativeKey, PhysicalKey}; +use winit_core::event::Modifiers; +use winit_core::keyboard::{ + Key, KeyCode, ModifiersKeys, ModifiersState, NamedKey, NativeKey, PhysicalKey, +}; use crate::platform_impl::{loword, primarylangid, scancode_to_physicalkey}; @@ -271,15 +274,46 @@ impl LayoutCache { } } - pub fn get_agnostic_mods(&mut self) -> ModifiersState { + pub fn get_mods(&mut self) -> Modifiers { let (_, layout) = self.get_current_layout(); let filter_out_altgr = layout.has_alt_graph && key_pressed(VK_RMENU); - let mut mods = ModifiersState::empty(); - mods.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT)); - mods.set(ModifiersState::CONTROL, key_pressed(VK_CONTROL) && !filter_out_altgr); - mods.set(ModifiersState::ALT, key_pressed(VK_MENU) && !filter_out_altgr); - mods.set(ModifiersState::META, key_pressed(VK_LWIN) || key_pressed(VK_RWIN)); - mods + let mut state = ModifiersState::empty(); + let mut pressed_mods = ModifiersKeys::empty(); + + state.set(ModifiersState::SHIFT, key_pressed(VK_SHIFT)); + pressed_mods.set( + ModifiersKeys::LSHIFT, + state.contains(ModifiersState::SHIFT) && key_pressed(VK_LSHIFT), + ); + pressed_mods.set( + ModifiersKeys::RSHIFT, + state.contains(ModifiersState::SHIFT) && key_pressed(VK_RSHIFT), + ); + + pressed_mods.set(ModifiersKeys::LCONTROL, key_pressed(VK_LCONTROL) && !filter_out_altgr); + pressed_mods.set(ModifiersKeys::RCONTROL, key_pressed(VK_RCONTROL) && !filter_out_altgr); + state.set( + ModifiersState::CONTROL, + pressed_mods.contains(ModifiersKeys::LCONTROL) + || pressed_mods.contains(ModifiersKeys::RCONTROL), + ); + + pressed_mods.set(ModifiersKeys::LALT, key_pressed(VK_LMENU) && !filter_out_altgr); + pressed_mods.set(ModifiersKeys::RALT, key_pressed(VK_RMENU) && !filter_out_altgr); + state.set( + ModifiersState::ALT, + pressed_mods.contains(ModifiersKeys::LALT) + || pressed_mods.contains(ModifiersKeys::RALT), + ); + + pressed_mods.set(ModifiersKeys::LMETA, key_pressed(VK_LWIN)); + pressed_mods.set(ModifiersKeys::RMETA, key_pressed(VK_RWIN)); + state.set( + ModifiersState::META, + pressed_mods.contains(ModifiersKeys::LMETA) + || pressed_mods.contains(ModifiersKeys::RMETA), + ); + Modifiers::new(state, pressed_mods) } fn prepare_layout(locale_id: u64) -> Layout { diff --git a/src/platform_impl/windows/window_state.rs b/src/platform_impl/windows/window_state.rs index a0ca3a539e..52ad4f8c59 100644 --- a/src/platform_impl/windows/window_state.rs +++ b/src/platform_impl/windows/window_state.rs @@ -17,7 +17,7 @@ use windows_sys::Win32::UI::WindowsAndMessaging::{ WS_MINIMIZEBOX, WS_OVERLAPPEDWINDOW, WS_POPUP, WS_SIZEBOX, WS_SYSMENU, WS_VISIBLE, }; use winit_core::icon::Icon; -use winit_core::keyboard::ModifiersState; +use winit_core::keyboard::{ModifiersKeys, ModifiersState}; use winit_core::monitor::Fullscreen; use winit_core::window::{Theme, WindowAttributes}; @@ -41,6 +41,7 @@ pub(crate) struct WindowState { pub scale_factor: f64, pub modifiers_state: ModifiersState, + pub modifiers_keys: ModifiersKeys, pub fullscreen: Option, pub current_theme: Theme, pub preferred_theme: Option, @@ -172,6 +173,7 @@ impl WindowState { scale_factor, modifiers_state: ModifiersState::default(), + modifiers_keys: ModifiersKeys::default(), fullscreen: None, current_theme, preferred_theme, diff --git a/winit-core/src/event.rs b/winit-core/src/event.rs index 416723749c..68de2f63bb 100644 --- a/winit-core/src/event.rs +++ b/winit-core/src/event.rs @@ -825,6 +825,11 @@ impl Modifiers { self.state } + /// The logical state of the modifier keys. + pub fn pressed_mods(&self) -> ModifiersKeys { + self.pressed_mods + } + /// The logical state of the left shift key. pub fn lshift_state(&self) -> ModifiersKeyState { self.mod_state(ModifiersKeys::LSHIFT)