windows: Better keyboard input support (#9180)
### Description Currently, there are some issues with input handling on Windows: #### 1. Direct crash when encountering IME input. https://github.com/zed-industries/zed/assets/14981363/598f7272-1948-4a42-99c5-2ef7b9162a1e #### 2. Handling messages every 1/60 seconds in the main thread. Despite being named "immediate_handle," it's not exactly immediate. ```rust // actually halt here let wait_result = unsafe { DCompositionWaitForCompositorClock(Some(&[self.inner.event]), INFINITE) }; // compositor clock ticked so we should draw a frame if wait_result == 1 { unsafe { invalidate_thread_windows(GetCurrentThreadId()) }; while unsafe { PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE) }.as_bool() ``` #### 3. According to Windows recommendations, character input should be obtained using `WM_CHAR` instead of `WM_KEYDOWN`. Additionally, there are problems with the handling within `WM_CHAR`. ```rust fn handle_char_msg(&self, wparam: WPARAM) -> LRESULT { let mut callbacks = self.callbacks.borrow_mut(); if let Some(callback) = callbacks.input.as_mut() { let modifiers = self.current_modifiers(); let msg_char = wparam.0 as u8 as char; // these are u16 chars, cant treat them as u8 ``` And, we don't handle `WM_SYSKEYDOWN` properly, which leads to `Alt + F4` not working. Release Notes: - N/A
This commit is contained in:
parent
2abb5aeaf2
commit
36cbfbfb94
3 changed files with 321 additions and 262 deletions
|
@ -3,7 +3,6 @@
|
|||
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashSet,
|
||||
ffi::{c_uint, c_void, OsString},
|
||||
os::windows::ffi::{OsStrExt, OsStringExt},
|
||||
path::{Path, PathBuf},
|
||||
|
@ -17,19 +16,23 @@ use async_task::Runnable;
|
|||
use copypasta::{ClipboardContext, ClipboardProvider};
|
||||
use futures::channel::oneshot::{self, Receiver};
|
||||
use itertools::Itertools;
|
||||
use parking_lot::Mutex;
|
||||
use parking_lot::{Mutex, RwLock};
|
||||
use smallvec::SmallVec;
|
||||
use time::UtcOffset;
|
||||
use util::{ResultExt, SemanticVersion};
|
||||
use windows::{
|
||||
core::{IUnknown, HRESULT, HSTRING, PCWSTR, PWSTR},
|
||||
Wdk::System::SystemServices::RtlGetVersion,
|
||||
Win32::{
|
||||
Foundation::{CloseHandle, BOOL, HANDLE, HWND, LPARAM, TRUE},
|
||||
Graphics::DirectComposition::DCompositionWaitForCompositorClock,
|
||||
Foundation::{CloseHandle, HANDLE, HWND},
|
||||
Graphics::{
|
||||
DirectComposition::DCompositionWaitForCompositorClock,
|
||||
Gdi::{RedrawWindow, HRGN, RDW_INVALIDATE, RDW_UPDATENOW},
|
||||
},
|
||||
System::{
|
||||
Com::{CoCreateInstance, CreateBindCtx, CLSCTX_ALL},
|
||||
Ole::{OleInitialize, OleUninitialize},
|
||||
Threading::{CreateEventW, GetCurrentThreadId, INFINITE},
|
||||
Threading::{CreateEventW, INFINITE},
|
||||
Time::{GetTimeZoneInformation, TIME_ZONE_ID_INVALID},
|
||||
},
|
||||
UI::{
|
||||
|
@ -40,21 +43,21 @@ use windows::{
|
|||
FOS_ALLOWMULTISELECT, FOS_FILEMUSTEXIST, FOS_PICKFOLDERS, SIGDN_FILESYSPATH,
|
||||
},
|
||||
WindowsAndMessaging::{
|
||||
DispatchMessageW, EnumThreadWindows, LoadImageW, PeekMessageW, PostQuitMessage,
|
||||
SetCursor, SystemParametersInfoW, TranslateMessage, HCURSOR, IDC_ARROW, IDC_CROSS,
|
||||
IDC_HAND, IDC_IBEAM, IDC_NO, IDC_SIZENS, IDC_SIZEWE, IMAGE_CURSOR, LR_DEFAULTSIZE,
|
||||
LR_SHARED, MSG, PM_REMOVE, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES,
|
||||
SW_SHOWDEFAULT, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE,
|
||||
DispatchMessageW, LoadImageW, PeekMessageW, PostQuitMessage, SetCursor,
|
||||
SystemParametersInfoW, TranslateMessage, HCURSOR, IDC_ARROW, IDC_CROSS, IDC_HAND,
|
||||
IDC_IBEAM, IDC_NO, IDC_SIZENS, IDC_SIZEWE, IMAGE_CURSOR, LR_DEFAULTSIZE, LR_SHARED,
|
||||
MSG, PM_REMOVE, SPI_GETWHEELSCROLLCHARS, SPI_GETWHEELSCROLLLINES, SW_SHOWDEFAULT,
|
||||
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
try_get_window_inner, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle,
|
||||
ForegroundExecutor, Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
||||
PlatformTextSystem, PlatformWindow, Task, WindowAppearance, WindowOptions, WindowParams,
|
||||
WindowsDispatcher, WindowsDisplay, WindowsTextSystem, WindowsWindow,
|
||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor,
|
||||
Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem,
|
||||
PlatformWindow, Task, WindowAppearance, WindowParams, WindowsDispatcher, WindowsDisplay,
|
||||
WindowsTextSystem, WindowsWindow,
|
||||
};
|
||||
|
||||
pub(crate) struct WindowsPlatform {
|
||||
|
@ -72,15 +75,13 @@ pub(crate) struct WindowsPlatformSystemSettings {
|
|||
pub(crate) wheel_scroll_lines: u32,
|
||||
}
|
||||
|
||||
type WindowHandleValues = HashSet<isize>;
|
||||
|
||||
pub(crate) struct WindowsPlatformInner {
|
||||
background_executor: BackgroundExecutor,
|
||||
pub(crate) foreground_executor: ForegroundExecutor,
|
||||
main_receiver: flume::Receiver<Runnable>,
|
||||
text_system: Arc<WindowsTextSystem>,
|
||||
callbacks: Mutex<Callbacks>,
|
||||
pub(crate) window_handle_values: RefCell<WindowHandleValues>,
|
||||
pub raw_window_handles: RwLock<SmallVec<[HWND; 4]>>,
|
||||
pub(crate) event: HANDLE,
|
||||
pub(crate) settings: RefCell<WindowsPlatformSystemSettings>,
|
||||
}
|
||||
|
@ -167,7 +168,7 @@ impl WindowsPlatform {
|
|||
let foreground_executor = ForegroundExecutor::new(dispatcher);
|
||||
let text_system = Arc::new(WindowsTextSystem::new());
|
||||
let callbacks = Mutex::new(Callbacks::default());
|
||||
let window_handle_values = RefCell::new(HashSet::new());
|
||||
let raw_window_handles = RwLock::new(SmallVec::new());
|
||||
let settings = RefCell::new(WindowsPlatformSystemSettings::new());
|
||||
let inner = Rc::new(WindowsPlatformInner {
|
||||
background_executor,
|
||||
|
@ -175,64 +176,31 @@ impl WindowsPlatform {
|
|||
main_receiver,
|
||||
text_system,
|
||||
callbacks,
|
||||
window_handle_values,
|
||||
raw_window_handles,
|
||||
event,
|
||||
settings,
|
||||
});
|
||||
Self { inner }
|
||||
}
|
||||
|
||||
/// runs message handlers that should be processed before dispatching to prevent translating unnecessary messages
|
||||
/// returns true if message is handled and should not dispatch
|
||||
fn run_immediate_msg_handlers(&self, msg: &MSG) -> bool {
|
||||
if msg.message == WM_SETTINGCHANGE {
|
||||
self.inner.settings.borrow_mut().update_all();
|
||||
return true;
|
||||
}
|
||||
|
||||
if !self
|
||||
.inner
|
||||
.window_handle_values
|
||||
.borrow()
|
||||
.contains(&msg.hwnd.0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if let Some(inner) = try_get_window_inner(msg.hwnd) {
|
||||
inner.handle_immediate_msg(msg.message, msg.wParam, msg.lParam)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
fn run_foreground_tasks(&self) {
|
||||
for runnable in self.inner.main_receiver.drain() {
|
||||
runnable.run();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "system" fn invalidate_window_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
let window_handle_values = unsafe { &*(lparam.0 as *const WindowHandleValues) };
|
||||
if !window_handle_values.contains(&hwnd.0) {
|
||||
return TRUE;
|
||||
fn redraw_all(&self) {
|
||||
for handle in self.inner.raw_window_handles.read().iter() {
|
||||
unsafe {
|
||||
RedrawWindow(
|
||||
*handle,
|
||||
None,
|
||||
HRGN::default(),
|
||||
RDW_INVALIDATE | RDW_UPDATENOW,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(inner) = try_get_window_inner(hwnd) {
|
||||
inner.invalidate_client_area();
|
||||
}
|
||||
TRUE
|
||||
}
|
||||
|
||||
/// invalidates all windows belonging to a thread causing a paint message to be scheduled
|
||||
fn invalidate_thread_windows(win32_thread_id: u32, window_handle_values: &WindowHandleValues) {
|
||||
unsafe {
|
||||
EnumThreadWindows(
|
||||
win32_thread_id,
|
||||
Some(invalidate_window_callback),
|
||||
LPARAM(window_handle_values as *const _ as isize),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
impl Platform for WindowsPlatform {
|
||||
|
@ -250,35 +218,34 @@ impl Platform for WindowsPlatform {
|
|||
|
||||
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
|
||||
on_finish_launching();
|
||||
let dispatch_event = self.inner.event;
|
||||
|
||||
'a: loop {
|
||||
let mut msg = MSG::default();
|
||||
// will be 0 if woken up by self.inner.event or 1 if the compositor clock ticked
|
||||
// SEE: https://learn.microsoft.com/en-us/windows/win32/directcomp/compositor-clock/compositor-clock
|
||||
let wait_result =
|
||||
unsafe { DCompositionWaitForCompositorClock(Some(&[self.inner.event]), INFINITE) };
|
||||
unsafe { DCompositionWaitForCompositorClock(Some(&[dispatch_event]), INFINITE) };
|
||||
|
||||
// compositor clock ticked so we should draw a frame
|
||||
if wait_result == 1 {
|
||||
self.redraw_all();
|
||||
unsafe {
|
||||
invalidate_thread_windows(
|
||||
GetCurrentThreadId(),
|
||||
&self.inner.window_handle_values.borrow(),
|
||||
)
|
||||
};
|
||||
let mut msg = MSG::default();
|
||||
|
||||
while unsafe { PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE) }.as_bool()
|
||||
{
|
||||
if msg.message == WM_QUIT {
|
||||
break 'a;
|
||||
}
|
||||
|
||||
if !self.run_immediate_msg_handlers(&msg) {
|
||||
unsafe { TranslateMessage(&msg) };
|
||||
unsafe { DispatchMessageW(&msg) };
|
||||
while PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE).as_bool() {
|
||||
if msg.message == WM_QUIT {
|
||||
break 'a;
|
||||
}
|
||||
if msg.message == WM_SETTINGCHANGE {
|
||||
self.inner.settings.borrow_mut().update_all();
|
||||
continue;
|
||||
}
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.run_foreground_tasks();
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue