Skip to content
Open
253 changes: 253 additions & 0 deletions examples/kbd_ev_print.rs
Original file line number Diff line number Diff line change
@@ -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<Box<dyn Window>>,
}

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<Str = SmolStr>
// Named(NamedKey)
// Character(Str)
// �Unidentified(NativeKey)
// 🕱Dead(Option<char>)
// text : Option<SmolStr>
// 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<dyn Error>> {
#[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(())
}
1 change: 1 addition & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 12 additions & 5 deletions src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
}
}

Expand Down
50 changes: 42 additions & 8 deletions src/platform_impl/windows/keyboard_layout.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 3 additions & 1 deletion src/platform_impl/windows/window_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -41,6 +41,7 @@ pub(crate) struct WindowState {
pub scale_factor: f64,

pub modifiers_state: ModifiersState,
pub modifiers_keys: ModifiersKeys,
pub fullscreen: Option<Fullscreen>,
pub current_theme: Theme,
pub preferred_theme: Option<Theme>,
Expand Down Expand Up @@ -172,6 +173,7 @@ impl WindowState {
scale_factor,

modifiers_state: ModifiersState::default(),
modifiers_keys: ModifiersKeys::default(),
fullscreen: None,
current_theme,
preferred_theme,
Expand Down
5 changes: 5 additions & 0 deletions winit-core/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down