From 47cf19d633f2e46ffe875281e4a0debc89a50b48 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 15 Jul 2026 10:31:36 +0000 Subject: [PATCH 1/2] Fix text selection on Mac --- desktop/ui/src/internal.rs | 2 + .../ui/src/internal/browser_process_client.rs | 7 ++ desktop/ui/src/internal/keyboard_handler.rs | 108 ++++++++++++++++++ 3 files changed, 117 insertions(+) create mode 100644 desktop/ui/src/internal/keyboard_handler.rs diff --git a/desktop/ui/src/internal.rs b/desktop/ui/src/internal.rs index c2194ef3eb..fd0a1598c5 100644 --- a/desktop/ui/src/internal.rs +++ b/desktop/ui/src/internal.rs @@ -8,6 +8,8 @@ mod render_process_v8_handler; mod context_menu_handler; mod display_handler; +#[cfg(target_os = "macos")] +mod keyboard_handler; mod life_span_handler; mod load_handler; mod request_handler; diff --git a/desktop/ui/src/internal/browser_process_client.rs b/desktop/ui/src/internal/browser_process_client.rs index 76475487b8..bc3acc9294 100644 --- a/desktop/ui/src/internal/browser_process_client.rs +++ b/desktop/ui/src/internal/browser_process_client.rs @@ -8,6 +8,8 @@ use crate::ipc::{MessageType, UnpackMessage, UnpackedMessage}; use super::context_menu_handler::ContextMenuHandlerImpl; use super::display_handler::DisplayHandlerImpl; +#[cfg(target_os = "macos")] +use super::keyboard_handler::KeyboardHandlerImpl; use super::life_span_handler::LifeSpanHandlerImpl; use super::load_handler::LoadHandlerImpl; use super::render_handler::RenderHandlerImpl; @@ -85,6 +87,11 @@ impl ImplClient for BrowserProcessClientImpl { Some(ContextMenuHandler::new(ContextMenuHandlerImpl::new())) } + #[cfg(target_os = "macos")] + fn keyboard_handler(&self) -> Option { + Some(cef::KeyboardHandler::new(KeyboardHandlerImpl::new())) + } + fn get_raw(&self) -> *mut _cef_client_t { self.object.cast() } diff --git a/desktop/ui/src/internal/keyboard_handler.rs b/desktop/ui/src/internal/keyboard_handler.rs new file mode 100644 index 0000000000..dbcb2f387b --- /dev/null +++ b/desktop/ui/src/internal/keyboard_handler.rs @@ -0,0 +1,108 @@ +use cef::rc::{Rc, RcImpl}; +use cef::sys::{_cef_keyboard_handler_t, cef_base_ref_counted_t, cef_event_flags_t, cef_key_event_type_t}; +use cef::{Browser, ImplBrowser, ImplBrowserHost, ImplFrame, ImplKeyboardHandler, KeyEvent, WrapKeyboardHandler}; + +const SHIFT: u32 = cef_event_flags_t::EVENTFLAG_SHIFT_DOWN.0; +const CONTROL: u32 = cef_event_flags_t::EVENTFLAG_CONTROL_DOWN.0; +const ALT: u32 = cef_event_flags_t::EVENTFLAG_ALT_DOWN.0; +const COMMAND: u32 = cef_event_flags_t::EVENTFLAG_COMMAND_DOWN.0; +const MODIFIER_MASK: u32 = SHIFT | CONTROL | ALT | COMMAND; + +const VK_END: i32 = 0x23; +const VK_HOME: i32 = 0x24; +const VK_UP: i32 = 0x26; +const VK_DOWN: i32 = 0x28; +const VK_A: i32 = 0x41; +const VK_C: i32 = 0x43; +const VK_V: i32 = 0x56; +const VK_X: i32 = 0x58; +const VK_Z: i32 = 0x5A; + +const KVK_HOME: i32 = 0x73; +const KVK_END: i32 = 0x77; +const NS_HOME_FUNCTION_KEY: u16 = 0xF729; +const NS_END_FUNCTION_KEY: u16 = 0xF72B; + +pub(crate) struct KeyboardHandlerImpl { + object: *mut RcImpl<_cef_keyboard_handler_t, Self>, +} +impl KeyboardHandlerImpl { + pub(crate) fn new() -> Self { + Self { object: std::ptr::null_mut() } + } +} + +impl ImplKeyboardHandler for KeyboardHandlerImpl { + fn on_key_event(&self, browser: Option<&mut Browser>, event: Option<&KeyEvent>, _os_event: *mut u8) -> std::ffi::c_int { + let (Some(browser), Some(event)) = (browser, event) else { return 0 }; + if event.type_ != cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into() { + return 0; + } + + let modifiers = event.modifiers & MODIFIER_MASK; + let shift = modifiers & SHIFT != 0; + + if modifiers & COMMAND != 0 && modifiers & (CONTROL | ALT) == 0 { + let Some(frame) = browser.focused_frame() else { return 0 }; + match (event.windows_key_code, shift) { + (VK_A, false) => frame.select_all(), + (VK_C, false) => frame.copy(), + (VK_V, false) => frame.paste(), + (VK_V, true) => frame.paste_and_match_style(), + (VK_X, false) => frame.cut(), + (VK_Z, false) => frame.undo(), + (VK_Z, true) => frame.redo(), + _ => return 0, + } + return 1; + } + + if modifiers == SHIFT | ALT { + let (windows_key_code, native_key_code, character) = match event.windows_key_code { + VK_UP => (VK_HOME, KVK_HOME, NS_HOME_FUNCTION_KEY), + VK_DOWN => (VK_END, KVK_END, NS_END_FUNCTION_KEY), + _ => return 0, + }; + let Some(host) = browser.host() else { return 0 }; + host.send_key_event(Some(&KeyEvent { + type_: cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into(), + modifiers: SHIFT, + windows_key_code, + native_key_code, + character, + unmodified_character: character, + ..Default::default() + })); + return 1; + } + + 0 + } + + fn get_raw(&self) -> *mut _cef_keyboard_handler_t { + self.object.cast() + } +} + +impl Clone for KeyboardHandlerImpl { + fn clone(&self) -> Self { + unsafe { + let rc_impl = &mut *self.object; + rc_impl.interface.add_ref(); + } + Self { object: self.object } + } +} +impl Rc for KeyboardHandlerImpl { + fn as_base(&self) -> &cef_base_ref_counted_t { + unsafe { + let base = &*self.object; + std::mem::transmute(&base.cef_object) + } + } +} +impl WrapKeyboardHandler for KeyboardHandlerImpl { + fn wrap_rc(&mut self, object: *mut RcImpl<_cef_keyboard_handler_t, Self>) { + self.object = object; + } +} From b506777602b970a5e99424ba4a5236a193258fc2 Mon Sep 17 00:00:00 2001 From: Timon Date: Wed, 15 Jul 2026 10:31:36 +0000 Subject: [PATCH 2/2] Review --- desktop/ui/src/internal/keyboard_handler.rs | 42 +++++++++++---------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/desktop/ui/src/internal/keyboard_handler.rs b/desktop/ui/src/internal/keyboard_handler.rs index dbcb2f387b..91b37d118e 100644 --- a/desktop/ui/src/internal/keyboard_handler.rs +++ b/desktop/ui/src/internal/keyboard_handler.rs @@ -1,12 +1,14 @@ use cef::rc::{Rc, RcImpl}; use cef::sys::{_cef_keyboard_handler_t, cef_base_ref_counted_t, cef_event_flags_t, cef_key_event_type_t}; -use cef::{Browser, ImplBrowser, ImplBrowserHost, ImplFrame, ImplKeyboardHandler, KeyEvent, WrapKeyboardHandler}; +use cef::{Browser, Frame, ImplBrowser, ImplBrowserHost, ImplFrame, ImplKeyboardHandler, KeyEvent, WrapKeyboardHandler}; const SHIFT: u32 = cef_event_flags_t::EVENTFLAG_SHIFT_DOWN.0; const CONTROL: u32 = cef_event_flags_t::EVENTFLAG_CONTROL_DOWN.0; const ALT: u32 = cef_event_flags_t::EVENTFLAG_ALT_DOWN.0; const COMMAND: u32 = cef_event_flags_t::EVENTFLAG_COMMAND_DOWN.0; const MODIFIER_MASK: u32 = SHIFT | CONTROL | ALT | COMMAND; +const COMMAND_SHIFT: u32 = COMMAND | SHIFT; +const SHIFT_ALT: u32 = SHIFT | ALT; const VK_END: i32 = 0x23; const VK_HOME: i32 = 0x24; @@ -39,30 +41,30 @@ impl ImplKeyboardHandler for KeyboardHandlerImpl { return 0; } - let modifiers = event.modifiers & MODIFIER_MASK; - let shift = modifiers & SHIFT != 0; + let shortcut = (event.modifiers & MODIFIER_MASK, event.windows_key_code); - if modifiers & COMMAND != 0 && modifiers & (CONTROL | ALT) == 0 { + let edit_operation: Option = match shortcut { + (COMMAND, VK_A) => Some(Frame::select_all), + (COMMAND, VK_C) => Some(Frame::copy), + (COMMAND, VK_V) => Some(Frame::paste), + (COMMAND_SHIFT, VK_V) => Some(Frame::paste_and_match_style), + (COMMAND, VK_X) => Some(Frame::cut), + (COMMAND, VK_Z) => Some(Frame::undo), + (COMMAND_SHIFT, VK_Z) => Some(Frame::redo), + _ => None, + }; + if let Some(edit_operation) = edit_operation { let Some(frame) = browser.focused_frame() else { return 0 }; - match (event.windows_key_code, shift) { - (VK_A, false) => frame.select_all(), - (VK_C, false) => frame.copy(), - (VK_V, false) => frame.paste(), - (VK_V, true) => frame.paste_and_match_style(), - (VK_X, false) => frame.cut(), - (VK_Z, false) => frame.undo(), - (VK_Z, true) => frame.redo(), - _ => return 0, - } + edit_operation(&frame); return 1; } - if modifiers == SHIFT | ALT { - let (windows_key_code, native_key_code, character) = match event.windows_key_code { - VK_UP => (VK_HOME, KVK_HOME, NS_HOME_FUNCTION_KEY), - VK_DOWN => (VK_END, KVK_END, NS_END_FUNCTION_KEY), - _ => return 0, - }; + let remap = match shortcut { + (SHIFT_ALT, VK_UP) => Some((VK_HOME, KVK_HOME, NS_HOME_FUNCTION_KEY)), + (SHIFT_ALT, VK_DOWN) => Some((VK_END, KVK_END, NS_END_FUNCTION_KEY)), + _ => None, + }; + if let Some((windows_key_code, native_key_code, character)) = remap { let Some(host) = browser.host() else { return 0 }; host.send_key_event(Some(&KeyEvent { type_: cef_key_event_type_t::KEYEVENT_RAWKEYDOWN.into(),