diff --git a/Cargo.lock b/Cargo.lock index 843b9c8544..a4a58b23d1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1695,16 +1695,6 @@ dependencies = [ "bytemuck", ] -[[package]] -name = "fontconfig-cache-parser" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7f8afb20c8069fd676d27b214559a337cc619a605d25a87baa90b49a06f3b18" -dependencies = [ - "bytemuck", - "thiserror 1.0.69", -] - [[package]] name = "fontconfig-parser" version = "0.5.8" @@ -1744,25 +1734,25 @@ dependencies = [ [[package]] name = "fontique" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39f97079e1293b8c1e9fb03a2875d328bd2ee8f3b95ce62959c0acc04049c708" +checksum = "ff3336bc0b87fe42305047263fa60d2eabd650d29cbe62fdeb2a66c7a0a595f9" dependencies = [ "bytemuck", - "fontconfig-cache-parser", "hashbrown 0.15.5", - "icu_locid", + "icu_locale_core", + "linebender_resource_handle", "memmap2", "objc2", "objc2-core-foundation", "objc2-core-text", "objc2-foundation", - "peniko 0.4.0", - "read-fonts 0.29.3", + "read-fonts 0.35.0", "roxmltree", "smallvec", "windows", "windows-core 0.58.0", + "yeslogic-fontconfig-sys", ] [[package]] @@ -2265,6 +2255,9 @@ dependencies = [ "graphite-desktop-embedded-resources", "graphite-desktop-wrapper", "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", "open", "rand 0.9.2", "rfd", @@ -2446,6 +2439,19 @@ dependencies = [ "serde", ] +[[package]] +name = "harfrust" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92c020db12c71d8a12a3fe7607873cade3a01a6287e29d540c8723276221b9d8" +dependencies = [ + "bitflags 2.9.3", + "bytemuck", + "core_maths", + "read-fonts 0.35.0", + "smallvec", +] + [[package]] name = "hashbrown" version = "0.15.5" @@ -2691,24 +2697,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0cde2700ccaed3872079a65fb1a78f6c0a36c91570f28755dda67bc8f7d9f00a" dependencies = [ "displaydoc", - "litemap 0.8.0", - "tinystr 0.8.1", - "writeable 0.6.1", + "litemap", + "tinystr", + "writeable", "zerovec", ] -[[package]] -name = "icu_locid" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" -dependencies = [ - "displaydoc", - "litemap 0.7.5", - "tinystr 0.7.6", - "writeable 0.5.5", -] - [[package]] name = "icu_normalizer" version = "2.0.0" @@ -2761,8 +2755,8 @@ dependencies = [ "displaydoc", "icu_locale_core", "stable_deref_trait", - "tinystr 0.8.1", - "writeable 0.6.1", + "tinystr", + "writeable", "yoke", "zerofrom", "zerotrie", @@ -3236,12 +3230,6 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" -[[package]] -name = "litemap" -version = "0.7.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23fb14cb19457329c82206317a5663005a4d404783dc74f4252769b0d5f42856" - [[package]] name = "litemap" version = "0.8.0" @@ -3811,9 +3799,9 @@ dependencies = [ [[package]] name = "objc2-core-text" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ba833d4a1cb1aac330f8c973fd92b6ff1858e4aef5cdd00a255eefb28022fb5" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" dependencies = [ "bitflags 2.9.3", "objc2-core-foundation", @@ -4038,14 +4026,15 @@ dependencies = [ [[package]] name = "parley" -version = "0.5.0" +version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13e57638545cf2ba4c3e72cc5715e53b1880b829cc3dbefda3d1700c58efe723" +checksum = "26746861bb76dbc9bcd5ed1b0b55d2fedf291100961251702a031ab2abd2ce52" dependencies = [ "fontique", + "harfrust", "hashbrown 0.15.5", - "peniko 0.4.0", - "skrifa 0.31.3", + "linebender_resource_handle", + "skrifa 0.37.0", "swash", ] @@ -4095,17 +4084,6 @@ version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "df94ce210e5bc13cb6651479fa48d14f601d9858cfe0467f43ae157023b938d3" -[[package]] -name = "peniko" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f9529efd019889b2a205193c14ffb6e2839b54ed9d2720674f10f4b04d87ac9" -dependencies = [ - "color", - "kurbo 0.11.3", - "smallvec", -] - [[package]] name = "peniko" version = "0.5.0" @@ -4769,16 +4747,6 @@ dependencies = [ "crossbeam-utils", ] -[[package]] -name = "read-fonts" -version = "0.29.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04ca636dac446b5664bd16c069c00a9621806895b8bb02c2dc68542b23b8f25d" -dependencies = [ - "bytemuck", - "font-types 0.9.0", -] - [[package]] name = "read-fonts" version = "0.34.0" @@ -4796,6 +4764,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6717cf23b488adf64b9d711329542ba34de147df262370221940dfabc2c91358" dependencies = [ "bytemuck", + "core_maths", "font-types 0.10.0", ] @@ -5434,16 +5403,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" -[[package]] -name = "skrifa" -version = "0.31.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbeb4ca4399663735553a09dd17ce7e49a0a0203f03b706b39628c4d913a8607" -dependencies = [ - "bytemuck", - "read-fonts 0.29.3", -] - [[package]] name = "skrifa" version = "0.36.0" @@ -5715,11 +5674,11 @@ dependencies = [ [[package]] name = "swash" -version = "0.2.5" +version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f745de914febc7c9ab4388dfaf94bbc87e69f57bb41133a9b0c84d4be49856f3" +checksum = "47846491253e976bdd07d0f9cc24b7daf24720d11309302ccbbc6e6b6e53550a" dependencies = [ - "skrifa 0.31.3", + "skrifa 0.37.0", "yazi", "zeno", ] @@ -5971,15 +5930,6 @@ dependencies = [ "strict-num", ] -[[package]] -name = "tinystr" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" -dependencies = [ - "displaydoc", -] - [[package]] name = "tinystr" version = "0.8.1" @@ -6580,7 +6530,7 @@ dependencies = [ "bytemuck", "futures-intrusive", "log", - "peniko 0.5.0", + "peniko", "png", "skrifa 0.37.0", "static_assertions", @@ -6597,7 +6547,7 @@ source = "git+https://github.com/linebender/vello#8f2f2564127812362d2c57ded20cad dependencies = [ "bytemuck", "guillotiere", - "peniko 0.5.0", + "peniko", "skrifa 0.37.0", "smallvec", ] @@ -7715,12 +7665,6 @@ version = "0.45.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "052283831dbae3d879dc7f51f3d92703a316ca49f91540417d38591826127814" -[[package]] -name = "writeable" -version = "0.5.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" - [[package]] name = "writeable" version = "0.6.1" @@ -7819,6 +7763,17 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e01738255b5a16e78bbb83e7fbba0a1e7dd506905cfc53f4622d89015a03fbb5" +[[package]] +name = "yeslogic-fontconfig-sys" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "503a066b4c037c440169d995b869046827dbc71263f6e8f3be6d77d4f3229dbd" +dependencies = [ + "dlib", + "once_cell", + "pkg-config", +] + [[package]] name = "yoke" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 4dcd0fa693..11ede35b63 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -182,7 +182,7 @@ image = { version = "0.25", default-features = false, features = [ "jpeg", "bmp", ] } -parley = "0.5" +parley = "0.6" skrifa = "0.36" pretty_assertions = "1.4" fern = { version = "0.7", features = ["colored"] } diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index 6aad2144ec..6a22efd319 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -58,5 +58,8 @@ windows = { version = "0.58.0", features = [ # macOS-specific dependencies [target.'cfg(target_os = "macos")'.dependencies] +objc2 = { version = "0.6.1", default-features = false } +objc2-foundation = { version = "0.3.2", default-features = false } +objc2-app-kit = { version = "0.3.2", default-features = false } muda = { git = "https://github.com/tauri-apps/muda.git", rev = "3f460b8fbaed59cda6d95ceea6904f000f093f15", default-features = false } diff --git a/desktop/src/app.rs b/desktop/src/app.rs index d0557cd143..f02e52ef70 100644 --- a/desktop/src/app.rs +++ b/desktop/src/app.rs @@ -44,6 +44,10 @@ pub(crate) struct App { } impl App { + pub(crate) fn init() { + Window::init(); + } + pub(crate) fn new( cef_context: Box, cef_view_info_sender: Sender, diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index 2961166764..e026e38368 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -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() { diff --git a/desktop/src/lib.rs b/desktop/src/lib.rs index fdf504d04d..35e604c2a8 100644 --- a/desktop/src/lib.rs +++ b/desktop/src/lib.rs @@ -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()); diff --git a/desktop/src/window.rs b/desktop/src/window.rs index e9994d23ca..6a6b2dce71 100644 --- a/desktop/src/window.rs +++ b/desktop/src/window.rs @@ -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) {} @@ -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) diff --git a/desktop/src/window/mac.rs b/desktop/src/window/mac.rs index f6cd4330d0..5688cbd38c 100644 --- a/desktop/src/window/mac.rs +++ b/desktop/src/window/mac.rs @@ -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; diff --git a/desktop/src/window/mac/app.rs b/desktop/src/window/mac/app.rs new file mode 100644 index 0000000000..36ca65c315 --- /dev/null +++ b/desktop/src/window/mac/app.rs @@ -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] } + } + } + } +); diff --git a/desktop/src/window/mac/menu.rs b/desktop/src/window/mac/menu.rs index e2b437a913..ba3a94df75 100644 --- a/desktop/src/window/mac/menu.rs +++ b/desktop/src/window/mac/menu.rs @@ -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 }); } diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index 1c4d1991b6..dedc61d230 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -164,8 +164,7 @@ fn convert_menu_bar_entry_to_menu_item( } let shortcut = match shortcut { - //TODO: Reenable shortcuts once a workaround for missing keyboard events is found - Some(ActionKeys::Keys(LayoutKeysGroup(keys))) if false => convert_layout_keys_to_shortcut(keys), + Some(ActionKeys::Keys(LayoutKeysGroup(keys))) => convert_layout_keys_to_shortcut(keys), _ => None, }; diff --git a/node-graph/libraries/core-types/src/text.rs b/node-graph/libraries/core-types/src/text.rs index 9321d7f729..b58a05a6e0 100644 --- a/node-graph/libraries/core-types/src/text.rs +++ b/node-graph/libraries/core-types/src/text.rs @@ -26,9 +26,9 @@ impl From for parley::Alignment { fn from(val: TextAlign) -> Self { match val { TextAlign::Left => parley::Alignment::Left, - TextAlign::Center => parley::Alignment::Middle, + TextAlign::Center => parley::Alignment::Center, TextAlign::Right => parley::Alignment::Right, - TextAlign::JustifyLeft => parley::Alignment::Justified, + TextAlign::JustifyLeft => parley::Alignment::Justify, } } } diff --git a/node-graph/nodes/text/src/lib.rs b/node-graph/nodes/text/src/lib.rs index fb776e9088..b4ac6f4566 100644 --- a/node-graph/nodes/text/src/lib.rs +++ b/node-graph/nodes/text/src/lib.rs @@ -33,9 +33,9 @@ impl From for parley::Alignment { fn from(val: TextAlign) -> Self { match val { TextAlign::Left => parley::Alignment::Left, - TextAlign::Center => parley::Alignment::Middle, + TextAlign::Center => parley::Alignment::Center, TextAlign::Right => parley::Alignment::Right, - TextAlign::JustifyLeft => parley::Alignment::Justified, + TextAlign::JustifyLeft => parley::Alignment::Justify, } } }