Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
259 changes: 259 additions & 0 deletions examples/kbd_ev_print.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,259 @@
//! 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(" ")
};
if state.contains(ModifiersState::ALT_GRAPH) {
s.push_str("⎇Gr")
} 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.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(())
}
2 changes: 2 additions & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,8 @@ 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.
- On Windows, added <kbd>AltGr</kbd> as a separate modifier (though currently <kbd>AltGr</kbd>+<kbd>LCtrl</kbd> can't be differentiated from just <kbd>AltGr</kbd>).

### Changed

Expand Down
20 changes: 15 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 Expand Up @@ -1040,6 +1047,9 @@ unsafe fn public_window_callback_inner(
let mut result = ProcResult::DefWindowProc(wparam);

// Send new modifiers before sending key events.
// NOTE: Some modifier key presses are not reported as KeyDown/Up events when the same
// alternative side modifier is being held, e.g., in a sequence of ↓LShift ↓RShift ↑RShift the
// last event is not reported.
let mods_changed_callback = || match msg {
WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP => {
update_modifiers(window, userdata);
Expand Down
59 changes: 50 additions & 9 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,53 @@ 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::LALT, key_pressed(VK_LMENU));
let is_ralt = key_pressed(VK_RMENU);
let is_altgr = layout.has_alt_graph && is_ralt;
pressed_mods.set(ModifiersKeys::RALT, is_ralt && !is_altgr);
state.set(
ModifiersState::ALT,
pressed_mods.contains(ModifiersKeys::LALT)
|| pressed_mods.contains(ModifiersKeys::RALT),
);
state.set(ModifiersState::ALT_GRAPH, is_altgr);

// On Windows AltGr = RAlt + LCtrl, and OS sends artificial LCtrl key event, which needs to
// be filtered out without touching "real" LCtrl events to allow separate bindings of
// LCtrl+AltGr+X and AltGr+X. TODO: this is likely only possible by tracking the
// physical LCtrl state via raw keyboard events as the message loop isn't capable of
// excluding artificial LCtrl events?
pressed_mods.set(ModifiersKeys::RCONTROL, key_pressed(VK_RCONTROL));
pressed_mods.set(ModifiersKeys::LCONTROL, key_pressed(VK_LCONTROL) && !is_altgr);
state.set(
ModifiersState::CONTROL,
pressed_mods.contains(ModifiersKeys::LCONTROL)
|| pressed_mods.contains(ModifiersKeys::RCONTROL),
);

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
Loading