mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-15 22:28:10 +08:00
Desktop: Mac menu workaround (#3398)
This commit is contained in:
@@ -44,6 +44,10 @@ pub(crate) struct App {
|
||||
}
|
||||
|
||||
impl App {
|
||||
pub(crate) fn init() {
|
||||
Window::init();
|
||||
}
|
||||
|
||||
pub(crate) fn new(
|
||||
cef_context: Box<dyn cef::CefContext>,
|
||||
cef_view_info_sender: Sender<cef::ViewInfoUpdate>,
|
||||
|
||||
@@ -88,11 +88,27 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat
|
||||
key_event.native_key_code = event.physical_key.to_native_keycode();
|
||||
|
||||
key_event.character = event.logical_key.to_char_representation() as u16;
|
||||
key_event.unmodified_character = event.key_without_modifiers.to_char_representation() as u16;
|
||||
|
||||
// Mitigation for CEF on Mac bug to prevent NSMenu being triggered by this key event.
|
||||
//
|
||||
// CEF converts the key event into an `NSEvent` internally and passes that to Chromium.
|
||||
// In some cases the `NSEvent` gets to the native Cocoa application, is considered "unhandled" and can trigger menus.
|
||||
//
|
||||
// Why mitigation works:
|
||||
// Leaving `key_event.unmodified_character = 0` still leads to CEF forwarding a "unhandled" event to the native application
|
||||
// but that event is discarded because `key_event.unmodified_character = 0` is considered non-printable and not used for shortcut matching.
|
||||
//
|
||||
// See https://github.com/chromiumembedded/cef/issues/3857
|
||||
//
|
||||
// TODO: Remove mitigation once bug is fixed or a better solution is found.
|
||||
#[cfg(not(target_os = "macos"))]
|
||||
{
|
||||
key_event.unmodified_character = event.key_without_modifiers.to_char_representation() as u16;
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")] // See https://www.magpcss.org/ceforum/viewtopic.php?start=10&t=11650
|
||||
if key_event.character == 0 && key_event.unmodified_character == 0 && event.text_with_all_modifiers.is_some() {
|
||||
key_event.unmodified_character = 1;
|
||||
key_event.character = 1;
|
||||
}
|
||||
|
||||
if key_event.type_ == cef_key_event_type_t::KEYEVENT_CHAR.into() {
|
||||
|
||||
@@ -36,6 +36,8 @@ pub fn start() {
|
||||
return;
|
||||
}
|
||||
|
||||
App::init();
|
||||
|
||||
let cli = Cli::parse();
|
||||
|
||||
let wgpu_context = futures::executor::block_on(gpu_context::create_wgpu_context());
|
||||
|
||||
@@ -4,9 +4,11 @@ use winit::window::{Window as WinitWindow, WindowAttributes};
|
||||
|
||||
use crate::consts::APP_NAME;
|
||||
use crate::event::AppEventScheduler;
|
||||
use crate::window::mac::NativeWindowImpl;
|
||||
use crate::wrapper::messages::MenuItem;
|
||||
|
||||
pub(crate) trait NativeWindow {
|
||||
fn init() {}
|
||||
fn configure(attributes: WindowAttributes, event_loop: &dyn ActiveEventLoop) -> WindowAttributes;
|
||||
fn new(window: &dyn WinitWindow, app_event_scheduler: AppEventScheduler) -> Self;
|
||||
fn update_menu(&self, _entries: Vec<MenuItem>) {}
|
||||
@@ -34,6 +36,10 @@ pub(crate) struct Window {
|
||||
}
|
||||
|
||||
impl Window {
|
||||
pub(crate) fn init() {
|
||||
NativeWindowImpl::init();
|
||||
}
|
||||
|
||||
pub(crate) fn new(event_loop: &dyn ActiveEventLoop, app_event_scheduler: AppEventScheduler) -> Self {
|
||||
let mut attributes = WindowAttributes::default()
|
||||
.with_title(APP_NAME)
|
||||
|
||||
@@ -2,15 +2,21 @@ use winit::event_loop::ActiveEventLoop;
|
||||
use winit::platform::macos::WindowAttributesMacOS;
|
||||
use winit::window::{Window, WindowAttributes};
|
||||
|
||||
use crate::consts::APP_NAME;
|
||||
use crate::event::AppEventScheduler;
|
||||
use crate::wrapper::messages::MenuItem;
|
||||
|
||||
mod app;
|
||||
mod menu;
|
||||
|
||||
pub(super) struct NativeWindowImpl {
|
||||
menu: menu::Menu,
|
||||
}
|
||||
|
||||
impl super::NativeWindow for NativeWindowImpl {
|
||||
fn init() {
|
||||
app::init();
|
||||
}
|
||||
|
||||
fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes {
|
||||
let mac_window = WindowAttributesMacOS::default()
|
||||
.with_titlebar_transparent(true)
|
||||
@@ -20,7 +26,7 @@ impl super::NativeWindow for NativeWindowImpl {
|
||||
}
|
||||
|
||||
fn new(_window: &dyn Window, app_event_scheduler: AppEventScheduler) -> Self {
|
||||
let menu = menu::Menu::new(app_event_scheduler, APP_NAME);
|
||||
let menu = menu::Menu::new(app_event_scheduler);
|
||||
|
||||
NativeWindowImpl { menu }
|
||||
}
|
||||
@@ -29,5 +35,3 @@ impl super::NativeWindow for NativeWindowImpl {
|
||||
self.menu.update(entries);
|
||||
}
|
||||
}
|
||||
|
||||
mod menu;
|
||||
|
||||
27
desktop/src/window/mac/app.rs
Normal file
27
desktop/src/window/mac/app.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
use objc2::{ClassType, define_class, msg_send};
|
||||
use objc2_app_kit::{NSApplication, NSEvent, NSEventType, NSResponder};
|
||||
use objc2_foundation::NSObject;
|
||||
|
||||
pub(super) fn init() {
|
||||
unsafe {
|
||||
let _: &NSApplication = msg_send![GraphiteApplication::class(), sharedApplication];
|
||||
}
|
||||
}
|
||||
|
||||
define_class!(
|
||||
#[unsafe(super(NSApplication, NSResponder, NSObject))]
|
||||
#[name = "GraphiteApplication"]
|
||||
pub(super) struct GraphiteApplication;
|
||||
|
||||
impl GraphiteApplication {
|
||||
#[unsafe(method(sendEvent:))]
|
||||
fn send_event(&self, event: &NSEvent) {
|
||||
// Route keyDown events straight to the key window to skip native menu shortcut handling.
|
||||
if event.r#type() == NSEventType::KeyDown && let Some(key_window) = self.keyWindow() {
|
||||
unsafe { msg_send![&key_window, sendEvent: event] }
|
||||
} else {
|
||||
unsafe { msg_send![super(self), sendEvent: event] }
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -1,6 +1,6 @@
|
||||
use muda::Menu as MudaMenu;
|
||||
use muda::accelerator::Accelerator;
|
||||
use muda::{AboutMetadataBuilder, CheckMenuItem, IsMenuItem, MenuEvent, MenuId, MenuItem, MenuItemKind, PredefinedMenuItem, Result, Submenu};
|
||||
use muda::{CheckMenuItem, IsMenuItem, MenuEvent, MenuId, MenuItem, MenuItemKind, PredefinedMenuItem, Result, Submenu};
|
||||
|
||||
use crate::event::{AppEvent, AppEventScheduler};
|
||||
use crate::wrapper::messages::MenuItem as WrapperMenuItem;
|
||||
@@ -10,18 +10,9 @@ pub(super) struct Menu {
|
||||
}
|
||||
|
||||
impl Menu {
|
||||
pub(super) fn new(event_scheduler: AppEventScheduler, app_name: &str) -> Self {
|
||||
let about = PredefinedMenuItem::about(None, Some(AboutMetadataBuilder::new().name(Some(app_name)).build()));
|
||||
let hide = PredefinedMenuItem::hide(None);
|
||||
let hide_others = PredefinedMenuItem::hide_others(None);
|
||||
let show_all = PredefinedMenuItem::show_all(None);
|
||||
let quit = PredefinedMenuItem::quit(None);
|
||||
let app_submenu = Submenu::with_items(
|
||||
"",
|
||||
true,
|
||||
&[&about, &PredefinedMenuItem::separator(), &hide, &hide_others, &show_all, &PredefinedMenuItem::separator(), &quit],
|
||||
)
|
||||
.unwrap();
|
||||
pub(super) fn new(event_scheduler: AppEventScheduler) -> Self {
|
||||
// TODO: Remove as much app submenu special handling as possible
|
||||
let app_submenu = Submenu::with_items("", true, &[]).unwrap();
|
||||
|
||||
let menu = MudaMenu::new();
|
||||
menu.prepend(&app_submenu).unwrap();
|
||||
@@ -29,6 +20,16 @@ impl Menu {
|
||||
menu.init_for_nsapp();
|
||||
|
||||
MenuEvent::set_event_handler(Some(move |event: MenuEvent| {
|
||||
let mtm = objc2::MainThreadMarker::new().expect("only ever called from main thread");
|
||||
let is_shortcut_triggered = objc2_app_kit::NSApplication::sharedApplication(mtm)
|
||||
.mainMenu()
|
||||
.map(|m| m.highlightedItem().is_some())
|
||||
.unwrap_or_default();
|
||||
if is_shortcut_triggered {
|
||||
tracing::error!("A keyboard input triggered a menu event. This is most likely a bug. Please report!");
|
||||
return;
|
||||
}
|
||||
|
||||
if let Some(id) = menu_id_to_u64(event.id()) {
|
||||
event_scheduler.schedule(AppEvent::MenuEvent { id });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user