From 095a5040b8837bfc83d6f09d2c575cfb4d799673 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Tue, 11 Nov 2025 23:32:52 +0100 Subject: [PATCH 01/18] add browser debug port env --- desktop/src/cef/internal/browser_process_app.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/desktop/src/cef/internal/browser_process_app.rs b/desktop/src/cef/internal/browser_process_app.rs index 75fa5a812e..ef5ba2ce34 100644 --- a/desktop/src/cef/internal/browser_process_app.rs +++ b/desktop/src/cef/internal/browser_process_app.rs @@ -72,6 +72,14 @@ impl ImplApp for BrowserProcessAppImpl { // Hide user prompt asking for keychain access cmd.append_switch(Some(&CefString::from("use-mock-keychain"))); } + + // Enable browser debugging via environment variable + if let Some(env) = std::env::var("GRAPHITE_BROWSER_DEBUG_PORT").ok() + && let Some(port) = env.parse::().ok() + { + cmd.append_switch_with_value(Some(&CefString::from("remote-debugging-port")), Some(&CefString::from(port.to_string().as_str()))); + cmd.append_switch_with_value(Some(&CefString::from("remote-allow-origins")), Some(&CefString::from("*"))); + } } } From f7c2f75088bdf588e5ebabd4705cd1b1aedabbb9 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Tue, 11 Nov 2025 23:35:20 +0100 Subject: [PATCH 02/18] mac use option as alt --- desktop/src/window/mac.rs | 6 ++++-- desktop/src/window/win.rs | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/desktop/src/window/mac.rs b/desktop/src/window/mac.rs index 4c4010a8b1..6f931e59e5 100644 --- a/desktop/src/window/mac.rs +++ b/desktop/src/window/mac.rs @@ -1,5 +1,6 @@ use winit::event_loop::ActiveEventLoop; use winit::window::{Window, WindowAttributes}; +use winit::platform::macos::{OptionAsAlt, WindowAttributesMacOS}; use crate::consts::APP_NAME; use crate::event::AppEventScheduler; @@ -11,10 +12,11 @@ pub(super) struct NativeWindowImpl { impl super::NativeWindow for NativeWindowImpl { fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes { - let mac_window = winit::platform::macos::WindowAttributesMacOS::default() + let mac_window = WindowAttributesMacOS::default() .with_titlebar_transparent(true) .with_fullsize_content_view(true) - .with_title_hidden(true); + .with_title_hidden(true) + .with_option_as_alt(OptionAsAlt::Both); attributes.with_platform_attributes(Box::new(mac_window)) } diff --git a/desktop/src/window/win.rs b/desktop/src/window/win.rs index 24ee0ef452..1dfe50aea3 100644 --- a/desktop/src/window/win.rs +++ b/desktop/src/window/win.rs @@ -1,4 +1,6 @@ use winit::event_loop::ActiveEventLoop; +use winit::icon::Icon; +use winit::platform::windows::WinIcon; use winit::window::{Window, WindowAttributes}; use crate::event::AppEventScheduler; @@ -9,8 +11,8 @@ pub(super) struct NativeWindowImpl { impl super::NativeWindow for NativeWindowImpl { fn configure(attributes: WindowAttributes, _event_loop: &dyn ActiveEventLoop) -> WindowAttributes { - if let Ok(win_icon) = winit::platform::windows::WinIcon::from_resource(1, None) { - let icon = winit::icon::Icon(std::sync::Arc::new(win_icon)); + if let Ok(win_icon) = WinIcon::from_resource(1, None) { + let icon = Icon(std::sync::Arc::new(win_icon)); attributes.with_window_icon(Some(icon)) } else { attributes From cbdfe13f1eca620a2300ae1dbb90d15200cfff79 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Tue, 11 Nov 2025 23:36:27 +0100 Subject: [PATCH 03/18] fix cef texture double sRGB conversion by using cef-rs fork with fix --- Cargo.lock | 11 ++++------- Cargo.toml | 5 +++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 94fdbf12a9..6d42046fab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -725,8 +725,7 @@ dependencies = [ [[package]] name = "cef" version = "141.4.0+141.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e05b0ae455c0cf0693da242b3336586cd0057702c0516731d8ba72962559a625" +source = "git+https://github.com/timon-schelling/cef-rs.git?rev=98493a182928f1ff8d5bf8b9eea61483235df75d#98493a182928f1ff8d5bf8b9eea61483235df75d" dependencies = [ "ash", "cef-dll-sys", @@ -744,9 +743,8 @@ dependencies = [ [[package]] name = "cef-dll-sys" -version = "141.6.0+141.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95e419bbb8b2190df8b6eb3934ecc4137533803fcc5ae2b076e591ee789071" +version = "141.4.0+141.0.9" +source = "git+https://github.com/timon-schelling/cef-rs.git?rev=98493a182928f1ff8d5bf8b9eea61483235df75d#98493a182928f1ff8d5bf8b9eea61483235df75d" dependencies = [ "anyhow", "cmake", @@ -1337,8 +1335,7 @@ checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" [[package]] name = "download-cef" version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d98178d9254efef0f69c1f584713d69c790ec00668cd98f783a5085fbefdbddc" +source = "git+https://github.com/timon-schelling/cef-rs.git?rev=98493a182928f1ff8d5bf8b9eea61483235df75d#98493a182928f1ff8d5bf8b9eea61483235df75d" dependencies = [ "bzip2", "clap", diff --git a/Cargo.toml b/Cargo.toml index 91e8fa5d00..586997f56e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -188,8 +188,9 @@ iai-callgrind = { version = "0.16" } ndarray = "0.16" strum = { version = "0.27", features = ["derive"] } dirs = "6.0" -cef = { version = "141", features = ["accelerated_osr"] } -cef-dll-sys = "141" +# TODO: remove fork usage when https://github.com/tauri-apps/cef-rs/pull/272 is merged and published +cef = { git = "https://github.com/timon-schelling/cef-rs.git", rev = "98493a182928f1ff8d5bf8b9eea61483235df75d", features = ["accelerated_osr"] } +cef-dll-sys = { git = "https://github.com/timon-schelling/cef-rs.git", rev = "98493a182928f1ff8d5bf8b9eea61483235df75d" } include_dir = "0.7" tracing-subscriber = { version = "0.3", features = ["env-filter"] } tracing = "0.1" From 10dbc3da34eb14ba7903485574150aeb9d3ae204 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Wed, 12 Nov 2025 15:56:09 +0100 Subject: [PATCH 04/18] fix keyboard input on mac --- desktop/src/cef/input.rs | 83 +++++++++++++++++++++++++--------------- 1 file changed, 52 insertions(+), 31 deletions(-) diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index d2a99d3baa..c1677d4181 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -64,46 +64,39 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat input_state.modifiers_changed(&modifiers.state()); } WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => { - let (named_key, character) = match &event.logical_key { - winit::keyboard::Key::Named(named_key) => ( - Some(named_key), - match named_key { - winit::keyboard::NamedKey::Enter => Some('\u{000d}'), - _ => None, - }, - ), - winit::keyboard::Key::Character(str) => { - let char = str.chars().next().unwrap_or('\0'); + let (named_key, character) = match &event.key_without_modifiers { + winit::keyboard::Key::Named(named_key) => (Some(named_key), std::char::from_u32(named_key.to_vk_bits() as u32)), + winit::keyboard::Key::Character(_) if event.text.is_some() => { + let char = event.text.as_ref().unwrap().chars().next().unwrap_or('\0'); (None, Some(char)) } _ => return, }; + let modifiers = input_state.cef_modifiers(&event.location, event.repeat).raw(); + + let vk_bits = if let Some(named_key) = named_key { + named_key.to_vk_bits() + } else if let Some(char) = character { + char.to_vk_bits() + } else { + 0 + }; + let native_key_code = event.physical_key.to_native_keycode(); - let modifiers = input_state.cef_modifiers(&event.location, event.repeat).raw(); + let Some(host) = browser.host() else { return }; let mut key_event = KeyEvent { size: size_of::(), modifiers, ..Default::default() }; - - if let Some(named_key) = named_key { - key_event.windows_key_code = named_key.to_vk_bits(); - } else if let Some(char) = character { - key_event.windows_key_code = char.to_vk_bits(); - } - key_event.native_key_code = native_key_code; - - let Some(host) = browser.host() else { return }; + key_event.windows_key_code = vk_bits; match event.state { ElementState::Pressed => { - key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN); - host.send_key_event(Some(&key_event)); - if let Some(char) = character { let mut char_key_event = KeyEvent { size: size_of::(), @@ -111,19 +104,43 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat is_system_key: 0, ..Default::default() }; + let mut buf = [0; 2]; char.encode_utf16(&mut buf); - char_key_event.windows_key_code = buf[0] as i32; - char_key_event.character = buf[0]; - char_key_event.native_key_code = native_key_code; - let mut buf = [0; 2]; - char.to_lowercase().next().unwrap().encode_utf16(&mut buf); - char_key_event.unmodified_character = buf[0]; - char_key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR); - host.send_key_event(Some(&char_key_event)); + + key_event.character = buf[0]; + key_event.unmodified_character = buf[0]; + key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN); + + if input_state.is_unmodified() && named_key.is_none() { + #[cfg(not(target_os = "macos"))] // TODO: Understand why this is needed to avoid double keydown events on mac + host.send_key_event(Some(&key_event)); + + char_key_event.native_key_code = native_key_code; + char_key_event.windows_key_code = buf[0] as i32; + char_key_event.character = buf[0]; + char_key_event.unmodified_character = buf[0]; + char_key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR); + host.send_key_event(Some(&char_key_event)); + } else { + host.send_key_event(Some(&key_event)); + } + } else { + key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN); + host.send_key_event(Some(&key_event)); } } ElementState::Released => { + if let Some(char) = character + && input_state.is_unmodified() + { + let mut buf = [0; 2]; + char.encode_utf16(&mut buf); + + key_event.character = buf[0]; + key_event.unmodified_character = buf[0]; + } + key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_KEYUP); host.send_key_event(Some(&key_event)); } @@ -175,6 +192,10 @@ impl InputState { fn cef_modifiers_mouse_event(&self) -> CefModifiers { self.cef_modifiers(&winit::keyboard::KeyLocation::Standard, false) } + + fn is_unmodified(&self) -> bool { + !self.modifiers.control_key() && !self.modifiers.alt_key() && !self.modifiers.meta_key() + } } impl From for CefModifiers { From 8d4bd90bdd65059067f604f358070b88441ca8c1 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Wed, 12 Nov 2025 16:03:14 +0100 Subject: [PATCH 05/18] add missing frontend messages --- frontend/src/messages.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frontend/src/messages.ts b/frontend/src/messages.ts index 45253cb2f5..0661e1f9ab 100644 --- a/frontend/src/messages.ts +++ b/frontend/src/messages.ts @@ -305,6 +305,13 @@ export class UpdateViewportHolePunch extends JsMessage { readonly active!: boolean; } +export class UpdateViewportPhysicalBounds extends JsMessage { + readonly x!: number; + readonly y!: number; + readonly width!: number; + readonly height!: number; +} + export class UpdateInputHints extends JsMessage { @Type(() => HintInfo) readonly hintData!: HintData; @@ -724,6 +731,8 @@ export class UpdateMouseCursor extends JsMessage { export class TriggerLoadFirstAutoSaveDocument extends JsMessage {} export class TriggerLoadRestAutoSaveDocuments extends JsMessage {} +export class TriggerOpenLaunchDocuments extends JsMessage {} + export class TriggerLoadPreferences extends JsMessage {} export class TriggerFetchAndOpenDocument extends JsMessage { @@ -1653,6 +1662,7 @@ export const messageMakers: Record = { TriggerLoadFirstAutoSaveDocument, TriggerLoadPreferences, TriggerLoadRestAutoSaveDocuments, + TriggerOpenLaunchDocuments, TriggerOpenDocument, TriggerPaste, TriggerSaveActiveDocument, @@ -1705,6 +1715,7 @@ export const messageMakers: Record = { UpdateToolOptionsLayout, UpdateToolShelfLayout, UpdateViewportHolePunch, + UpdateViewportPhysicalBounds, UpdateVisibleNodes, UpdateWirePathInProgress, UpdateWorkingColorsLayout, From 4505a13f6910824ed0f323866593d5605f7ef88a Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Wed, 12 Nov 2025 16:30:21 +0100 Subject: [PATCH 06/18] fixup --- desktop/src/window/mac.rs | 2 +- .../viewport/viewport_message_handler.rs | 17 ++++++++++------- frontend/src/io-managers/persistence.ts | 4 ++++ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/desktop/src/window/mac.rs b/desktop/src/window/mac.rs index 6f931e59e5..0c4099f2a8 100644 --- a/desktop/src/window/mac.rs +++ b/desktop/src/window/mac.rs @@ -1,6 +1,6 @@ use winit::event_loop::ActiveEventLoop; -use winit::window::{Window, WindowAttributes}; use winit::platform::macos::{OptionAsAlt, WindowAttributesMacOS}; +use winit::window::{Window, WindowAttributes}; use crate::consts::APP_NAME; use crate::event::AppEventScheduler; diff --git a/editor/src/messages/viewport/viewport_message_handler.rs b/editor/src/messages/viewport/viewport_message_handler.rs index d562917715..91014965a9 100644 --- a/editor/src/messages/viewport/viewport_message_handler.rs +++ b/editor/src/messages/viewport/viewport_message_handler.rs @@ -37,13 +37,16 @@ impl MessageHandler for ViewportMessageHandler { ViewportMessage::RepropagateUpdate => {} } - let physical_bounds = self.bounds().to_physical(); - responses.add(FrontendMessage::UpdateViewportPhysicalBounds { - x: physical_bounds.x(), - y: physical_bounds.y(), - width: physical_bounds.width(), - height: physical_bounds.height(), - }); + #[cfg(not(target_family = "wasm"))] + { + let physical_bounds = self.bounds().to_physical(); + responses.add(FrontendMessage::UpdateViewportPhysicalBounds { + x: physical_bounds.x(), + y: physical_bounds.y(), + width: physical_bounds.width(), + height: physical_bounds.height(), + }); + } responses.add(NavigationMessage::CanvasPan { delta: DVec2::ZERO }); responses.add(NodeGraphMessage::SetGridAlignedEdges); diff --git a/frontend/src/io-managers/persistence.ts b/frontend/src/io-managers/persistence.ts index 4ce45933f2..be9bde6530 100644 --- a/frontend/src/io-managers/persistence.ts +++ b/frontend/src/io-managers/persistence.ts @@ -10,6 +10,7 @@ import { TriggerLoadFirstAutoSaveDocument, TriggerLoadRestAutoSaveDocuments, TriggerSaveActiveDocument, + TriggerOpenLaunchDocuments, } from "@graphite/messages"; import { type PortfolioState } from "@graphite/state-providers/portfolio"; @@ -171,6 +172,9 @@ export function createPersistenceManager(editor: Editor, portfolio: PortfolioSta editor.subscriptions.subscribeJsMessage(TriggerLoadRestAutoSaveDocuments, async () => { await loadRestDocuments(); }); + editor.subscriptions.subscribeJsMessage(TriggerOpenLaunchDocuments, async () => { + // TODO: Could be used to load documents from URL params or similar on launch + }); editor.subscriptions.subscribeJsMessage(TriggerSaveActiveDocument, async (triggerSaveActiveDocument) => { const documentId = String(triggerSaveActiveDocument.documentId); const previouslySavedDocuments = await get>("documents", graphiteStore); From 36a1b749c094df72bd009d3083c47d20c4a805ea Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 13 Nov 2025 03:44:46 +0100 Subject: [PATCH 07/18] fix keyboard input mac --- desktop/src/cef/input.rs | 98 +++++++++++++++++---------------------- desktop/src/window/mac.rs | 2 +- 2 files changed, 43 insertions(+), 57 deletions(-) diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index c1677d4181..4a2d4345a7 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -64,26 +64,16 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat input_state.modifiers_changed(&modifiers.state()); } WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => { - let (named_key, character) = match &event.key_without_modifiers { - winit::keyboard::Key::Named(named_key) => (Some(named_key), std::char::from_u32(named_key.to_vk_bits() as u32)), - winit::keyboard::Key::Character(_) if event.text.is_some() => { - let char = event.text.as_ref().unwrap().chars().next().unwrap_or('\0'); - (None, Some(char)) - } - _ => return, - }; - let modifiers = input_state.cef_modifiers(&event.location, event.repeat).raw(); - let vk_bits = if let Some(named_key) = named_key { - named_key.to_vk_bits() - } else if let Some(char) = character { - char.to_vk_bits() - } else { - 0 - }; - let native_key_code = event.physical_key.to_native_keycode(); + let vk_bits = match &event.key_without_modifiers { + winit::keyboard::Key::Named(named_key) => named_key.to_vk_bits(), + winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or('\0').to_vk_bits(), + _ => return, + }; + let char = event.text_with_all_modifiers.as_ref().map(|text| text.chars().next().unwrap_or('\0').encode_utf16(&mut [0; 2])[0]); + let unmodified_char = event.text.as_ref().map(|text| text.chars().next().unwrap_or('\0').encode_utf16(&mut [0; 2])[0]); let Some(host) = browser.host() else { return }; @@ -92,53 +82,49 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat modifiers, ..Default::default() }; - key_event.native_key_code = native_key_code; - key_event.windows_key_code = vk_bits; + #[cfg(target_os = "macos")] + { + key_event.native_key_code = native_key_code; + key_event.windows_key_code = vk_bits; + } match event.state { ElementState::Pressed => { - if let Some(char) = character { - let mut char_key_event = KeyEvent { - size: size_of::(), - modifiers, - is_system_key: 0, - ..Default::default() - }; - - let mut buf = [0; 2]; - char.encode_utf16(&mut buf); - - key_event.character = buf[0]; - key_event.unmodified_character = buf[0]; - key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN); - - if input_state.is_unmodified() && named_key.is_none() { - #[cfg(not(target_os = "macos"))] // TODO: Understand why this is needed to avoid double keydown events on mac - host.send_key_event(Some(&key_event)); - - char_key_event.native_key_code = native_key_code; - char_key_event.windows_key_code = buf[0] as i32; - char_key_event.character = buf[0]; - char_key_event.unmodified_character = buf[0]; - char_key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR); - host.send_key_event(Some(&char_key_event)); - } else { - host.send_key_event(Some(&key_event)); + let mut char_key_event = KeyEvent { + size: size_of::(), + modifiers, + is_system_key: 0, + ..Default::default() + }; + + key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN); + + if let winit::keyboard::Key::Character(_) = &event.logical_key { + #[cfg(not(target_os = "macos"))] // TODO: Understand why this is needed to avoid double keydown events on mac + host.send_key_event(Some(&key_event)); + + char_key_event.native_key_code = native_key_code; + #[cfg(target_os = "windows")] + { + char_key_event.windows_key_code = char.unwrap_or(0) as i32; + } + #[cfg(not(target_os = "windows"))] + { + char_key_event.windows_key_code = vk_bits; } + char_key_event.character = char.unwrap_or(0); + char_key_event.unmodified_character = unmodified_char.unwrap_or(0); + char_key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR); + host.send_key_event(Some(&char_key_event)); } else { - key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN); host.send_key_event(Some(&key_event)); } } + ElementState::Released => { - if let Some(char) = character - && input_state.is_unmodified() - { - let mut buf = [0; 2]; - char.encode_utf16(&mut buf); - - key_event.character = buf[0]; - key_event.unmodified_character = buf[0]; + if char.is_some() { + key_event.character = char.unwrap(); + key_event.unmodified_character = unmodified_char.unwrap_or(0); } key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_KEYUP); @@ -193,7 +179,7 @@ impl InputState { self.cef_modifiers(&winit::keyboard::KeyLocation::Standard, false) } - fn is_unmodified(&self) -> bool { + fn is_no_mod_pressed(&self) -> bool { !self.modifiers.control_key() && !self.modifiers.alt_key() && !self.modifiers.meta_key() } } diff --git a/desktop/src/window/mac.rs b/desktop/src/window/mac.rs index 0c4099f2a8..c3d38ca6b4 100644 --- a/desktop/src/window/mac.rs +++ b/desktop/src/window/mac.rs @@ -16,7 +16,7 @@ impl super::NativeWindow for NativeWindowImpl { .with_titlebar_transparent(true) .with_fullsize_content_view(true) .with_title_hidden(true) - .with_option_as_alt(OptionAsAlt::Both); + .with_option_as_alt(OptionAsAlt::OnlyLeft); attributes.with_platform_attributes(Box::new(mac_window)) } From 077146621ed27fdb69388171b2feddee195ed79b Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 13 Nov 2025 03:51:05 +0100 Subject: [PATCH 08/18] dbg --- desktop/src/cef/input.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index 4a2d4345a7..f53e031389 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -64,6 +64,7 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat input_state.modifiers_changed(&modifiers.state()); } WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => { + dbg!(event); let modifiers = input_state.cef_modifiers(&event.location, event.repeat).raw(); let native_key_code = event.physical_key.to_native_keycode(); From 9cababc75a50adcfce079d2b7d066ae7051e2bb2 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 13 Nov 2025 17:50:26 +0100 Subject: [PATCH 09/18] re implement keyboard mapping Co-authored-by: csmoe --- desktop/src/cef/input.rs | 94 +++++++++++---------------------- desktop/src/cef/input/keymap.rs | 4 +- 2 files changed, 34 insertions(+), 64 deletions(-) diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index f53e031389..53b59153c5 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -1,5 +1,5 @@ use cef::sys::{cef_event_flags_t, cef_key_event_type_t, cef_mouse_button_type_t}; -use cef::{Browser, ImplBrowser, ImplBrowserHost, KeyEvent, KeyEventType, MouseEvent}; +use cef::{Browser, ImplBrowser, ImplBrowserHost, KeyEvent, MouseEvent}; use std::time::Instant; use winit::dpi::PhysicalPosition; use winit::event::{ButtonSource, ElementState, MouseButton, MouseScrollDelta, WindowEvent}; @@ -64,72 +64,42 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat input_state.modifiers_changed(&modifiers.state()); } WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => { - dbg!(event); - let modifiers = input_state.cef_modifiers(&event.location, event.repeat).raw(); - - let native_key_code = event.physical_key.to_native_keycode(); - let vk_bits = match &event.key_without_modifiers { - winit::keyboard::Key::Named(named_key) => named_key.to_vk_bits(), - winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or('\0').to_vk_bits(), - _ => return, - }; - let char = event.text_with_all_modifiers.as_ref().map(|text| text.chars().next().unwrap_or('\0').encode_utf16(&mut [0; 2])[0]); - let unmodified_char = event.text.as_ref().map(|text| text.chars().next().unwrap_or('\0').encode_utf16(&mut [0; 2])[0]); - let Some(host) = browser.host() else { return }; - let mut key_event = KeyEvent { - size: size_of::(), - modifiers, - ..Default::default() - }; - #[cfg(target_os = "macos")] - { - key_event.native_key_code = native_key_code; - key_event.windows_key_code = vk_bits; - } + let mut key_event = KeyEvent::default(); - match event.state { - ElementState::Pressed => { - let mut char_key_event = KeyEvent { - size: size_of::(), - modifiers, - is_system_key: 0, - ..Default::default() - }; - - key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_RAWKEYDOWN); - - if let winit::keyboard::Key::Character(_) = &event.logical_key { - #[cfg(not(target_os = "macos"))] // TODO: Understand why this is needed to avoid double keydown events on mac - host.send_key_event(Some(&key_event)); - - char_key_event.native_key_code = native_key_code; - #[cfg(target_os = "windows")] - { - char_key_event.windows_key_code = char.unwrap_or(0) as i32; - } - #[cfg(not(target_os = "windows"))] - { - char_key_event.windows_key_code = vk_bits; - } - char_key_event.character = char.unwrap_or(0); - char_key_event.unmodified_character = unmodified_char.unwrap_or(0); - char_key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_CHAR); - host.send_key_event(Some(&char_key_event)); - } else { - host.send_key_event(Some(&key_event)); - } - } + key_event.type_ = match (event.state, &event.logical_key) { + (ElementState::Pressed, winit::keyboard::Key::Character(_)) => cef_key_event_type_t::KEYEVENT_CHAR, + (ElementState::Pressed, _) => cef_key_event_type_t::KEYEVENT_KEYDOWN, + (ElementState::Released, _) => cef_key_event_type_t::KEYEVENT_KEYUP, + } + .into(); - ElementState::Released => { - if char.is_some() { - key_event.character = char.unwrap(); - key_event.unmodified_character = unmodified_char.unwrap_or(0); - } + key_event.modifiers = input_state.cef_modifiers(&event.location, event.repeat).raw(); - key_event.type_ = KeyEventType::from(cef_key_event_type_t::KEYEVENT_KEYUP); - host.send_key_event(Some(&key_event)); + key_event.windows_key_code = match &event.logical_key { + winit::keyboard::Key::Named(named) => named.to_vk_bits(), + winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default() as i32, + _ => 0, + }; + key_event.native_key_code = event.physical_key.to_native_keycode(); + + key_event.character = key_to_char(&event.logical_key) as u16; + key_event.unmodified_character = key_to_char(&event.key_without_modifiers) as u16; + + host.send_key_event(Some(&key_event)); + + fn key_to_char(key: &winit::keyboard::Key) -> char { + match key { + winit::keyboard::Key::Named(named) => match named { + winit::keyboard::NamedKey::Tab => '\t', + winit::keyboard::NamedKey::Enter => '\r', + winit::keyboard::NamedKey::Backspace => '\x08', + winit::keyboard::NamedKey::Escape => '\x1b', + _ => '\0', + }, + winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default(), + _ => '\0', } } } diff --git a/desktop/src/cef/input/keymap.rs b/desktop/src/cef/input/keymap.rs index 44cd56f43e..9ed4696c7d 100644 --- a/desktop/src/cef/input/keymap.rs +++ b/desktop/src/cef/input/keymap.rs @@ -8,11 +8,11 @@ impl ToNativeKeycode for winit::keyboard::PhysicalKey { #[cfg(target_os = "linux")] { - self.to_scancode().map(|evdev| (evdev + 8) as i32).unwrap_or(0) + self.to_scancode().map(|evdev| (evdev + 8) as i32).unwrap_or_default() } #[cfg(any(target_os = "macos", target_os = "windows"))] { - self.to_scancode().map(|c| c as i32).unwrap_or(0) + self.to_scancode().map(|c| c as i32).unwrap_or_default() } } } From feae93590bce758391921217d9e040dadce34c55 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 13 Nov 2025 19:08:43 +0100 Subject: [PATCH 10/18] Fix double arrow keys --- desktop/src/cef/input.rs | 10 +++++++++- desktop/src/cef/input/keymap.rs | 4 ++-- desktop/src/window/mac.rs | 5 ++--- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index 53b59153c5..d42cf4bb49 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -70,7 +70,7 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat key_event.type_ = match (event.state, &event.logical_key) { (ElementState::Pressed, winit::keyboard::Key::Character(_)) => cef_key_event_type_t::KEYEVENT_CHAR, - (ElementState::Pressed, _) => cef_key_event_type_t::KEYEVENT_KEYDOWN, + (ElementState::Pressed, _) => cef_key_event_type_t::KEYEVENT_RAWKEYDOWN, (ElementState::Released, _) => cef_key_event_type_t::KEYEVENT_KEYUP, } .into(); @@ -87,6 +87,14 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat key_event.character = key_to_char(&event.logical_key) as u16; key_event.unmodified_character = key_to_char(&event.key_without_modifiers) 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 + && let Some(text) = &event.text_with_all_modifiers + { + key_event.character = text.chars().next().unwrap_or_default() as u16; + } + host.send_key_event(Some(&key_event)); fn key_to_char(key: &winit::keyboard::Key) -> char { diff --git a/desktop/src/cef/input/keymap.rs b/desktop/src/cef/input/keymap.rs index 9ed4696c7d..0e8d57812f 100644 --- a/desktop/src/cef/input/keymap.rs +++ b/desktop/src/cef/input/keymap.rs @@ -49,10 +49,10 @@ impl ToVKBits for winit::keyboard::NamedKey { (0x5B, Meta), (0x0D, Enter), (0x09, Tab), - (0x28, ArrowDown), (0x25, ArrowLeft), - (0x27, ArrowRight), (0x26, ArrowUp), + (0x27, ArrowRight), + (0x28, ArrowDown), (0x23, End), (0x24, Home), (0x22, PageDown), diff --git a/desktop/src/window/mac.rs b/desktop/src/window/mac.rs index c3d38ca6b4..f6cd4330d0 100644 --- a/desktop/src/window/mac.rs +++ b/desktop/src/window/mac.rs @@ -1,5 +1,5 @@ use winit::event_loop::ActiveEventLoop; -use winit::platform::macos::{OptionAsAlt, WindowAttributesMacOS}; +use winit::platform::macos::WindowAttributesMacOS; use winit::window::{Window, WindowAttributes}; use crate::consts::APP_NAME; @@ -15,8 +15,7 @@ impl super::NativeWindow for NativeWindowImpl { let mac_window = WindowAttributesMacOS::default() .with_titlebar_transparent(true) .with_fullsize_content_view(true) - .with_title_hidden(true) - .with_option_as_alt(OptionAsAlt::OnlyLeft); + .with_title_hidden(true); attributes.with_platform_attributes(Box::new(mac_window)) } From e3e3655c61bf6c799157c76439c0c8f3d125d62a Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 13 Nov 2025 21:01:34 +0000 Subject: [PATCH 11/18] try fix for non mac --- desktop/src/cef/input.rs | 40 +++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index d42cf4bb49..62b1cedf4a 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -66,33 +66,43 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat WindowEvent::KeyboardInput { device_id: _, event, is_synthetic: _ } => { let Some(host) = browser.host() else { return }; - let mut key_event = KeyEvent::default(); - - key_event.type_ = match (event.state, &event.logical_key) { - (ElementState::Pressed, winit::keyboard::Key::Character(_)) => cef_key_event_type_t::KEYEVENT_CHAR, - (ElementState::Pressed, _) => cef_key_event_type_t::KEYEVENT_RAWKEYDOWN, - (ElementState::Released, _) => cef_key_event_type_t::KEYEVENT_KEYUP, - } - .into(); + let mut key_event = KeyEvent { + type_: match (event.state, &event.logical_key) { + (ElementState::Pressed, winit::keyboard::Key::Character(_)) => cef_key_event_type_t::KEYEVENT_CHAR, + (ElementState::Pressed, _) => cef_key_event_type_t::KEYEVENT_RAWKEYDOWN, + (ElementState::Released, _) => cef_key_event_type_t::KEYEVENT_KEYUP, + } + .into(), + ..Default::default() + }; key_event.modifiers = input_state.cef_modifiers(&event.location, event.repeat).raw(); key_event.windows_key_code = match &event.logical_key { winit::keyboard::Key::Named(named) => named.to_vk_bits(), - winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default() as i32, + winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default().to_vk_bits(), _ => 0, }; + key_event.native_key_code = event.physical_key.to_native_keycode(); key_event.character = key_to_char(&event.logical_key) as u16; key_event.unmodified_character = key_to_char(&event.key_without_modifiers) 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 - && let Some(text) = &event.text_with_all_modifiers + if key_event.character == 0 && key_event.unmodified_character == 0 { + key_event.character = 1; + } + + #[cfg(not(target_os = "macos"))] { - key_event.character = text.chars().next().unwrap_or_default() as u16; + if key_event.type_ == cef_key_event_type_t::KEYEVENT_CHAR.into() { + let mut key_down_event = key_event.clone(); + key_down_event.type_ = cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into(); + host.send_key_event(Some(&key_down_event)); + + key_event.windows_key_code = key_to_char(&event.logical_key) as i32; + } } host.send_key_event(Some(&key_event)); @@ -157,10 +167,6 @@ impl InputState { fn cef_modifiers_mouse_event(&self) -> CefModifiers { self.cef_modifiers(&winit::keyboard::KeyLocation::Standard, false) } - - fn is_no_mod_pressed(&self) -> bool { - !self.modifiers.control_key() && !self.modifiers.alt_key() && !self.modifiers.meta_key() - } } impl From for CefModifiers { From c7cde9c597e24ef7270ccc69c93a382939a2ce0f Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 13 Nov 2025 21:08:15 +0000 Subject: [PATCH 12/18] test --- desktop/src/cef/input.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index 62b1cedf4a..055e51be31 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -80,6 +80,9 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat key_event.windows_key_code = match &event.logical_key { winit::keyboard::Key::Named(named) => named.to_vk_bits(), + #[cfg(target_os = "macos")] + winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default() as i32, + #[cfg(not(target_os = "macos"))] winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default().to_vk_bits(), _ => 0, }; From b8157ad1b12f0a8c4143405837aba6dc282c77cc Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 13 Nov 2025 22:10:48 +0100 Subject: [PATCH 13/18] Revert "test" This reverts commit c7cde9c597e24ef7270ccc69c93a382939a2ce0f. --- desktop/src/cef/input.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index 055e51be31..62b1cedf4a 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -80,9 +80,6 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat key_event.windows_key_code = match &event.logical_key { winit::keyboard::Key::Named(named) => named.to_vk_bits(), - #[cfg(target_os = "macos")] - winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default() as i32, - #[cfg(not(target_os = "macos"))] winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default().to_vk_bits(), _ => 0, }; From 2d5313aa042dd676ecd11f81cde03d23ceb6de4b Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Thu, 13 Nov 2025 22:32:51 +0100 Subject: [PATCH 14/18] fix mac --- desktop/src/cef/input.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index 62b1cedf4a..6b881ffcd9 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -90,8 +90,8 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat key_event.unmodified_character = key_to_char(&event.key_without_modifiers) 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 { - key_event.character = 1; + if key_event.character == 0 && key_event.unmodified_character == 0 && event.text_with_all_modifiers.is_some() { + key_event.unmodified_character = 1; } #[cfg(not(target_os = "macos"))] From 0d104b4602add3b949b6a58fb2622fa9cc291886 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 14 Nov 2025 00:11:22 +0000 Subject: [PATCH 15/18] some cleanup --- desktop/src/cef/input.rs | 269 ++------------------------------ desktop/src/cef/input/keymap.rs | 30 +++- desktop/src/cef/input/state.rs | 242 ++++++++++++++++++++++++++++ 3 files changed, 277 insertions(+), 264 deletions(-) create mode 100644 desktop/src/cef/input/state.rs diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index 6b881ffcd9..3aa78b78fa 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -1,29 +1,30 @@ use cef::sys::{cef_event_flags_t, cef_key_event_type_t, cef_mouse_button_type_t}; use cef::{Browser, ImplBrowser, ImplBrowserHost, KeyEvent, MouseEvent}; -use std::time::Instant; -use winit::dpi::PhysicalPosition; use winit::event::{ButtonSource, ElementState, MouseButton, MouseScrollDelta, WindowEvent}; mod keymap; -use keymap::{ToNativeKeycode, ToVKBits}; +use keymap::{ToCharRepresentation, ToNativeKeycode, ToVKBits}; -use super::consts::{MULTICLICK_ALLOWED_TRAVEL, MULTICLICK_TIMEOUT, PINCH_ZOOM_SPEED, SCROLL_LINE_HEIGHT, SCROLL_LINE_WIDTH, SCROLL_SPEED_X, SCROLL_SPEED_Y}; +mod state; +pub(crate) use state::InputState; + +use super::consts::{PINCH_ZOOM_SPEED, SCROLL_LINE_HEIGHT, SCROLL_LINE_WIDTH, SCROLL_SPEED_X, SCROLL_SPEED_Y}; pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputState, event: &WindowEvent) { match event { WindowEvent::PointerMoved { position, .. } | WindowEvent::PointerEntered { position, .. } => { - input_state.cursor_move(&position); + input_state.cursor_move(position); let Some(host) = browser.host() else { return }; host.send_mouse_move_event(Some(&input_state.into()), 0); } WindowEvent::PointerLeft { position, .. } => { if let Some(position) = position { - input_state.cursor_move(&position); + input_state.cursor_move(position); } let Some(host) = browser.host() else { return }; - host.send_mouse_move_event(Some(&input_state.into()), 1); + host.send_mouse_move_event(Some(&(input_state.into())), 1); } WindowEvent::PointerButton { state, button, .. } => { let mouse_button = match button { @@ -86,8 +87,8 @@ 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 = key_to_char(&event.logical_key) as u16; - key_event.unmodified_character = key_to_char(&event.key_without_modifiers) as u16; + key_event.character = event.logical_key.to_char_representation() as u16; + 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() { @@ -101,25 +102,11 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat key_down_event.type_ = cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into(); host.send_key_event(Some(&key_down_event)); - key_event.windows_key_code = key_to_char(&event.logical_key) as i32; + key_event.windows_key_code = event.logical_key.to_char_representation() as i32; } } host.send_key_event(Some(&key_event)); - - fn key_to_char(key: &winit::keyboard::Key) -> char { - match key { - winit::keyboard::Key::Named(named) => match named { - winit::keyboard::NamedKey::Tab => '\t', - winit::keyboard::NamedKey::Enter => '\r', - winit::keyboard::NamedKey::Backspace => '\x08', - winit::keyboard::NamedKey::Escape => '\x1b', - _ => '\0', - }, - winit::keyboard::Key::Character(char) => char.chars().next().unwrap_or_default(), - _ => '\0', - } - } } WindowEvent::PinchGesture { delta, .. } => { if !delta.is_normal() { @@ -138,237 +125,3 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat _ => {} } } - -#[derive(Default)] -pub(crate) struct InputState { - modifiers: winit::keyboard::ModifiersState, - mouse_position: MousePosition, - mouse_state: MouseState, - mouse_click_tracker: ClickTracker, -} -impl InputState { - fn modifiers_changed(&mut self, modifiers: &winit::keyboard::ModifiersState) { - self.modifiers = *modifiers; - } - - fn cursor_move(&mut self, position: &PhysicalPosition) { - self.mouse_position = position.into(); - } - - fn mouse_input(&mut self, button: &MouseButton, state: &ElementState) -> ClickCount { - self.mouse_state.update(button, state); - self.mouse_click_tracker.input(button, state, self.mouse_position) - } - - fn cef_modifiers(&self, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> CefModifiers { - CefModifiers::new(self, location, is_repeat) - } - - fn cef_modifiers_mouse_event(&self) -> CefModifiers { - self.cef_modifiers(&winit::keyboard::KeyLocation::Standard, false) - } -} - -impl From for CefModifiers { - fn from(val: InputState) -> Self { - CefModifiers::new(&val, &winit::keyboard::KeyLocation::Standard, false) - } -} -impl From<&InputState> for MouseEvent { - fn from(val: &InputState) -> Self { - MouseEvent { - x: val.mouse_position.x as i32, - y: val.mouse_position.y as i32, - modifiers: val.cef_modifiers_mouse_event().raw(), - } - } -} -impl From<&mut InputState> for MouseEvent { - fn from(val: &mut InputState) -> Self { - MouseEvent { - x: val.mouse_position.x as i32, - y: val.mouse_position.y as i32, - modifiers: val.cef_modifiers_mouse_event().raw(), - } - } -} - -#[derive(Default, Clone, Copy)] -pub(crate) struct MousePosition { - x: usize, - y: usize, -} -impl From<&PhysicalPosition> for MousePosition { - fn from(position: &PhysicalPosition) -> Self { - Self { - x: position.x as usize, - y: position.y as usize, - } - } -} - -#[derive(Default, Clone)] -pub(crate) struct MouseState { - left: bool, - right: bool, - middle: bool, -} -impl MouseState { - pub(crate) fn update(&mut self, button: &MouseButton, state: &ElementState) { - match state { - ElementState::Pressed => match button { - MouseButton::Left => self.left = true, - MouseButton::Right => self.right = true, - MouseButton::Middle => self.middle = true, - _ => {} - }, - ElementState::Released => match button { - MouseButton::Left => self.left = false, - MouseButton::Right => self.right = false, - MouseButton::Middle => self.middle = false, - _ => {} - }, - } - } -} - -#[derive(Default)] -struct ClickTracker { - left: Option, - middle: Option, - right: Option, -} -impl ClickTracker { - fn input(&mut self, button: &MouseButton, state: &ElementState, position: MousePosition) -> ClickCount { - let record = match button { - MouseButton::Left => &mut self.left, - MouseButton::Right => &mut self.right, - MouseButton::Middle => &mut self.middle, - _ => return ClickCount::Single, - }; - - let Some(record) = record else { - *record = Some(ClickRecord { position, ..Default::default() }); - return ClickCount::Single; - }; - - let prev_time = record.time; - let prev_position = record.position; - - let now = Instant::now(); - record.time = now; - record.position = position; - - match state { - ElementState::Pressed if record.down_count == ClickCount::Double => { - *record = ClickRecord { - down_count: ClickCount::Single, - ..*record - }; - return ClickCount::Single; - } - ElementState::Released if record.up_count == ClickCount::Double => { - *record = ClickRecord { - up_count: ClickCount::Single, - ..*record - }; - return ClickCount::Single; - } - _ => {} - } - - let dx = position.x.abs_diff(prev_position.x); - let dy = position.y.abs_diff(prev_position.y); - let within_dist = dx <= MULTICLICK_ALLOWED_TRAVEL && dy <= MULTICLICK_ALLOWED_TRAVEL; - let within_time = now.saturating_duration_since(prev_time) <= MULTICLICK_TIMEOUT; - - let count = if within_time && within_dist { ClickCount::Double } else { ClickCount::Single }; - - *record = match state { - ElementState::Pressed => ClickRecord { down_count: count, ..*record }, - ElementState::Released => ClickRecord { up_count: count, ..*record }, - }; - count - } -} - -#[derive(Clone, Copy, PartialEq, Default)] -enum ClickCount { - #[default] - Single, - Double, -} -impl From for i32 { - fn from(count: ClickCount) -> i32 { - match count { - ClickCount::Single => 1, - ClickCount::Double => 2, - } - } -} - -#[derive(Clone, Copy)] -struct ClickRecord { - time: Instant, - position: MousePosition, - down_count: ClickCount, - up_count: ClickCount, -} - -impl Default for ClickRecord { - fn default() -> Self { - Self { - time: Instant::now(), - position: Default::default(), - down_count: Default::default(), - up_count: Default::default(), - } - } -} - -struct CefModifiers(u32); -impl CefModifiers { - fn new(input_state: &InputState, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> Self { - let mut inner = 0; - - if input_state.modifiers.shift_key() { - inner |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN as u32; - } - if input_state.modifiers.control_key() { - inner |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN as u32; - } - if input_state.modifiers.alt_key() { - inner |= cef_event_flags_t::EVENTFLAG_ALT_DOWN as u32; - } - if input_state.modifiers.meta_key() { - inner |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN as u32; - } - - if input_state.mouse_state.left { - inner |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON as u32; - } - if input_state.mouse_state.right { - inner |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON as u32; - } - if input_state.mouse_state.middle { - inner |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON as u32; - } - - if is_repeat { - inner |= cef_event_flags_t::EVENTFLAG_IS_REPEAT as u32; - } - - inner |= match location { - winit::keyboard::KeyLocation::Left => cef_event_flags_t::EVENTFLAG_IS_LEFT as u32, - winit::keyboard::KeyLocation::Right => cef_event_flags_t::EVENTFLAG_IS_RIGHT as u32, - winit::keyboard::KeyLocation::Numpad => cef_event_flags_t::EVENTFLAG_IS_KEY_PAD as u32, - winit::keyboard::KeyLocation::Standard => 0, - }; - - Self(inner) - } - - fn raw(&self) -> u32 { - self.0 - } -} diff --git a/desktop/src/cef/input/keymap.rs b/desktop/src/cef/input/keymap.rs index 0e8d57812f..17816fb694 100644 --- a/desktop/src/cef/input/keymap.rs +++ b/desktop/src/cef/input/keymap.rs @@ -1,8 +1,30 @@ -pub trait ToNativeKeycode { +use winit::keyboard::{Key, NamedKey, PhysicalKey}; + +pub(crate) trait ToCharRepresentation { + fn to_char_representation(&self) -> char; +} + +impl ToCharRepresentation for Key { + fn to_char_representation(&self) -> char { + match self { + Key::Named(named) => match named { + NamedKey::Tab => '\t', + NamedKey::Enter => '\r', + NamedKey::Backspace => '\x08', + NamedKey::Escape => '\x1b', + _ => '\0', + }, + Key::Character(char) => char.chars().next().unwrap_or_default(), + _ => '\0', + } + } +} + +pub(crate) trait ToNativeKeycode { fn to_native_keycode(&self) -> i32; } -impl ToNativeKeycode for winit::keyboard::PhysicalKey { +impl ToNativeKeycode for PhysicalKey { fn to_native_keycode(&self) -> i32 { use winit::platform::scancode::PhysicalKeyExtScancode; @@ -17,7 +39,6 @@ impl ToNativeKeycode for winit::keyboard::PhysicalKey { } } -// Windows Virtual keyboard binary representation pub(crate) trait ToVKBits { fn to_vk_bits(&self) -> i32; } @@ -32,10 +53,8 @@ macro_rules! map_enum { } }; } - impl ToVKBits for winit::keyboard::NamedKey { fn to_vk_bits(&self) -> i32 { - use winit::keyboard::NamedKey; map_enum!( self, NamedKey, @@ -152,7 +171,6 @@ macro_rules! map { } }; } - impl ToVKBits for char { fn to_vk_bits(&self) -> i32 { map!( diff --git a/desktop/src/cef/input/state.rs b/desktop/src/cef/input/state.rs new file mode 100644 index 0000000000..52abead503 --- /dev/null +++ b/desktop/src/cef/input/state.rs @@ -0,0 +1,242 @@ +use cef::MouseEvent; +use std::time::Instant; +use winit::dpi::PhysicalPosition; +use winit::event::{ElementState, MouseButton}; + +use crate::cef::consts::{MULTICLICK_ALLOWED_TRAVEL, MULTICLICK_TIMEOUT}; + +#[derive(Default)] +pub(crate) struct InputState { + modifiers: winit::keyboard::ModifiersState, + mouse_position: MousePosition, + mouse_state: MouseState, + mouse_click_tracker: ClickTracker, +} +impl InputState { + pub(crate) fn modifiers_changed(&mut self, modifiers: &winit::keyboard::ModifiersState) { + self.modifiers = *modifiers; + } + + pub(crate) fn cursor_move(&mut self, position: &PhysicalPosition) { + self.mouse_position = position.into(); + } + + pub(crate) fn mouse_input(&mut self, button: &MouseButton, state: &ElementState) -> ClickCount { + self.mouse_state.update(button, state); + self.mouse_click_tracker.input(button, state, self.mouse_position) + } + + pub(crate) fn cef_modifiers(&self, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> CefModifiers { + CefModifiers::new(self, location, is_repeat) + } + + pub(crate) fn cef_mouse_modifiers(&self) -> CefModifiers { + self.cef_modifiers(&winit::keyboard::KeyLocation::Standard, false) + } +} + +impl From for CefModifiers { + fn from(val: InputState) -> Self { + CefModifiers::new(&val, &winit::keyboard::KeyLocation::Standard, false) + } +} +impl From<&InputState> for MouseEvent { + fn from(val: &InputState) -> Self { + MouseEvent { + x: val.mouse_position.x as i32, + y: val.mouse_position.y as i32, + modifiers: val.cef_mouse_modifiers().raw(), + } + } +} +impl From<&mut InputState> for MouseEvent { + fn from(val: &mut InputState) -> Self { + MouseEvent { + x: val.mouse_position.x as i32, + y: val.mouse_position.y as i32, + modifiers: val.cef_mouse_modifiers().raw(), + } + } +} + +#[derive(Default, Clone, Copy)] +pub(crate) struct MousePosition { + x: usize, + y: usize, +} +impl From<&PhysicalPosition> for MousePosition { + fn from(position: &PhysicalPosition) -> Self { + Self { + x: position.x as usize, + y: position.y as usize, + } + } +} + +#[derive(Default, Clone)] +pub(crate) struct MouseState { + left: bool, + right: bool, + middle: bool, +} +impl MouseState { + pub(crate) fn update(&mut self, button: &MouseButton, state: &ElementState) { + match state { + ElementState::Pressed => match button { + MouseButton::Left => self.left = true, + MouseButton::Right => self.right = true, + MouseButton::Middle => self.middle = true, + _ => {} + }, + ElementState::Released => match button { + MouseButton::Left => self.left = false, + MouseButton::Right => self.right = false, + MouseButton::Middle => self.middle = false, + _ => {} + }, + } + } +} + +#[derive(Default)] +struct ClickTracker { + left: Option, + middle: Option, + right: Option, +} +impl ClickTracker { + fn input(&mut self, button: &MouseButton, state: &ElementState, position: MousePosition) -> ClickCount { + let record = match button { + MouseButton::Left => &mut self.left, + MouseButton::Right => &mut self.right, + MouseButton::Middle => &mut self.middle, + _ => return ClickCount::Single, + }; + + let Some(record) = record else { + *record = Some(ClickRecord { position, ..Default::default() }); + return ClickCount::Single; + }; + + let prev_time = record.time; + let prev_position = record.position; + + let now = Instant::now(); + record.time = now; + record.position = position; + + match state { + ElementState::Pressed if record.down_count == ClickCount::Double => { + *record = ClickRecord { + down_count: ClickCount::Single, + ..*record + }; + return ClickCount::Single; + } + ElementState::Released if record.up_count == ClickCount::Double => { + *record = ClickRecord { + up_count: ClickCount::Single, + ..*record + }; + return ClickCount::Single; + } + _ => {} + } + + let dx = position.x.abs_diff(prev_position.x); + let dy = position.y.abs_diff(prev_position.y); + let within_dist = dx <= MULTICLICK_ALLOWED_TRAVEL && dy <= MULTICLICK_ALLOWED_TRAVEL; + let within_time = now.saturating_duration_since(prev_time) <= MULTICLICK_TIMEOUT; + + let count = if within_time && within_dist { ClickCount::Double } else { ClickCount::Single }; + + *record = match state { + ElementState::Pressed => ClickRecord { down_count: count, ..*record }, + ElementState::Released => ClickRecord { up_count: count, ..*record }, + }; + count + } +} + +#[derive(Clone, Copy, PartialEq, Default)] +pub(crate) enum ClickCount { + #[default] + Single, + Double, +} +impl From for i32 { + fn from(count: ClickCount) -> i32 { + match count { + ClickCount::Single => 1, + ClickCount::Double => 2, + } + } +} + +#[derive(Clone, Copy)] +struct ClickRecord { + time: Instant, + position: MousePosition, + down_count: ClickCount, + up_count: ClickCount, +} + +impl Default for ClickRecord { + fn default() -> Self { + Self { + time: Instant::now(), + position: Default::default(), + down_count: Default::default(), + up_count: Default::default(), + } + } +} + +pub(crate) struct CefModifiers(u32); +impl CefModifiers { + fn new(input_state: &InputState, location: &winit::keyboard::KeyLocation, is_repeat: bool) -> Self { + use cef::sys::cef_event_flags_t; + + let mut inner = 0; + + if input_state.modifiers.shift_key() { + inner |= cef_event_flags_t::EVENTFLAG_SHIFT_DOWN as u32; + } + if input_state.modifiers.control_key() { + inner |= cef_event_flags_t::EVENTFLAG_CONTROL_DOWN as u32; + } + if input_state.modifiers.alt_key() { + inner |= cef_event_flags_t::EVENTFLAG_ALT_DOWN as u32; + } + if input_state.modifiers.meta_key() { + inner |= cef_event_flags_t::EVENTFLAG_COMMAND_DOWN as u32; + } + + if input_state.mouse_state.left { + inner |= cef_event_flags_t::EVENTFLAG_LEFT_MOUSE_BUTTON as u32; + } + if input_state.mouse_state.right { + inner |= cef_event_flags_t::EVENTFLAG_RIGHT_MOUSE_BUTTON as u32; + } + if input_state.mouse_state.middle { + inner |= cef_event_flags_t::EVENTFLAG_MIDDLE_MOUSE_BUTTON as u32; + } + + if is_repeat { + inner |= cef_event_flags_t::EVENTFLAG_IS_REPEAT as u32; + } + + inner |= match location { + winit::keyboard::KeyLocation::Left => cef_event_flags_t::EVENTFLAG_IS_LEFT as u32, + winit::keyboard::KeyLocation::Right => cef_event_flags_t::EVENTFLAG_IS_RIGHT as u32, + winit::keyboard::KeyLocation::Numpad => cef_event_flags_t::EVENTFLAG_IS_KEY_PAD as u32, + winit::keyboard::KeyLocation::Standard => 0, + }; + + Self(inner) + } + + pub(crate) fn raw(&self) -> u32 { + self.0 + } +} From 1a922048887d659ca2e3e594eddaa1e0bf82d296 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Fri, 14 Nov 2025 03:36:33 +0100 Subject: [PATCH 16/18] fix zoom in shortcut on mac introduced in #3377 --- desktop/wrapper/src/intercept_editor_message.rs | 2 +- desktop/wrapper/src/intercept_frontend_message.rs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/desktop/wrapper/src/intercept_editor_message.rs b/desktop/wrapper/src/intercept_editor_message.rs index 0f3bf93967..933de19b95 100644 --- a/desktop/wrapper/src/intercept_editor_message.rs +++ b/desktop/wrapper/src/intercept_editor_message.rs @@ -2,6 +2,6 @@ use super::DesktopWrapperMessageDispatcher; use super::messages::EditorMessage; pub(super) fn intercept_editor_message(_dispatcher: &mut DesktopWrapperMessageDispatcher, message: EditorMessage) -> Option { - // TODO: remove it turns out to be unnecessary + // TODO: remove if it turns out to be unnecessary Some(message) } diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index 97adb05cc6..dedc61d230 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -321,6 +321,7 @@ fn convert_layout_keys_to_shortcut(layout_keys: &Vec) -> Option key = Some(KeyCode::ScrollLock), Key::Pause => key = Some(KeyCode::Pause), Key::Unidentified => key = Some(KeyCode::Unidentified), + Key::FakeKeyPlus => key = Some(KeyCode::Equal), _ => key = None, } } From c15e11653bdef275942c971d4939d9755ac92d46 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Sat, 15 Nov 2025 12:23:53 +0100 Subject: [PATCH 17/18] disable menu shortcut display --- desktop/src/cef/input.rs | 17 ++++++++--------- .../wrapper/src/intercept_frontend_message.rs | 3 ++- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index 3aa78b78fa..6cbef09fb9 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -95,16 +95,15 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat key_event.unmodified_character = 1; } - #[cfg(not(target_os = "macos"))] - { - if key_event.type_ == cef_key_event_type_t::KEYEVENT_CHAR.into() { - let mut key_down_event = key_event.clone(); - key_down_event.type_ = cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into(); - host.send_key_event(Some(&key_down_event)); - - key_event.windows_key_code = event.logical_key.to_char_representation() as i32; - } + + if key_event.type_ == cef_key_event_type_t::KEYEVENT_CHAR.into() { + let mut key_down_event = key_event.clone(); + key_down_event.type_ = cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into(); + host.send_key_event(Some(&key_down_event)); + + key_event.windows_key_code = event.logical_key.to_char_representation() as i32; } + host.send_key_event(Some(&key_event)); } diff --git a/desktop/wrapper/src/intercept_frontend_message.rs b/desktop/wrapper/src/intercept_frontend_message.rs index dedc61d230..1c4d1991b6 100644 --- a/desktop/wrapper/src/intercept_frontend_message.rs +++ b/desktop/wrapper/src/intercept_frontend_message.rs @@ -164,7 +164,8 @@ fn convert_menu_bar_entry_to_menu_item( } let shortcut = match shortcut { - Some(ActionKeys::Keys(LayoutKeysGroup(keys))) => convert_layout_keys_to_shortcut(keys), + //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), _ => None, }; From b08f5c58343c992c5257d9a2032e39c1ba5f8824 Mon Sep 17 00:00:00 2001 From: Timon Schelling Date: Sat, 15 Nov 2025 19:20:57 +0100 Subject: [PATCH 18/18] fixup --- Cargo.lock | 39 ++++----------------------------------- Cargo.toml | 2 +- desktop/Cargo.toml | 22 ++-------------------- desktop/src/cef/input.rs | 2 -- 4 files changed, 7 insertions(+), 58 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6d42046fab..1d7599a62b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -731,7 +731,7 @@ dependencies = [ "cef-dll-sys", "libc", "libloading", - "metal 0.32.0", + "metal", "objc", "objc2-io-surface", "thiserror 2.0.16", @@ -1014,17 +1014,6 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation 0.9.4", - "libc", -] - [[package]] name = "core-graphics-types" version = "0.2.0" @@ -2299,22 +2288,17 @@ dependencies = [ name = "graphite-desktop" version = "0.1.0" dependencies = [ - "ash", "bytemuck", "cef", "cef-dll-sys", "clap", - "core-foundation 0.10.1", "derivative", "dirs", "futures", "glam", "graphite-desktop-embedded-resources", "graphite-desktop-wrapper", - "libc", - "metal 0.31.0", "muda", - "objc", "open", "rand 0.9.2", "rfd", @@ -3419,21 +3403,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "metal" -version = "0.31.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f569fb946490b5743ad69813cb19629130ce9374034abe31614a36402d18f99e" -dependencies = [ - "bitflags 2.9.3", - "block", - "core-graphics-types 0.1.3", - "foreign-types 0.5.0", - "log", - "objc", - "paste", -] - [[package]] name = "metal" version = "0.32.0" @@ -3442,7 +3411,7 @@ checksum = "00c15a6f673ff72ddcc22394663290f870fb224c1bfce55734a75c414150e605" dependencies = [ "bitflags 2.9.3", "block", - "core-graphics-types 0.2.0", + "core-graphics-types", "foreign-types 0.5.0", "log", "objc", @@ -6890,7 +6859,7 @@ dependencies = [ "bytemuck", "cfg-if", "cfg_aliases", - "core-graphics-types 0.2.0", + "core-graphics-types", "glow", "glutin_wgl_sys", "gpu-alloc", @@ -6902,7 +6871,7 @@ dependencies = [ "libc", "libloading", "log", - "metal 0.32.0", + "metal", "naga 27.0.3", "ndk-sys", "objc", diff --git a/Cargo.toml b/Cargo.toml index 586997f56e..1f0250b878 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -189,7 +189,7 @@ ndarray = "0.16" strum = { version = "0.27", features = ["derive"] } dirs = "6.0" # TODO: remove fork usage when https://github.com/tauri-apps/cef-rs/pull/272 is merged and published -cef = { git = "https://github.com/timon-schelling/cef-rs.git", rev = "98493a182928f1ff8d5bf8b9eea61483235df75d", features = ["accelerated_osr"] } +cef = { git = "https://github.com/timon-schelling/cef-rs.git", rev = "98493a182928f1ff8d5bf8b9eea61483235df75d" } cef-dll-sys = { git = "https://github.com/timon-schelling/cef-rs.git", rev = "98493a182928f1ff8d5bf8b9eea61483235df75d" } include_dir = "0.7" tracing-subscriber = { version = "0.3", features = ["env-filter"] } diff --git a/desktop/Cargo.toml b/desktop/Cargo.toml index a45b091ac4..1ab391fc59 100644 --- a/desktop/Cargo.toml +++ b/desktop/Cargo.toml @@ -13,12 +13,7 @@ default = ["recommended", "embedded_resources"] recommended = ["gpu", "accelerated_paint"] embedded_resources = ["dep:graphite-desktop-embedded-resources"] gpu = ["graphite-desktop-wrapper/gpu"] - -# Hardware acceleration features -accelerated_paint = ["accelerated_paint_dmabuf", "accelerated_paint_d3d11", "accelerated_paint_iosurface"] -accelerated_paint_dmabuf = ["libc", "ash"] -accelerated_paint_d3d11 = ["windows", "ash"] -accelerated_paint_iosurface = ["metal", "objc", "core-foundation"] +accelerated_paint = ["cef/accelerated_osr"] [dependencies] # Local dependencies @@ -45,32 +40,19 @@ rand = { workspace = true, features = ["thread_rng"] } serde = { workspace = true } clap = { workspace = true, features = ["derive"] } -# Hardware acceleration dependencies -ash = { version = "0.38", optional = true } - # Windows-specific dependencies [target.'cfg(target_os = "windows")'.dependencies] windows = { version = "0.58.0", features = [ "Win32_Foundation", - "Win32_Graphics_Direct3D11", - "Win32_Graphics_Direct3D12", - "Win32_Graphics_Dxgi", - "Win32_Graphics_Dxgi_Common", "Win32_Graphics_Dwm", "Win32_Graphics_Gdi", "Win32_System_LibraryLoader", "Win32_UI_Controls", "Win32_UI_WindowsAndMessaging", "Win32_UI_HiDpi", -], optional = true } +] } # macOS-specific dependencies [target.'cfg(target_os = "macos")'.dependencies] muda = { git = "https://github.com/tauri-apps/muda.git", rev = "3f460b8fbaed59cda6d95ceea6904f000f093f15", default-features = false } -metal = { version = "0.31.0", optional = true } -objc = { version = "0.2", optional = true } -core-foundation = { version = "0.10", optional = true } -# Linux-specific dependencies -[target.'cfg(target_os = "linux")'.dependencies] -libc = { version = "0.2", optional = true } diff --git a/desktop/src/cef/input.rs b/desktop/src/cef/input.rs index 6cbef09fb9..2961166764 100644 --- a/desktop/src/cef/input.rs +++ b/desktop/src/cef/input.rs @@ -95,7 +95,6 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat key_event.unmodified_character = 1; } - if key_event.type_ == cef_key_event_type_t::KEYEVENT_CHAR.into() { let mut key_down_event = key_event.clone(); key_down_event.type_ = cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into(); @@ -103,7 +102,6 @@ pub(crate) fn handle_window_event(browser: &Browser, input_state: &mut InputStat key_event.windows_key_code = event.logical_key.to_char_representation() as i32; } - host.send_key_event(Some(&key_event)); }