Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
28ff731
keyevent: add side-aware modifier state on Windows
eugenesvk May 18, 2025
a5629ee
add kbd_ev_print example
eugenesvk May 19, 2025
ae3c8da
update example
eugenesvk May 19, 2025
729f04b
fmt example
eugenesvk May 19, 2025
57fdf68
fmt
eugenesvk May 19, 2025
7f9788a
Windows: update side-aware `event::Modifiers` information on state ch…
eugenesvk May 19, 2025
19d9ce0
clippy
eugenesvk May 19, 2025
f54ff88
don't quit on Escape
eugenesvk May 19, 2025
d6c3825
add more info to examples
eugenesvk May 19, 2025
8b891ba
update changelog
eugenesvk May 20, 2025
2ce6f6e
undo removing into
eugenesvk May 20, 2025
e4139a6
clippy
eugenesvk May 20, 2025
db353a6
save 1 keystate call for Shift
eugenesvk May 20, 2025
96feb00
add side-aware keys comparison on mod update
eugenesvk May 20, 2025
6a3ab48
add AltGr to the example
eugenesvk May 19, 2025
5c96dfc
add ALTGR modifier flag
eugenesvk May 19, 2025
8aecc93
add ALTGR to modifiersstate
eugenesvk May 19, 2025
4fdc15e
comment on modifier change limitations
eugenesvk May 19, 2025
1b18f39
update changelog
eugenesvk May 19, 2025
3381e4a
rename AltGr to AltGraph
eugenesvk May 20, 2025
50773da
add lock keys to the example
eugenesvk May 20, 2025
2658fa7
add lock keys to the enum
eugenesvk May 20, 2025
c26c919
add lock keys to setting Modifiers
eugenesvk May 20, 2025
834cc27
clippy
eugenesvk May 20, 2025
cc9bbfa
add all lock keys and other missing modifiers to the enum
eugenesvk May 21, 2025
69a5e13
add all lock keys and other missing modifiers to example
eugenesvk May 21, 2025
6bf241e
win: all lock keys and other missing modifiers to setting Modifiers
eugenesvk May 21, 2025
aa6a494
clippy
eugenesvk May 21, 2025
293a6ac
add helper methods for checking modifier state
eugenesvk May 21, 2025
f24f970
update example
eugenesvk May 21, 2025
37224d2
update changelog
eugenesvk May 20, 2025
eb026a0
convert ModifiersState into a single side-aware Modifiers struct
eugenesvk May 22, 2025
8f00e80
update Modifiers helper methods to include either/L/R state check met…
eugenesvk May 22, 2025
217e7ec
delete redundant ModifiersKeyState
eugenesvk May 23, 2025
5be441d
removed dupe Modifiers from event
eugenesvk May 22, 2025
d6191da
win: dedupe modifier state storage in Window state
eugenesvk May 22, 2025
fbf65aa
win: dedupe modifier state check
eugenesvk May 22, 2025
a0f7269
win: dedupe modifier state check in keyboard layout
eugenesvk May 22, 2025
9b5c85b
win: dedupe modifier state check in event loop
eugenesvk May 22, 2025
0ad163b
example: update kbd_ev_print
eugenesvk May 22, 2025
c7a332c
example: update application
eugenesvk May 22, 2025
b254daa
fmt
eugenesvk May 22, 2025
7852006
changelog: update unreleased
eugenesvk May 22, 2025
649de6d
__todo: add extra side-agnostic modifiers for common keybinding keys
eugenesvk May 22, 2025
11f1cf0
__todo (win): set extra side-agnostic modifiers for common keybinding…
eugenesvk May 22, 2025
bdb421e
appkit: dedupe modifiers
eugenesvk May 22, 2025
fc59101
orbital: dedupe modifiers
eugenesvk May 22, 2025
79f84e1
linux: use deduped modifiers API
eugenesvk May 22, 2025
005d53e
wayland: use deduped Modifiers
eugenesvk May 22, 2025
2f4a8a7
win: add TODO
eugenesvk May 22, 2025
eb08d47
web: use the deduped Modifiers
eugenesvk May 22, 2025
e3ec3a7
test: use deduped Modifiers
eugenesvk May 22, 2025
e2ac8d0
x11: use deduped Modifiers
eugenesvk May 22, 2025
8a6435d
clippy
eugenesvk May 22, 2025
e1e9fe3
add helper constants/methods for matching common shortcut modifiers
eugenesvk May 23, 2025
7593dd7
fix mod match logic in examples
eugenesvk May 23, 2025
d31fb5e
clippy
eugenesvk May 23, 2025
f184743
changelog: update unreleased
eugenesvk May 23, 2025
b41eb08
implement Display for Modifiers
eugenesvk May 23, 2025
9990876
upd kdb_ev_print example to use Display formatting
eugenesvk May 23, 2025
624361f
change ctrl symbols to avoid similar symbols with different semantics
eugenesvk May 24, 2025
fb4b01c
add Primary modifier: Ctrl on PC/Linux, Cmd on Mac
eugenesvk May 24, 2025
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
99 changes: 47 additions & 52 deletions examples/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use winit::error::RequestError;
use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, WindowEvent};
use winit::event_loop::{ActiveEventLoop, EventLoop};
use winit::icon::{Icon, RgbaIcon};
use winit::keyboard::{Key, ModifiersState};
use winit::keyboard::Key;
use winit::monitor::Fullscreen;
#[cfg(macos_platform)]
use winit::platform::macos::{OptionAsAlt, WindowAttributesMacOS, WindowExtMacOS};
Expand All @@ -36,6 +36,7 @@ use winit::platform::web::{ActiveEventLoopExtWeb, WindowAttributesWeb};
use winit::platform::x11::{ActiveEventLoopExtX11, WindowAttributesX11};
use winit::window::{CursorGrabMode, ResizeDirection, Theme, Window, WindowAttributes, WindowId};
use winit_core::application::macos::ApplicationHandlerExtMacOS;
use winit_core::keyboard::Modifiers;

#[path = "util/tracing.rs"]
mod tracing;
Expand Down Expand Up @@ -358,14 +359,14 @@ impl Application {
}

/// Process the key binding.
fn process_key_binding(key: &str, mods: &ModifiersState) -> Option<Action> {
fn process_key_binding(key: &str, mods: &Modifiers) -> Option<Action> {
KEY_BINDINGS
.iter()
.find_map(|binding| binding.is_triggered_by(&key, mods).then_some(binding.action))
}

/// Process mouse binding.
fn process_mouse_binding(button: MouseButton, mods: &ModifiersState) -> Option<Action> {
fn process_mouse_binding(button: MouseButton, mods: &Modifiers) -> Option<Action> {
MOUSE_BINDINGS
.iter()
.find_map(|binding| binding.is_triggered_by(&button, mods).then_some(binding.action))
Expand Down Expand Up @@ -447,7 +448,7 @@ impl ApplicationHandler for Application {
self.windows.remove(&window_id);
},
WindowEvent::ModifiersChanged(modifiers) => {
window.modifiers = modifiers.state();
window.modifiers = modifiers;
info!("Modifiers changed to {:?}", window.modifiers);
},
WindowEvent::MouseWheel { delta, .. } => match delta {
Expand Down Expand Up @@ -621,7 +622,7 @@ struct WindowState {
/// Cursor position over the window.
cursor_position: Option<PhysicalPosition<f64>>,
/// Window modifiers state.
modifiers: ModifiersState,
modifiers: Modifiers,
/// Occlusion state of the window.
occluded: bool,
/// Current cursor grab mode.
Expand Down Expand Up @@ -1002,17 +1003,19 @@ impl WindowState {

struct Binding<T: Eq> {
trigger: T,
mods: ModifiersState,
mods: Modifiers,
action: Action,
}

impl<T: Eq> Binding<T> {
const fn new(trigger: T, mods: ModifiersState, action: Action) -> Self {
const fn new(trigger: T, mods: Modifiers, action: Action) -> Self {
Self { trigger, mods, action }
}

fn is_triggered_by(&self, trigger: &T, mods: &ModifiersState) -> bool {
&self.trigger == trigger && &self.mods == mods
fn is_triggered_by(&self, trigger: &T, mods: &Modifiers) -> bool {
// only consider common shortcut-related mods Shift, Alt, Meta, Control (side-agnostic)
// ignore mismatch in app's Shift+F and actual Shift+NumLock+F
&self.trigger == trigger && self.mods.shortcut_match(mods)
}
}

Expand Down Expand Up @@ -1141,12 +1144,12 @@ fn load_icon(bytes: &[u8]) -> Icon {
RgbaIcon::new(icon_rgba, icon_width, icon_height).expect("Failed to open icon").into()
}

fn modifiers_to_string(mods: ModifiersState) -> String {
fn modifiers_to_string(mods: Modifiers) -> String {
let mut mods_line = String::new();
// Always add + since it's printed as a part of the bindings.
for (modifier, desc) in [
(
ModifiersState::META,
Modifiers::META,
if cfg!(target_os = "windows") {
"Win+"
} else if cfg!(target_vendor = "apple") {
Expand All @@ -1155,9 +1158,9 @@ fn modifiers_to_string(mods: ModifiersState) -> String {
"Super+"
},
),
(ModifiersState::ALT, "Alt+"),
(ModifiersState::CONTROL, "Ctrl+"),
(ModifiersState::SHIFT, "Shift+"),
(Modifiers::ALT, "Alt+"),
(Modifiers::CONTROL, "Ctrl+"),
(Modifiers::SHIFT, "Shift+"),
] {
if !mods.contains(modifier) {
continue;
Expand Down Expand Up @@ -1272,54 +1275,46 @@ const CURSORS: &[CursorIcon] = &[
];

const KEY_BINDINGS: &[Binding<&'static str>] = &[
Binding::new("Q", ModifiersState::CONTROL, Action::CloseWindow),
Binding::new("H", ModifiersState::CONTROL, Action::PrintHelp),
Binding::new("F", ModifiersState::SHIFT, Action::ToggleAnimatedFillColor),
Binding::new("F", ModifiersState::CONTROL, Action::ToggleFullscreen),
Binding::new("Q", Modifiers::CONTROL, Action::CloseWindow),
Binding::new("H", Modifiers::CONTROL, Action::PrintHelp),
Binding::new("F", Modifiers::SHIFT, Action::ToggleAnimatedFillColor),
Binding::new("F", Modifiers::CONTROL, Action::ToggleFullscreen),
#[cfg(macos_platform)]
Binding::new("F", ModifiersState::ALT, Action::ToggleSimpleFullscreen),
Binding::new("D", ModifiersState::CONTROL, Action::ToggleDecorations),
Binding::new("I", ModifiersState::CONTROL, Action::ToggleImeInput),
Binding::new("L", ModifiersState::CONTROL, Action::CycleCursorGrab),
Binding::new("P", ModifiersState::CONTROL, Action::ToggleResizeIncrements),
Binding::new("R", ModifiersState::CONTROL, Action::ToggleResizable),
Binding::new("R", ModifiersState::ALT, Action::RequestResize),
Binding::new("R", ModifiersState::SHIFT, Action::ToggleContinuousRedraw),
Binding::new("F", Modifiers::ALT, Action::ToggleSimpleFullscreen),
Binding::new("D", Modifiers::CONTROL, Action::ToggleDecorations),
Binding::new("I", Modifiers::CONTROL, Action::ToggleImeInput),
Binding::new("L", Modifiers::CONTROL, Action::CycleCursorGrab),
Binding::new("P", Modifiers::CONTROL, Action::ToggleResizeIncrements),
Binding::new("R", Modifiers::CONTROL, Action::ToggleResizable),
Binding::new("R", Modifiers::ALT, Action::RequestResize),
Binding::new("R", Modifiers::SHIFT, Action::ToggleContinuousRedraw),
// M.
Binding::new("M", ModifiersState::CONTROL.union(ModifiersState::ALT), Action::DumpMonitors),
Binding::new("M", ModifiersState::CONTROL, Action::ToggleMaximize),
Binding::new("M", ModifiersState::ALT, Action::Minimize),
Binding::new("M", Modifiers::CONTROL.union(Modifiers::ALT), Action::DumpMonitors),
Binding::new("M", Modifiers::CONTROL, Action::ToggleMaximize),
Binding::new("M", Modifiers::ALT, Action::Minimize),
// N.
Binding::new("N", ModifiersState::CONTROL, Action::CreateNewWindow),
Binding::new("N", Modifiers::CONTROL, Action::CreateNewWindow),
// C.
Binding::new("C", ModifiersState::CONTROL, Action::NextCursor),
Binding::new("C", ModifiersState::ALT, Action::NextCustomCursor),
Binding::new("C", Modifiers::CONTROL, Action::NextCursor),
Binding::new("C", Modifiers::ALT, Action::NextCustomCursor),
#[cfg(web_platform)]
Binding::new(
"C",
ModifiersState::CONTROL.union(ModifiersState::SHIFT),
Action::UrlCustomCursor,
),
Binding::new("C", Modifiers::CONTROL.union(Modifiers::SHIFT), Action::UrlCustomCursor),
#[cfg(web_platform)]
Binding::new(
"C",
ModifiersState::ALT.union(ModifiersState::SHIFT),
Action::AnimationCustomCursor,
),
Binding::new("Z", ModifiersState::CONTROL, Action::ToggleCursorVisibility),
Binding::new("C", Modifiers::ALT.union(Modifiers::SHIFT), Action::AnimationCustomCursor),
Binding::new("Z", Modifiers::CONTROL, Action::ToggleCursorVisibility),
// K.
Binding::new("K", ModifiersState::empty(), Action::SetTheme(None)),
Binding::new("K", ModifiersState::META, Action::SetTheme(Some(Theme::Light))),
Binding::new("K", ModifiersState::CONTROL, Action::SetTheme(Some(Theme::Dark))),
Binding::new("K", Modifiers::empty(), Action::SetTheme(None)),
Binding::new("K", Modifiers::META, Action::SetTheme(Some(Theme::Light))),
Binding::new("K", Modifiers::CONTROL, Action::SetTheme(Some(Theme::Dark))),
#[cfg(macos_platform)]
Binding::new("T", ModifiersState::META, Action::CreateNewTab),
Binding::new("T", Modifiers::META, Action::CreateNewTab),
#[cfg(macos_platform)]
Binding::new("O", ModifiersState::CONTROL, Action::CycleOptionAsAlt),
Binding::new("S", ModifiersState::CONTROL, Action::Message),
Binding::new("O", Modifiers::CONTROL, Action::CycleOptionAsAlt),
Binding::new("S", Modifiers::CONTROL, Action::Message),
];

const MOUSE_BINDINGS: &[Binding<MouseButton>] = &[
Binding::new(MouseButton::Left, ModifiersState::ALT, Action::DragResizeWindow),
Binding::new(MouseButton::Left, ModifiersState::CONTROL, Action::DragWindow),
Binding::new(MouseButton::Right, ModifiersState::CONTROL, Action::ShowWindowMenu),
Binding::new(MouseButton::Left, Modifiers::ALT, Action::DragResizeWindow),
Binding::new(MouseButton::Left, Modifiers::CONTROL, Action::DragWindow),
Binding::new(MouseButton::Right, Modifiers::CONTROL, Action::ShowWindowMenu),
];
170 changes: 170 additions & 0 deletions examples/kbd_ev_print.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
//! 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>>,
}

// https://docs.rs/winit/latest/winit/event/struct.Modifiers.html
// 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, KeyEvent};
use winit::keyboard::{Key, KeyLocation, 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) => {
println!("Δ {mods:#}\tmodifier state");
},
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.\n ⇧ Shift ⎈ Control ◆ Meta ⎇ Alt ⎇Gr AltGraph ⇪ CapsLock ⇭ \
NumLock ⇳🔒 ScrollLock\n ƒ Fn ƒ🔒 FnLock カナ🔒 KanaLock ‹👍 Loya 👍› Roya 🔣 \
Symbol 🔣🔒 SymbolLock\n🖮 is KeyboardInput: ⚗ synthetic, ↓↑ pressed/unknown, 🔁 \
repeat\n 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(())
}
7 changes: 7 additions & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@ 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.
- Added support for using <kbd>AltGr</kbd>, <kbd>CapsLock</kbd>,<kbd>NumLock</kbd>, <kbd>ScrollLock</kbd>, <kbd>Fn</kbd>, <kbd>FnLock</kbd>, <kbd>KanaLock</kbd>, <kbd>Loya</kbd>, <kbd>Roya</kbd>, <kbd>Symbol</kbd>, <kbd>SymbolLock</kbd> as separate modifiers.
- 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>).
- On Windows, added <kbd>CapsLock</kbd>,<kbd>NumLock</kbd>, <kbd>ScrollLock</kbd>, <kbd>KanaLock</kbd>, <kbd>Loya</kbd>, <kbd>Roya</kbd> as separate modifiers.

### Changed

Expand Down Expand Up @@ -201,6 +205,8 @@ changelog entry.
- Move `IconExtWindows` into `WinIcon`.
- Move `EventLoopExtPumpEvents` and `PumpStatus` from platform module to `winit::event_loop::pump_events`.
- Move `EventLoopExtRunOnDemand` from platform module to `winit::event_loop::run_on_demand`.
- Replaced `winit::keyboard::ModifiersState` with the new `winit_core::keyboard::Modifiers`.
- Implement `Display` for `winit_core::keyboard::Modifiers` to print ‹⇧⎇ modifier combos, including an `:#` alternate notation to preserve modifier symbol positioning (for vertically-aligned formatting).

### Removed

Expand Down Expand Up @@ -240,6 +246,7 @@ changelog entry.
- Remove `NamedKey::Space`, match on `Key::Character(" ")` instead.
- Remove `PartialEq` impl for `WindowAttributes`.
- `WindowAttributesExt*` platform extensions; use `WindowAttributes*` instead.
- Remove `winit::keyboard::ModifiersKeyState`, replaced with the new `winit_core::keyboard::Modifiers` containing the same information.

### Fixed

Expand Down
Loading