macOS: add a way to hook standard keybinding events
Add macOS specific application handler to deliver macOS specific events. Co-authored-by: Mads Marquart <mads@marquart.dk>
This commit is contained in:
parent
a5f5ce6a3d
commit
c913cdab0b
5 changed files with 110 additions and 3 deletions
|
|
@ -22,7 +22,9 @@ use winit::event::{DeviceEvent, DeviceId, Ime, MouseButton, MouseScrollDelta, Wi
|
||||||
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
use winit::event_loop::{ActiveEventLoop, EventLoop};
|
||||||
use winit::keyboard::{Key, ModifiersState};
|
use winit::keyboard::{Key, ModifiersState};
|
||||||
#[cfg(macos_platform)]
|
#[cfg(macos_platform)]
|
||||||
use winit::platform::macos::{OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS};
|
use winit::platform::macos::{
|
||||||
|
ApplicationHandlerExtMacOS, OptionAsAlt, WindowAttributesExtMacOS, WindowExtMacOS,
|
||||||
|
};
|
||||||
#[cfg(any(x11_platform, wayland_platform))]
|
#[cfg(any(x11_platform, wayland_platform))]
|
||||||
use winit::platform::startup_notify::{
|
use winit::platform::startup_notify::{
|
||||||
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
|
self, EventLoopExtStartupNotify, WindowAttributesExtStartupNotify, WindowExtStartupNotify,
|
||||||
|
|
@ -552,6 +554,23 @@ impl ApplicationHandler for Application {
|
||||||
// We must drop the context here.
|
// We must drop the context here.
|
||||||
self.context = None;
|
self.context = None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
|
||||||
|
Some(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_os = "macos")]
|
||||||
|
impl ApplicationHandlerExtMacOS for Application {
|
||||||
|
fn standard_key_binding(
|
||||||
|
&mut self,
|
||||||
|
_event_loop: &dyn ActiveEventLoop,
|
||||||
|
window_id: WindowId,
|
||||||
|
action: &str,
|
||||||
|
) {
|
||||||
|
info!(?window_id, ?action, "macOS standard key binding");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// State of the window.
|
/// State of the window.
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
|
|
||||||
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
|
use crate::event::{DeviceEvent, DeviceId, StartCause, WindowEvent};
|
||||||
use crate::event_loop::ActiveEventLoop;
|
use crate::event_loop::ActiveEventLoop;
|
||||||
|
#[cfg(any(docsrs, macos_platform))]
|
||||||
|
use crate::platform::macos::ApplicationHandlerExtMacOS;
|
||||||
use crate::window::WindowId;
|
use crate::window::WindowId;
|
||||||
|
|
||||||
/// The handler of the application events.
|
/// The handler of the application events.
|
||||||
|
|
@ -343,6 +345,15 @@ pub trait ApplicationHandler {
|
||||||
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||||
let _ = event_loop;
|
let _ = event_loop;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The macOS-specific handler.
|
||||||
|
///
|
||||||
|
/// The return value from this should not change at runtime.
|
||||||
|
#[cfg(any(docsrs, macos_platform))]
|
||||||
|
#[inline(always)]
|
||||||
|
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deny(clippy::missing_trait_methods)]
|
#[deny(clippy::missing_trait_methods)]
|
||||||
|
|
@ -411,6 +422,12 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for &mut A {
|
||||||
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||||
(**self).memory_warning(event_loop);
|
(**self).memory_warning(event_loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(docsrs, macos_platform))]
|
||||||
|
#[inline]
|
||||||
|
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
|
||||||
|
(**self).macos_handler()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[deny(clippy::missing_trait_methods)]
|
#[deny(clippy::missing_trait_methods)]
|
||||||
|
|
@ -479,4 +496,10 @@ impl<A: ?Sized + ApplicationHandler> ApplicationHandler for Box<A> {
|
||||||
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
fn memory_warning(&mut self, event_loop: &dyn ActiveEventLoop) {
|
||||||
(**self).memory_warning(event_loop);
|
(**self).memory_warning(event_loop);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(docsrs, macos_platform))]
|
||||||
|
#[inline]
|
||||||
|
fn macos_handler(&mut self) -> Option<&mut dyn ApplicationHandlerExtMacOS> {
|
||||||
|
(**self).macos_handler()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ changelog entry.
|
||||||
and `Serialize` on many types.
|
and `Serialize` on many types.
|
||||||
- Add `MonitorHandle::current_video_mode()`.
|
- Add `MonitorHandle::current_video_mode()`.
|
||||||
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
|
- Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`.
|
||||||
|
- Add `ApplicationHandlerExtMacOS` trait, and a `macos_handler` method to `ApplicationHandler` which returns a `dyn ApplicationHandlerExtMacOS` which allows for macOS specific extensions to winit.
|
||||||
|
- Add a `standard_key_binding` method to the `ApplicationHandlerExtMacOS` trait. This allows handling of standard keybindings such as "go to end of line" on macOS.
|
||||||
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
|
- On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game`
|
||||||
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
|
to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games.
|
||||||
- Add `WindowId::into_raw()` and `from_raw()`.
|
- Add `WindowId::into_raw()` and `from_raw()`.
|
||||||
|
|
|
||||||
|
|
@ -75,9 +75,10 @@ use std::os::raw::c_void;
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
use crate::application::ApplicationHandler;
|
||||||
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
|
use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
|
||||||
use crate::monitor::MonitorHandle;
|
use crate::monitor::MonitorHandle;
|
||||||
use crate::window::{Window, WindowAttributes};
|
use crate::window::{Window, WindowAttributes, WindowId};
|
||||||
|
|
||||||
/// Additional methods on [`Window`] that are specific to MacOS.
|
/// Additional methods on [`Window`] that are specific to MacOS.
|
||||||
pub trait WindowExtMacOS {
|
pub trait WindowExtMacOS {
|
||||||
|
|
@ -548,3 +549,52 @@ pub enum OptionAsAlt {
|
||||||
#[default]
|
#[default]
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Additional events on [`ApplicationHandler`] that are specific to macOS.
|
||||||
|
///
|
||||||
|
/// This can be registered with [`ApplicationHandler::macos_handler`].
|
||||||
|
pub trait ApplicationHandlerExtMacOS: ApplicationHandler {
|
||||||
|
/// The system interpreted a keypress as a standard key binding command.
|
||||||
|
///
|
||||||
|
/// Examples include inserting tabs and newlines, or moving the insertion point, see
|
||||||
|
/// [`NSStandardKeyBindingResponding`] for the full list of key bindings. They are often text
|
||||||
|
/// editing related.
|
||||||
|
///
|
||||||
|
/// This corresponds to the [`doCommandBySelector:`] method on `NSTextInputClient`.
|
||||||
|
///
|
||||||
|
/// The `action` parameter contains the string representation of the selector. Examples include
|
||||||
|
/// `"insertBacktab:"`, `"indent:"` and `"noop:"`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// impl ApplicationHandlerExtMacOS for App {
|
||||||
|
/// fn standard_key_binding(
|
||||||
|
/// &mut self,
|
||||||
|
/// event_loop: &dyn ActiveEventLoop,
|
||||||
|
/// window_id: WindowId,
|
||||||
|
/// action: &str,
|
||||||
|
/// ) {
|
||||||
|
/// match action {
|
||||||
|
/// "moveBackward:" => self.cursor.position -= 1,
|
||||||
|
/// "moveForward:" => self.cursor.position += 1,
|
||||||
|
/// _ => {} // Ignore other actions
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`NSStandardKeyBindingResponding`]: https://developer.apple.com/documentation/appkit/nsstandardkeybindingresponding?language=objc
|
||||||
|
/// [`doCommandBySelector:`]: https://developer.apple.com/documentation/appkit/nstextinputclient/1438256-docommandbyselector?language=objc
|
||||||
|
#[doc(alias = "doCommandBySelector:")]
|
||||||
|
fn standard_key_binding(
|
||||||
|
&mut self,
|
||||||
|
event_loop: &dyn ActiveEventLoop,
|
||||||
|
window_id: WindowId,
|
||||||
|
action: &str,
|
||||||
|
) {
|
||||||
|
let _ = event_loop;
|
||||||
|
let _ = window_id;
|
||||||
|
let _ = action;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -412,8 +412,9 @@ declare_class!(
|
||||||
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human
|
// Basically, we're sent this message whenever a keyboard event that doesn't generate a "human
|
||||||
// readable" character happens, i.e. newlines, tabs, and Ctrl+C.
|
// readable" character happens, i.e. newlines, tabs, and Ctrl+C.
|
||||||
#[method(doCommandBySelector:)]
|
#[method(doCommandBySelector:)]
|
||||||
fn do_command_by_selector(&self, _command: Sel) {
|
fn do_command_by_selector(&self, command: Sel) {
|
||||||
trace_scope!("doCommandBySelector:");
|
trace_scope!("doCommandBySelector:");
|
||||||
|
|
||||||
// We shouldn't forward any character from just committed text, since we'll end up sending
|
// We shouldn't forward any character from just committed text, since we'll end up sending
|
||||||
// it twice with some IMEs like Korean one. We'll also always send `Enter` in that case,
|
// it twice with some IMEs like Korean one. We'll also always send `Enter` in that case,
|
||||||
// which is not desired given it was used to confirm IME input.
|
// which is not desired given it was used to confirm IME input.
|
||||||
|
|
@ -428,6 +429,18 @@ declare_class!(
|
||||||
// Leave preedit so that we also report the key-up for this key.
|
// Leave preedit so that we also report the key-up for this key.
|
||||||
self.ivars().ime_state.set(ImeState::Ground);
|
self.ivars().ime_state.set(ImeState::Ground);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Send command action to user if they requested it.
|
||||||
|
let window_id = self.window().id();
|
||||||
|
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||||
|
if let Some(handler) = app.macos_handler() {
|
||||||
|
handler.standard_key_binding(event_loop, window_id, command.name());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// The documentation for `-[NSTextInputClient doCommandBySelector:]` clearly states that
|
||||||
|
// we should not be forwarding this event up the responder chain, so no calling `super`
|
||||||
|
// here either.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue