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
34 changes: 32 additions & 2 deletions winit-appkit/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -307,8 +307,7 @@ pub(super) fn ralt_pressed(event: &NSEvent) -> bool {
event.modifierFlags().contains(NX_DEVICERALTKEYMASK)
}

pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
let flags = event.modifierFlags();
pub(super) fn mods_from_flags(flags: NSEventModifierFlags) -> Modifiers {
let mut state = ModifiersState::empty();
let mut pressed_mods = ModifiersKeys::empty();

Expand All @@ -331,6 +330,37 @@ pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
Modifiers::new(state, pressed_mods)
}

pub(super) fn event_mods(event: &NSEvent) -> Modifiers {
mods_from_flags(event.modifierFlags())
}

/// For each modifier key, returns `(logical_key, left_held, right_held)`
/// based on the device-specific bits in `flags`.
pub(super) fn per_modifier_held(flags: NSEventModifierFlags) -> [(Key, bool, bool); 4] {
[
(
Key::Named(NamedKey::Shift),
flags.contains(NX_DEVICELSHIFTKEYMASK),
flags.contains(NX_DEVICERSHIFTKEYMASK),
),
(
Key::Named(NamedKey::Control),
flags.contains(NX_DEVICELCTLKEYMASK),
flags.contains(NX_DEVICERCTLKEYMASK),
),
(
Key::Named(NamedKey::Alt),
flags.contains(NX_DEVICELALTKEYMASK),
flags.contains(NX_DEVICERALTKEYMASK),
),
(
Key::Named(NamedKey::Meta),
flags.contains(NX_DEVICELCMDKEYMASK),
flags.contains(NX_DEVICERCMDKEYMASK),
),
]
}

pub(super) fn dummy_event() -> Option<Retained<NSEvent>> {
NSEvent::otherEventWithType_location_modifierFlags_timestamp_windowNumber_context_subtype_data1_data2(
NSEventType::ApplicationDefined,
Expand Down
9 changes: 9 additions & 0 deletions winit-appkit/src/ffi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,17 @@ unsafe extern "C" {
pub fn CGDisplayGetDisplayIDFromUUID(uuid: &CFUUID) -> CGDirectDisplayID;
}

pub type CGEventSourceStateID = u32;
pub type CGEventFlags = u64;

/// Combined session state: union of all event sources in the user session.
pub const kCGEventSourceStateCombinedSessionState: CGEventSourceStateID = 0;

#[link(name = "CoreGraphics", kind = "framework")]
unsafe extern "C" {
/// Returns the current modifier flags for the given event source.
pub fn CGEventSourceFlagsState(stateID: CGEventSourceStateID) -> CGEventFlags;

// Wildly used private APIs; Apple uses them for their Terminal.app.
pub fn CGSMainConnectionID() -> *mut AnyObject;
pub fn CGSSetWindowBackgroundBlurRadius(
Expand Down
121 changes: 117 additions & 4 deletions winit-appkit/src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ use winit_core::window::ImeCapabilities;
use super::app_state::AppState;
use super::cursor::{default_cursor, invisible_cursor};
use super::event::{
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
scancode_to_physicalkey,
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, mods_from_flags,
per_modifier_held, ralt_pressed, scancode_to_physicalkey,
};
use super::window::window_id;
use crate::OptionAsAlt;
Expand Down Expand Up @@ -109,6 +109,32 @@ fn get_left_modifier_code(key: &Key) -> KeyCode {
}
}

fn synthetic_modifier_key_event(
logical_key: &Key,
location: KeyLocation,
state: ElementState,
) -> WindowEvent {
let physical_key = match location {
KeyLocation::Left => get_left_modifier_code(logical_key),
KeyLocation::Right => get_right_modifier_code(logical_key),
_ => unreachable!(),
};
WindowEvent::KeyboardInput {
device_id: None,
event: KeyEvent {
physical_key: physical_key.into(),
logical_key: logical_key.clone(),
text: None,
location,
state,
repeat: false,
text_with_all_modifiers: None,
key_without_modifiers: logical_key.clone(),
},
is_synthetic: true,
}
}

#[derive(Debug)]
pub struct ViewState {
/// Strong reference to the global application state.
Expand Down Expand Up @@ -931,14 +957,101 @@ impl WinitView {
input_context.invalidateCharacterCoordinates();
}

/// Reset modifiers and emit a synthetic ModifiersChanged event if deemed necessary.
pub(super) fn reset_modifiers(&self) {
/// Emit synthetic key-release events for all tracked modifier keys,
/// then clear tracking state and modifiers. Called on focus loss.
pub(super) fn synthesize_modifier_key_releases(&self) {
let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();

for (logical_key, location_mask) in phys_mod_state.drain() {
if location_mask.contains(ModLocationMask::LEFT) {
self.queue_event(synthetic_modifier_key_event(
&logical_key,
KeyLocation::Left,
ElementState::Released,
));
}
if location_mask.contains(ModLocationMask::RIGHT) {
self.queue_event(synthetic_modifier_key_event(
&logical_key,
KeyLocation::Right,
ElementState::Released,
));
}
}

if !self.ivars().modifiers.get().state().is_empty() {
self.ivars().modifiers.set(Modifiers::default());
self.queue_event(WindowEvent::ModifiersChanged(self.ivars().modifiers.get()));
}
}

/// Query hardware modifier state via `CGEventSourceFlagsState` and
/// emit synthetic key events + `ModifiersChanged` for any differences
/// against `phys_modifiers`. Called on focus gain.
pub(super) fn synchronize_modifiers(&self) {
use objc2_app_kit::NSEventModifierFlags;

use super::ffi::{
CGEventFlags, CGEventSourceFlagsState, kCGEventSourceStateCombinedSessionState,
};

// CGEventFlags and NSEventModifierFlags share the IOHIDFamily
// NX_DEVICE* bit layout. See IOLLEvent.h.
const _: () = assert!(
size_of::<CGEventFlags>() <= size_of::<usize>(),
"CGEventFlags must fit in NSEventModifierFlags (NSUInteger)",
);

let cg_flags = unsafe { CGEventSourceFlagsState(kCGEventSourceStateCombinedSessionState) };
let flags = NSEventModifierFlags(cg_flags as usize);

let mut phys_mod_state = self.ivars().phys_modifiers.borrow_mut();

for (logical_key, left_held, right_held) in per_modifier_held(flags) {
let old = phys_mod_state.get(&logical_key).copied().unwrap_or(ModLocationMask::empty());

let mut new_mask = ModLocationMask::empty();

if left_held != old.contains(ModLocationMask::LEFT) {
let state = if left_held { ElementState::Pressed } else { ElementState::Released };
self.queue_event(synthetic_modifier_key_event(
&logical_key,
KeyLocation::Left,
state,
));
}
if left_held {
new_mask |= ModLocationMask::LEFT;
}

if right_held != old.contains(ModLocationMask::RIGHT) {
let state = if right_held { ElementState::Pressed } else { ElementState::Released };
self.queue_event(synthetic_modifier_key_event(
&logical_key,
KeyLocation::Right,
state,
));
}
if right_held {
new_mask |= ModLocationMask::RIGHT;
}

if new_mask.is_empty() {
phys_mod_state.remove(&logical_key);
} else {
phys_mod_state.insert(logical_key, new_mask);
}
}

drop(phys_mod_state);

let modifiers = mods_from_flags(flags);
if modifiers != self.ivars().modifiers.get() {
self.ivars().modifiers.set(modifiers);
self.queue_event(WindowEvent::ModifiersChanged(modifiers));
}
}

pub(super) fn set_option_as_alt(&self, value: OptionAsAlt) {
self.ivars().option_as_alt.set(value)
}
Expand Down
12 changes: 4 additions & 8 deletions winit-appkit/src/window_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -187,20 +187,16 @@ define_class!(
let _entered = debug_span!("windowDidBecomeKey:").entered();
// TODO: center the cursor if the window had mouse grab when it
// lost focus

self.view().synchronize_modifiers();

self.queue_event(WindowEvent::Focused(true));
}

#[unsafe(method(windowDidResignKey:))]
fn window_did_resign_key(&self, _: Option<&AnyObject>) {
let _entered = debug_span!("windowDidResignKey:").entered();
// It happens rather often, e.g. when the user is Cmd+Tabbing, that the
// NSWindowDelegate will receive a didResignKey event despite no event
// being received when the modifiers are released. This is because
// flagsChanged events are received by the NSView instead of the
// NSWindowDelegate, and as a result a tracked modifiers state can quite
// easily fall out of synchrony with reality. This requires us to emit
// a synthetic ModifiersChanged event when we lose focus.
self.view().reset_modifiers();
self.view().synthesize_modifier_key_releases();

self.queue_event(WindowEvent::Focused(false));
}
Expand Down
2 changes: 1 addition & 1 deletion winit-core/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ pub enum WindowEvent {
///
/// * Synthetic key press events are generated for all keys pressed when a window gains
/// focus. Likewise, synthetic key release events are generated for all keys pressed when
/// a window goes out of focus. ***Currently, this is only functional on X11 and
/// a window goes out of focus. ***Currently, this is only functional on macOS, X11, and
/// Windows***
///
/// Otherwise, this value is always `false`.
Expand Down
Loading