mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 13:51:30 +11:00
Add WindowExtMacOS::{set_,}option_as_alt
This adds an ability to control left and right `Option` keys to be treated as `Alt`, thus not producing diacritical marks. Co-authored-by: Kirill Chibisov <contact@kchibisov.com>
This commit is contained in:
parent
13613931cf
commit
180a4c7a16
|
@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre
|
||||||
|
|
||||||
# Unreleased
|
# Unreleased
|
||||||
|
|
||||||
|
- On macOS, added `WindowExtMacOS::option_as_alt` and `WindowExtMacOS::set_option_as_alt`.
|
||||||
- On Windows, fix window size for maximized, undecorated windows.
|
- On Windows, fix window size for maximized, undecorated windows.
|
||||||
- On Windows and macOS, add `WindowBuilder::with_active`.
|
- On Windows and macOS, add `WindowBuilder::with_active`.
|
||||||
- Add `Window::is_minimized`.
|
- Add `Window::is_minimized`.
|
||||||
|
|
67
examples/window_option_as_alt.rs
Normal file
67
examples/window_option_as_alt.rs
Normal file
|
@ -0,0 +1,67 @@
|
||||||
|
#![allow(clippy::single_match)]
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use winit::platform::macos::{OptionAsAlt, WindowExtMacOS};
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
use winit::{
|
||||||
|
event::ElementState,
|
||||||
|
event::{Event, MouseButton, WindowEvent},
|
||||||
|
event_loop::EventLoop,
|
||||||
|
window::WindowBuilder,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Prints the keyboard events characters received when option_is_alt is true versus false.
|
||||||
|
/// A left mouse click will toggle option_is_alt.
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn main() {
|
||||||
|
let event_loop = EventLoop::new();
|
||||||
|
|
||||||
|
let window = WindowBuilder::new()
|
||||||
|
.with_title("A fantastic window!")
|
||||||
|
.with_inner_size(winit::dpi::LogicalSize::new(128.0, 128.0))
|
||||||
|
.build(&event_loop)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let mut option_as_alt = window.option_as_alt();
|
||||||
|
|
||||||
|
event_loop.run(move |event, _, control_flow| {
|
||||||
|
control_flow.set_wait();
|
||||||
|
|
||||||
|
match event {
|
||||||
|
Event::WindowEvent {
|
||||||
|
event: WindowEvent::CloseRequested,
|
||||||
|
window_id,
|
||||||
|
} if window_id == window.id() => control_flow.set_exit(),
|
||||||
|
Event::WindowEvent { event, .. } => match event {
|
||||||
|
WindowEvent::MouseInput {
|
||||||
|
state: ElementState::Pressed,
|
||||||
|
button: MouseButton::Left,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
option_as_alt = match option_as_alt {
|
||||||
|
OptionAsAlt::None => OptionAsAlt::OnlyLeft,
|
||||||
|
OptionAsAlt::OnlyLeft => OptionAsAlt::OnlyRight,
|
||||||
|
OptionAsAlt::OnlyRight => OptionAsAlt::Both,
|
||||||
|
OptionAsAlt::Both => OptionAsAlt::None,
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Received Mouse click, toggling option_as_alt to: {option_as_alt:?}");
|
||||||
|
window.set_option_as_alt(option_as_alt);
|
||||||
|
}
|
||||||
|
WindowEvent::ReceivedCharacter(c) => println!("ReceivedCharacter: {c:?}"),
|
||||||
|
WindowEvent::KeyboardInput { .. } => println!("KeyboardInput: {event:?}"),
|
||||||
|
_ => (),
|
||||||
|
},
|
||||||
|
Event::MainEventsCleared => {
|
||||||
|
window.request_redraw();
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(target_os = "macos"))]
|
||||||
|
fn main() {
|
||||||
|
println!("This example is only supported on MacOS");
|
||||||
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
use objc2::rc::Id;
|
|
||||||
use std::os::raw::c_void;
|
use std::os::raw::c_void;
|
||||||
|
|
||||||
|
use objc2::rc::Id;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
event_loop::{EventLoopBuilder, EventLoopWindowTarget},
|
||||||
monitor::MonitorHandle,
|
monitor::MonitorHandle,
|
||||||
|
@ -55,6 +56,17 @@ pub trait WindowExtMacOS {
|
||||||
|
|
||||||
/// Put the window in a state which indicates a file save is required.
|
/// Put the window in a state which indicates a file save is required.
|
||||||
fn set_document_edited(&self, edited: bool);
|
fn set_document_edited(&self, edited: bool);
|
||||||
|
|
||||||
|
/// Set option as alt behavior as described in [`OptionAsAlt`].
|
||||||
|
///
|
||||||
|
/// This will ignore diacritical marks and accent characters from
|
||||||
|
/// being processed as received characters. Instead, the input
|
||||||
|
/// device's raw character will be placed in event queues with the
|
||||||
|
/// Alt modifier set.
|
||||||
|
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt);
|
||||||
|
|
||||||
|
/// Getter for the [`WindowExtMacOS::set_option_as_alt`].
|
||||||
|
fn option_as_alt(&self) -> OptionAsAlt;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowExtMacOS for Window {
|
impl WindowExtMacOS for Window {
|
||||||
|
@ -97,6 +109,16 @@ impl WindowExtMacOS for Window {
|
||||||
fn set_document_edited(&self, edited: bool) {
|
fn set_document_edited(&self, edited: bool) {
|
||||||
self.window.set_document_edited(edited)
|
self.window.set_document_edited(edited)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
|
||||||
|
self.window.set_option_as_alt(option_as_alt)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn option_as_alt(&self) -> OptionAsAlt {
|
||||||
|
self.window.option_as_alt()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Corresponds to `NSApplicationActivationPolicy`.
|
/// Corresponds to `NSApplicationActivationPolicy`.
|
||||||
|
@ -142,6 +164,11 @@ pub trait WindowBuilderExtMacOS {
|
||||||
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
|
fn with_has_shadow(self, has_shadow: bool) -> WindowBuilder;
|
||||||
/// Window accepts click-through mouse events.
|
/// Window accepts click-through mouse events.
|
||||||
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
|
fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> WindowBuilder;
|
||||||
|
|
||||||
|
/// Set whether the `OptionAsAlt` key is interpreted as the `Alt` modifier.
|
||||||
|
///
|
||||||
|
/// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
|
||||||
|
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> WindowBuilder;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowBuilderExtMacOS for WindowBuilder {
|
impl WindowBuilderExtMacOS for WindowBuilder {
|
||||||
|
@ -201,6 +228,12 @@ impl WindowBuilderExtMacOS for WindowBuilder {
|
||||||
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
|
self.platform_specific.accepts_first_mouse = accepts_first_mouse;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> WindowBuilder {
|
||||||
|
self.platform_specific.option_as_alt = option_as_alt;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait EventLoopBuilderExtMacOS {
|
pub trait EventLoopBuilderExtMacOS {
|
||||||
|
@ -311,3 +344,28 @@ impl<T> EventLoopWindowTargetExtMacOS for EventLoopWindowTarget<T> {
|
||||||
self.p.hide_other_applications()
|
self.p.hide_other_applications()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Option as alt behavior.
|
||||||
|
///
|
||||||
|
/// The default is `None`.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
||||||
|
pub enum OptionAsAlt {
|
||||||
|
/// The left `Option` key is treated as `Alt`.
|
||||||
|
OnlyLeft,
|
||||||
|
|
||||||
|
/// The right `Option` key is treated as `Alt`.
|
||||||
|
OnlyRight,
|
||||||
|
|
||||||
|
/// Both `Option` keys are treated as `Alt`.
|
||||||
|
Both,
|
||||||
|
|
||||||
|
/// No special handling is applied for `Option` key.
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for OptionAsAlt {
|
||||||
|
fn default() -> Self {
|
||||||
|
OptionAsAlt::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -67,6 +67,35 @@ extern_methods!(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn keyEventWithType(
|
||||||
|
type_: NSEventType,
|
||||||
|
location: NSPoint,
|
||||||
|
modifier_flags: NSEventModifierFlags,
|
||||||
|
timestamp: NSTimeInterval,
|
||||||
|
window_num: NSInteger,
|
||||||
|
context: Option<&NSObject>,
|
||||||
|
characters: &NSString,
|
||||||
|
characters_ignoring_modifiers: &NSString,
|
||||||
|
is_a_repeat: bool,
|
||||||
|
scancode: c_ushort,
|
||||||
|
) -> Id<Self, Shared> {
|
||||||
|
unsafe {
|
||||||
|
msg_send_id![
|
||||||
|
Self::class(),
|
||||||
|
keyEventWithType: type_,
|
||||||
|
location: location,
|
||||||
|
modifierFlags: modifier_flags,
|
||||||
|
timestamp: timestamp,
|
||||||
|
windowNumber: window_num,
|
||||||
|
context: context,
|
||||||
|
characters: characters,
|
||||||
|
charactersIgnoringModifiers: characters_ignoring_modifiers,
|
||||||
|
isARepeat: is_a_repeat,
|
||||||
|
keyCode: scancode,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[sel(locationInWindow)]
|
#[sel(locationInWindow)]
|
||||||
pub fn locationInWindow(&self) -> NSPoint;
|
pub fn locationInWindow(&self) -> NSPoint;
|
||||||
|
|
||||||
|
@ -123,6 +152,15 @@ extern_methods!(
|
||||||
#[sel(stage)]
|
#[sel(stage)]
|
||||||
pub fn stage(&self) -> NSInteger;
|
pub fn stage(&self) -> NSInteger;
|
||||||
|
|
||||||
|
#[sel(isARepeat)]
|
||||||
|
pub fn is_a_repeat(&self) -> bool;
|
||||||
|
|
||||||
|
#[sel(windowNumber)]
|
||||||
|
pub fn window_number(&self) -> NSInteger;
|
||||||
|
|
||||||
|
#[sel(timestamp)]
|
||||||
|
pub fn timestamp(&self) -> NSTimeInterval;
|
||||||
|
|
||||||
pub fn characters(&self) -> Option<Id<NSString, Shared>> {
|
pub fn characters(&self) -> Option<Id<NSString, Shared>> {
|
||||||
unsafe { msg_send_id![self, characters] }
|
unsafe { msg_send_id![self, characters] }
|
||||||
}
|
}
|
||||||
|
@ -130,6 +168,16 @@ extern_methods!(
|
||||||
pub fn charactersIgnoringModifiers(&self) -> Option<Id<NSString, Shared>> {
|
pub fn charactersIgnoringModifiers(&self) -> Option<Id<NSString, Shared>> {
|
||||||
unsafe { msg_send_id![self, charactersIgnoringModifiers] }
|
unsafe { msg_send_id![self, charactersIgnoringModifiers] }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn lalt_pressed(&self) -> bool {
|
||||||
|
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||||
|
raw_modifiers & NX_DEVICELALTKEYMASK != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ralt_pressed(&self) -> bool {
|
||||||
|
let raw_modifiers = self.modifierFlags().bits() as u32;
|
||||||
|
raw_modifiers & NX_DEVICERALTKEYMASK != 0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -138,6 +186,10 @@ unsafe impl NSCopying for NSEvent {
|
||||||
type Output = NSEvent;
|
type Output = NSEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The values are from the https://github.com/apple-oss-distributions/IOHIDFamily/blob/19666c840a6d896468416ff0007040a10b7b46b8/IOHIDSystem/IOKit/hidsystem/IOLLEvent.h#L258-L259
|
||||||
|
const NX_DEVICELALTKEYMASK: u32 = 0x00000020;
|
||||||
|
const NX_DEVICERALTKEYMASK: u32 = 0x00000040;
|
||||||
|
|
||||||
bitflags! {
|
bitflags! {
|
||||||
pub struct NSEventModifierFlags: NSUInteger {
|
pub struct NSEventModifierFlags: NSUInteger {
|
||||||
const NSAlphaShiftKeyMask = 1 << 16;
|
const NSAlphaShiftKeyMask = 1 << 16;
|
||||||
|
|
|
@ -15,6 +15,7 @@ use super::appkit::{
|
||||||
NSApp, NSCursor, NSEvent, NSEventModifierFlags, NSEventPhase, NSResponder, NSTrackingRectTag,
|
NSApp, NSCursor, NSEvent, NSEventModifierFlags, NSEventPhase, NSResponder, NSTrackingRectTag,
|
||||||
NSView,
|
NSView,
|
||||||
};
|
};
|
||||||
|
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{LogicalPosition, LogicalSize},
|
dpi::{LogicalPosition, LogicalSize},
|
||||||
event::{
|
event::{
|
||||||
|
@ -463,7 +464,17 @@ declare_class!(
|
||||||
}
|
}
|
||||||
let was_in_preedit = self.state.ime_state == ImeState::Preedit;
|
let was_in_preedit = self.state.ime_state == ImeState::Preedit;
|
||||||
|
|
||||||
let characters = get_characters(event, false);
|
// Get the characters from the event.
|
||||||
|
let ev_mods = event_mods(event);
|
||||||
|
let ignore_alt_characters = match self.window().option_as_alt() {
|
||||||
|
OptionAsAlt::OnlyLeft if event.lalt_pressed() => true,
|
||||||
|
OptionAsAlt::OnlyRight if event.ralt_pressed() => true,
|
||||||
|
OptionAsAlt::Both if ev_mods.alt() => true,
|
||||||
|
_ => false,
|
||||||
|
} && !ev_mods.ctrl()
|
||||||
|
&& !ev_mods.logo();
|
||||||
|
|
||||||
|
let characters = get_characters(event, ignore_alt_characters);
|
||||||
self.state.forward_key_to_app = false;
|
self.state.forward_key_to_app = false;
|
||||||
|
|
||||||
// The `interpretKeyEvents` function might call
|
// The `interpretKeyEvents` function might call
|
||||||
|
@ -474,7 +485,13 @@ declare_class!(
|
||||||
// is not handled by IME and should be handled by the application)
|
// is not handled by IME and should be handled by the application)
|
||||||
let mut text_commited = false;
|
let mut text_commited = false;
|
||||||
if self.state.ime_allowed {
|
if self.state.ime_allowed {
|
||||||
let events_for_nsview = NSArray::from_slice(&[event.copy()]);
|
let new_event = if ignore_alt_characters {
|
||||||
|
replace_event_chars(event, &characters)
|
||||||
|
} else {
|
||||||
|
event.copy()
|
||||||
|
};
|
||||||
|
|
||||||
|
let events_for_nsview = NSArray::from_slice(&[new_event]);
|
||||||
unsafe { self.interpretKeyEvents(&events_for_nsview) };
|
unsafe { self.interpretKeyEvents(&events_for_nsview) };
|
||||||
|
|
||||||
// If the text was commited we must treat the next keyboard event as IME related.
|
// If the text was commited we must treat the next keyboard event as IME related.
|
||||||
|
@ -501,7 +518,7 @@ declare_class!(
|
||||||
state: ElementState::Pressed,
|
state: ElementState::Pressed,
|
||||||
scancode,
|
scancode,
|
||||||
virtual_keycode,
|
virtual_keycode,
|
||||||
modifiers: event_mods(event),
|
modifiers: ev_mods,
|
||||||
},
|
},
|
||||||
is_synthetic: false,
|
is_synthetic: false,
|
||||||
});
|
});
|
||||||
|
@ -982,3 +999,21 @@ fn mouse_button(event: &NSEvent) -> MouseButton {
|
||||||
n => MouseButton::Other(n as u16),
|
n => MouseButton::Other(n as u16),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn replace_event_chars(event: &NSEvent, characters: &str) -> Id<NSEvent, Shared> {
|
||||||
|
let ns_chars = NSString::from_str(characters);
|
||||||
|
let chars_ignoring_mods = event.charactersIgnoringModifiers().unwrap();
|
||||||
|
|
||||||
|
NSEvent::keyEventWithType(
|
||||||
|
event.type_(),
|
||||||
|
event.locationInWindow(),
|
||||||
|
event.modifierFlags(),
|
||||||
|
event.timestamp(),
|
||||||
|
event.window_number(),
|
||||||
|
None,
|
||||||
|
&ns_chars,
|
||||||
|
&chars_ignoring_mods,
|
||||||
|
event.is_a_repeat(),
|
||||||
|
event.scancode(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -21,7 +21,7 @@ use crate::{
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||||
event::WindowEvent,
|
event::WindowEvent,
|
||||||
icon::Icon,
|
icon::Icon,
|
||||||
platform::macos::WindowExtMacOS,
|
platform::macos::{OptionAsAlt, WindowExtMacOS},
|
||||||
platform_impl::platform::{
|
platform_impl::platform::{
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
appkit::NSWindowOrderingMode,
|
appkit::NSWindowOrderingMode,
|
||||||
|
@ -85,6 +85,7 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||||
pub disallow_hidpi: bool,
|
pub disallow_hidpi: bool,
|
||||||
pub has_shadow: bool,
|
pub has_shadow: bool,
|
||||||
pub accepts_first_mouse: bool,
|
pub accepts_first_mouse: bool,
|
||||||
|
pub option_as_alt: OptionAsAlt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||||
|
@ -100,6 +101,7 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||||
disallow_hidpi: false,
|
disallow_hidpi: false,
|
||||||
has_shadow: true,
|
has_shadow: true,
|
||||||
accepts_first_mouse: true,
|
accepts_first_mouse: true,
|
||||||
|
option_as_alt: Default::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -157,6 +159,9 @@ pub struct SharedState {
|
||||||
/// transitioning back to borderless fullscreen.
|
/// transitioning back to borderless fullscreen.
|
||||||
save_presentation_opts: Option<NSApplicationPresentationOptions>,
|
save_presentation_opts: Option<NSApplicationPresentationOptions>,
|
||||||
pub current_theme: Option<Theme>,
|
pub current_theme: Option<Theme>,
|
||||||
|
|
||||||
|
/// The state of the `Option` as `Alt`.
|
||||||
|
pub(crate) option_as_alt: OptionAsAlt,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedState {
|
impl SharedState {
|
||||||
|
@ -368,6 +373,8 @@ impl WinitWindow {
|
||||||
this.center();
|
this.center();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.set_option_as_alt(pl_attrs.option_as_alt);
|
||||||
|
|
||||||
Id::into_shared(this)
|
Id::into_shared(this)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1349,6 +1356,16 @@ impl WindowExtMacOS for WinitWindow {
|
||||||
fn set_document_edited(&self, edited: bool) {
|
fn set_document_edited(&self, edited: bool) {
|
||||||
self.setDocumentEdited(edited)
|
self.setDocumentEdited(edited)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
|
||||||
|
let mut shared_state_lock = self.shared_state.lock().unwrap();
|
||||||
|
shared_state_lock.option_as_alt = option_as_alt;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn option_as_alt(&self) -> OptionAsAlt {
|
||||||
|
let shared_state_lock = self.shared_state.lock().unwrap();
|
||||||
|
shared_state_lock.option_as_alt
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn get_ns_theme() -> Theme {
|
pub(super) fn get_ns_theme() -> Theme {
|
||||||
|
|
Loading…
Reference in a new issue