mirror of
https://github.com/zed-industries/zed.git
synced 2024-09-18 18:08:07 +03:00
windows: mouse and keyboard (#8791)
Windows mouse and keyboard working! I also tweaked the message loop so that it didn't get stuck. The peek message loop was almost never returning for me during testing. Release Notes: - Added windows mouse and keyboard support ![windows-mouse-and-keyboard](https://github.com/zed-industries/zed/assets/1284289/08578fbf-0cb2-4e44-bab1-3c4f0291ea4b)
This commit is contained in:
parent
7c9f680b1b
commit
36c4831806
@ -314,8 +314,12 @@ version = "0.53.0"
|
|||||||
features = [
|
features = [
|
||||||
"Win32_Graphics_Gdi",
|
"Win32_Graphics_Gdi",
|
||||||
"Win32_UI_WindowsAndMessaging",
|
"Win32_UI_WindowsAndMessaging",
|
||||||
|
"Win32_UI_Input_KeyboardAndMouse",
|
||||||
|
"Win32_System_SystemServices",
|
||||||
"Win32_Security",
|
"Win32_Security",
|
||||||
"Win32_System_Threading",
|
"Win32_System_Threading",
|
||||||
|
"Win32_System_DataExchange",
|
||||||
|
"Win32_System_Ole",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
use std::{
|
use std::{
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::HashSet,
|
collections::HashSet,
|
||||||
|
ffi::{c_uint, c_void},
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
@ -15,27 +16,39 @@ use async_task::Runnable;
|
|||||||
use futures::channel::oneshot::Receiver;
|
use futures::channel::oneshot::Receiver;
|
||||||
use parking_lot::Mutex;
|
use parking_lot::Mutex;
|
||||||
use time::UtcOffset;
|
use time::UtcOffset;
|
||||||
use util::SemanticVersion;
|
use util::{ResultExt, SemanticVersion};
|
||||||
use windows::Win32::{
|
use windows::Win32::{
|
||||||
Foundation::{CloseHandle, HANDLE, HWND},
|
Foundation::{CloseHandle, GetLastError, HANDLE, HWND, WAIT_EVENT},
|
||||||
System::Threading::{CreateEventW, INFINITE},
|
System::Threading::{CreateEventW, INFINITE},
|
||||||
UI::WindowsAndMessaging::{
|
UI::WindowsAndMessaging::{
|
||||||
DispatchMessageW, MsgWaitForMultipleObjects, PeekMessageW, PostQuitMessage,
|
DispatchMessageW, GetMessageW, MsgWaitForMultipleObjects, PostQuitMessage,
|
||||||
TranslateMessage, MSG, PM_REMOVE, QS_ALLINPUT, WM_QUIT,
|
SystemParametersInfoW, TranslateMessage, MSG, QS_ALLINPUT, SPI_GETWHEELSCROLLCHARS,
|
||||||
|
SPI_GETWHEELSCROLLLINES, SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS, WM_QUIT, WM_SETTINGCHANGE,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle, ForegroundExecutor,
|
try_get_window_inner, Action, AnyWindowHandle, BackgroundExecutor, ClipboardItem, CursorStyle,
|
||||||
Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput, PlatformTextSystem,
|
ForegroundExecutor, Keymap, Menu, PathPromptOptions, Platform, PlatformDisplay, PlatformInput,
|
||||||
PlatformWindow, Task, WindowAppearance, WindowOptions, WindowsDispatcher, WindowsDisplay,
|
PlatformTextSystem, PlatformWindow, Task, WindowAppearance, WindowOptions, WindowsDispatcher,
|
||||||
WindowsTextSystem, WindowsWindow,
|
WindowsDisplay, WindowsTextSystem, WindowsWindow,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) struct WindowsPlatform {
|
pub(crate) struct WindowsPlatform {
|
||||||
inner: Rc<WindowsPlatformInner>,
|
inner: Rc<WindowsPlatformInner>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Windows settings pulled from SystemParametersInfo
|
||||||
|
/// https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-systemparametersinfow
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub(crate) struct WindowsPlatformSystemSettings {
|
||||||
|
/// SEE: SPI_GETWHEELSCROLLCHARS
|
||||||
|
pub(crate) wheel_scroll_chars: u32,
|
||||||
|
|
||||||
|
/// SEE: SPI_GETWHEELSCROLLLINES
|
||||||
|
pub(crate) wheel_scroll_lines: u32,
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) struct WindowsPlatformInner {
|
pub(crate) struct WindowsPlatformInner {
|
||||||
background_executor: BackgroundExecutor,
|
background_executor: BackgroundExecutor,
|
||||||
pub(crate) foreground_executor: ForegroundExecutor,
|
pub(crate) foreground_executor: ForegroundExecutor,
|
||||||
@ -44,6 +57,7 @@ pub(crate) struct WindowsPlatformInner {
|
|||||||
callbacks: Mutex<Callbacks>,
|
callbacks: Mutex<Callbacks>,
|
||||||
pub(crate) window_handles: RefCell<HashSet<AnyWindowHandle>>,
|
pub(crate) window_handles: RefCell<HashSet<AnyWindowHandle>>,
|
||||||
pub(crate) event: HANDLE,
|
pub(crate) event: HANDLE,
|
||||||
|
pub(crate) settings: RefCell<WindowsPlatformSystemSettings>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for WindowsPlatformInner {
|
impl Drop for WindowsPlatformInner {
|
||||||
@ -65,6 +79,57 @@ struct Callbacks {
|
|||||||
validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
validate_app_menu_command: Option<Box<dyn FnMut(&dyn Action) -> bool>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum WindowsMessageWaitResult {
|
||||||
|
ForegroundExecution,
|
||||||
|
WindowsMessage(MSG),
|
||||||
|
Error,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WindowsPlatformSystemSettings {
|
||||||
|
fn new() -> Self {
|
||||||
|
let mut settings = Self::default();
|
||||||
|
settings.update_all();
|
||||||
|
settings
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_all(&mut self) {
|
||||||
|
self.update_wheel_scroll_lines();
|
||||||
|
self.update_wheel_scroll_chars();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_wheel_scroll_lines(&mut self) {
|
||||||
|
let mut value = c_uint::default();
|
||||||
|
let result = unsafe {
|
||||||
|
SystemParametersInfoW(
|
||||||
|
SPI_GETWHEELSCROLLLINES,
|
||||||
|
0,
|
||||||
|
Some((&mut value) as *mut c_uint as *mut c_void),
|
||||||
|
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if result.log_err() != None {
|
||||||
|
self.wheel_scroll_lines = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn update_wheel_scroll_chars(&mut self) {
|
||||||
|
let mut value = c_uint::default();
|
||||||
|
let result = unsafe {
|
||||||
|
SystemParametersInfoW(
|
||||||
|
SPI_GETWHEELSCROLLCHARS,
|
||||||
|
0,
|
||||||
|
Some((&mut value) as *mut c_uint as *mut c_void),
|
||||||
|
SYSTEM_PARAMETERS_INFO_UPDATE_FLAGS::default(),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if result.log_err() != None {
|
||||||
|
self.wheel_scroll_chars = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl WindowsPlatform {
|
impl WindowsPlatform {
|
||||||
pub(crate) fn new() -> Self {
|
pub(crate) fn new() -> Self {
|
||||||
let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
|
let (main_sender, main_receiver) = flume::unbounded::<Runnable>();
|
||||||
@ -75,6 +140,7 @@ impl WindowsPlatform {
|
|||||||
let text_system = Arc::new(WindowsTextSystem::new());
|
let text_system = Arc::new(WindowsTextSystem::new());
|
||||||
let callbacks = Mutex::new(Callbacks::default());
|
let callbacks = Mutex::new(Callbacks::default());
|
||||||
let window_handles = RefCell::new(HashSet::new());
|
let window_handles = RefCell::new(HashSet::new());
|
||||||
|
let settings = RefCell::new(WindowsPlatformSystemSettings::new());
|
||||||
let inner = Rc::new(WindowsPlatformInner {
|
let inner = Rc::new(WindowsPlatformInner {
|
||||||
background_executor,
|
background_executor,
|
||||||
foreground_executor,
|
foreground_executor,
|
||||||
@ -83,9 +149,44 @@ impl WindowsPlatform {
|
|||||||
callbacks,
|
callbacks,
|
||||||
window_handles,
|
window_handles,
|
||||||
event,
|
event,
|
||||||
|
settings,
|
||||||
});
|
});
|
||||||
Self { inner }
|
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 let Some(inner) = try_get_window_inner(msg.hwnd) {
|
||||||
|
inner.handle_immediate_msg(msg.message, msg.wParam, msg.lParam)
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_message(&self) -> WindowsMessageWaitResult {
|
||||||
|
let wait_result = unsafe {
|
||||||
|
MsgWaitForMultipleObjects(Some(&[self.inner.event]), false, INFINITE, QS_ALLINPUT)
|
||||||
|
};
|
||||||
|
|
||||||
|
match wait_result {
|
||||||
|
WAIT_EVENT(0) => WindowsMessageWaitResult::ForegroundExecution,
|
||||||
|
WAIT_EVENT(1) => {
|
||||||
|
let mut msg = MSG::default();
|
||||||
|
unsafe { GetMessageW(&mut msg, HWND::default(), 0, 0) };
|
||||||
|
WindowsMessageWaitResult::WindowsMessage(msg)
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
log::error!("unhandled windows wait message: {}", wait_result.0);
|
||||||
|
WindowsMessageWaitResult::Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Platform for WindowsPlatform {
|
impl Platform for WindowsPlatform {
|
||||||
@ -103,22 +204,27 @@ impl Platform for WindowsPlatform {
|
|||||||
|
|
||||||
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
|
fn run(&self, on_finish_launching: Box<dyn 'static + FnOnce()>) {
|
||||||
on_finish_launching();
|
on_finish_launching();
|
||||||
'a: loop {
|
loop {
|
||||||
unsafe {
|
match self.wait_message() {
|
||||||
MsgWaitForMultipleObjects(Some(&[self.inner.event]), false, INFINITE, QS_ALLINPUT)
|
WindowsMessageWaitResult::ForegroundExecution => {
|
||||||
};
|
for runnable in self.inner.main_receiver.drain() {
|
||||||
let mut msg = MSG::default();
|
runnable.run();
|
||||||
while unsafe { PeekMessageW(&mut msg, HWND::default(), 0, 0, PM_REMOVE) }.as_bool() {
|
}
|
||||||
if msg.message == WM_QUIT {
|
|
||||||
break 'a;
|
|
||||||
}
|
}
|
||||||
unsafe { TranslateMessage(&msg) };
|
WindowsMessageWaitResult::WindowsMessage(msg) => {
|
||||||
unsafe { DispatchMessageW(&msg) };
|
if msg.message == WM_QUIT {
|
||||||
}
|
break;
|
||||||
while let Ok(runnable) = self.inner.main_receiver.try_recv() {
|
}
|
||||||
runnable.run();
|
|
||||||
|
if !self.run_immediate_msg_handlers(&msg) {
|
||||||
|
unsafe { TranslateMessage(&msg) };
|
||||||
|
unsafe { DispatchMessageW(&msg) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WindowsMessageWaitResult::Error => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut callbacks = self.inner.callbacks.lock();
|
let mut callbacks = self.inner.callbacks.lock();
|
||||||
if let Some(callback) = callbacks.quit.as_mut() {
|
if let Some(callback) = callbacks.quit.as_mut() {
|
||||||
callback()
|
callback()
|
||||||
|
@ -3,6 +3,8 @@ use windows::Win32::Foundation::{LPARAM, WPARAM};
|
|||||||
pub(crate) trait HiLoWord {
|
pub(crate) trait HiLoWord {
|
||||||
fn hiword(&self) -> u16;
|
fn hiword(&self) -> u16;
|
||||||
fn loword(&self) -> u16;
|
fn loword(&self) -> u16;
|
||||||
|
fn signed_hiword(&self) -> i16;
|
||||||
|
fn signed_loword(&self) -> i16;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HiLoWord for WPARAM {
|
impl HiLoWord for WPARAM {
|
||||||
@ -13,6 +15,14 @@ impl HiLoWord for WPARAM {
|
|||||||
fn loword(&self) -> u16 {
|
fn loword(&self) -> u16 {
|
||||||
(self.0 & 0xFFFF) as u16
|
(self.0 & 0xFFFF) as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn signed_hiword(&self) -> i16 {
|
||||||
|
((self.0 >> 16) & 0xFFFF) as i16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signed_loword(&self) -> i16 {
|
||||||
|
(self.0 & 0xFFFF) as i16
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HiLoWord for LPARAM {
|
impl HiLoWord for LPARAM {
|
||||||
@ -23,4 +33,12 @@ impl HiLoWord for LPARAM {
|
|||||||
fn loword(&self) -> u16 {
|
fn loword(&self) -> u16 {
|
||||||
(self.0 & 0xFFFF) as u16
|
(self.0 & 0xFFFF) as u16
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn signed_hiword(&self) -> i16 {
|
||||||
|
((self.0 >> 16) & 0xFFFF) as i16
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signed_loword(&self) -> i16 {
|
||||||
|
(self.0 & 0xFFFF) as i16
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -18,24 +18,58 @@ use windows::{
|
|||||||
core::{w, HSTRING, PCWSTR},
|
core::{w, HSTRING, PCWSTR},
|
||||||
Win32::{
|
Win32::{
|
||||||
Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM},
|
Foundation::{HINSTANCE, HWND, LPARAM, LRESULT, WPARAM},
|
||||||
UI::WindowsAndMessaging::{
|
System::SystemServices::{
|
||||||
CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage,
|
MK_LBUTTON, MK_MBUTTON, MK_RBUTTON, MK_XBUTTON1, MK_XBUTTON2, MODIFIERKEYS_FLAGS,
|
||||||
RegisterClassW, SetWindowLongPtrW, SetWindowTextW, ShowWindow, CREATESTRUCTW,
|
},
|
||||||
CW_USEDEFAULT, GWLP_USERDATA, HMENU, IDC_ARROW, SW_MAXIMIZE, SW_SHOW, WINDOW_EX_STYLE,
|
UI::{
|
||||||
WINDOW_LONG_PTR_INDEX, WM_CLOSE, WM_DESTROY, WM_MOVE, WM_NCCREATE, WM_NCDESTROY,
|
Input::KeyboardAndMouse::{
|
||||||
WM_PAINT, WM_SIZE, WNDCLASSW, WS_OVERLAPPEDWINDOW, WS_VISIBLE,
|
GetKeyState, VIRTUAL_KEY, VK_BACK, VK_CONTROL, VK_DOWN, VK_END, VK_ESCAPE, VK_F1,
|
||||||
|
VK_F24, VK_HOME, VK_INSERT, VK_LEFT, VK_LWIN, VK_MENU, VK_NEXT, VK_PRIOR,
|
||||||
|
VK_RETURN, VK_RIGHT, VK_RWIN, VK_SHIFT, VK_SPACE, VK_TAB, VK_UP,
|
||||||
|
},
|
||||||
|
WindowsAndMessaging::{
|
||||||
|
CreateWindowExW, DefWindowProcW, GetWindowLongPtrW, LoadCursorW, PostQuitMessage,
|
||||||
|
RegisterClassW, SetWindowLongPtrW, SetWindowTextW, ShowWindow, CREATESTRUCTW,
|
||||||
|
CW_USEDEFAULT, GWLP_USERDATA, HMENU, IDC_ARROW, SW_MAXIMIZE, SW_SHOW, WHEEL_DELTA,
|
||||||
|
WINDOW_EX_STYLE, WINDOW_LONG_PTR_INDEX, WM_CHAR, WM_CLOSE, WM_DESTROY, WM_KEYDOWN,
|
||||||
|
WM_KEYUP, WM_LBUTTONDOWN, WM_LBUTTONUP, WM_MBUTTONDOWN, WM_MBUTTONUP,
|
||||||
|
WM_MOUSEHWHEEL, WM_MOUSEMOVE, WM_MOUSEWHEEL, WM_MOVE, WM_NCCREATE, WM_NCDESTROY,
|
||||||
|
WM_PAINT, WM_RBUTTONDOWN, WM_RBUTTONUP, WM_SIZE, WM_SYSCHAR, WM_SYSKEYDOWN,
|
||||||
|
WM_SYSKEYUP, WM_XBUTTONDOWN, WM_XBUTTONUP, WNDCLASSW, WS_OVERLAPPEDWINDOW,
|
||||||
|
WS_VISIBLE, XBUTTON1, XBUTTON2,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
platform::blade::BladeRenderer, AnyWindowHandle, Bounds, GlobalPixels, HiLoWord, Modifiers,
|
platform::blade::BladeRenderer, AnyWindowHandle, Bounds, GlobalPixels, HiLoWord, KeyDownEvent,
|
||||||
Pixels, PlatformAtlas, PlatformDisplay, PlatformInput, PlatformInputHandler, PlatformWindow,
|
KeyUpEvent, Keystroke, Modifiers, MouseButton, MouseDownEvent, MouseMoveEvent, MouseUpEvent,
|
||||||
Point, PromptLevel, Scene, Size, WindowAppearance, WindowBounds, WindowOptions, WindowsDisplay,
|
NavigationDirection, Pixels, PlatformAtlas, PlatformDisplay, PlatformInput,
|
||||||
WindowsPlatformInner,
|
PlatformInputHandler, PlatformWindow, Point, PromptLevel, Scene, ScrollDelta, Size, TouchPhase,
|
||||||
|
WindowAppearance, WindowBounds, WindowOptions, WindowsDisplay, WindowsPlatformInner,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct WindowsWindowInner {
|
#[derive(PartialEq)]
|
||||||
|
pub(crate) enum CallbackResult {
|
||||||
|
/// handled by system or user callback
|
||||||
|
Handled {
|
||||||
|
/// `true` if user callback handled event
|
||||||
|
by_callback: bool,
|
||||||
|
},
|
||||||
|
Unhandled,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CallbackResult {
|
||||||
|
pub fn is_handled(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Handled { by_callback: _ } => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct WindowsWindowInner {
|
||||||
hwnd: HWND,
|
hwnd: HWND,
|
||||||
origin: Cell<Point<GlobalPixels>>,
|
origin: Cell<Point<GlobalPixels>>,
|
||||||
size: Cell<Size<GlobalPixels>>,
|
size: Cell<Size<GlobalPixels>>,
|
||||||
@ -109,76 +143,424 @@ impl WindowsWindowInner {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_virtual_key_pressed(&self, vkey: VIRTUAL_KEY) -> bool {
|
||||||
|
unsafe { GetKeyState(vkey.0 as i32) < 0 }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn current_modifiers(&self) -> Modifiers {
|
||||||
|
Modifiers {
|
||||||
|
control: self.is_virtual_key_pressed(VK_CONTROL),
|
||||||
|
alt: self.is_virtual_key_pressed(VK_MENU),
|
||||||
|
shift: self.is_virtual_key_pressed(VK_SHIFT),
|
||||||
|
command: self.is_virtual_key_pressed(VK_LWIN) || self.is_virtual_key_pressed(VK_RWIN),
|
||||||
|
function: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// returns true if message is handled and should not dispatch
|
||||||
|
pub(crate) fn handle_immediate_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> bool {
|
||||||
|
match msg {
|
||||||
|
WM_KEYDOWN | WM_SYSKEYDOWN => self.handle_keydown_msg(wparam).is_handled(),
|
||||||
|
WM_KEYUP | WM_SYSKEYUP => self.handle_keyup_msg(wparam).is_handled(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
fn handle_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
log::debug!("msg: {msg}, wparam: {}, lparam: {}", wparam.0, lparam.0);
|
log::debug!("msg: {msg}, wparam: {}, lparam: {}", wparam.0, lparam.0);
|
||||||
match msg {
|
match msg {
|
||||||
WM_MOVE => {
|
WM_MOVE => self.handle_move_msg(lparam),
|
||||||
let x = lparam.loword() as f64;
|
WM_SIZE => self.handle_size_msg(lparam),
|
||||||
let y = lparam.hiword() as f64;
|
WM_PAINT => self.handle_paint_msg(),
|
||||||
self.origin.set(Point::new(x.into(), y.into()));
|
WM_CLOSE => self.handle_close_msg(msg, wparam, lparam),
|
||||||
let mut callbacks = self.callbacks.borrow_mut();
|
WM_DESTROY => self.handle_destroy_msg(),
|
||||||
if let Some(callback) = callbacks.moved.as_mut() {
|
WM_MOUSEMOVE => self.handle_mouse_move_msg(lparam, wparam),
|
||||||
callback()
|
WM_LBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Left, lparam),
|
||||||
|
WM_RBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Right, lparam),
|
||||||
|
WM_MBUTTONDOWN => self.handle_mouse_down_msg(MouseButton::Middle, lparam),
|
||||||
|
WM_XBUTTONDOWN => {
|
||||||
|
let nav_dir = match wparam.hiword() {
|
||||||
|
XBUTTON1 => Some(NavigationDirection::Forward),
|
||||||
|
XBUTTON2 => Some(NavigationDirection::Back),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(nav_dir) = nav_dir {
|
||||||
|
self.handle_mouse_down_msg(MouseButton::Navigate(nav_dir), lparam)
|
||||||
|
} else {
|
||||||
|
LRESULT(1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WM_SIZE => {
|
WM_LBUTTONUP => self.handle_mouse_up_msg(MouseButton::Left, lparam),
|
||||||
// todo!("windows"): handle maximized or minimized
|
WM_RBUTTONUP => self.handle_mouse_up_msg(MouseButton::Right, lparam),
|
||||||
let width = lparam.loword().max(1) as f64;
|
WM_MBUTTONUP => self.handle_mouse_up_msg(MouseButton::Middle, lparam),
|
||||||
let height = lparam.hiword().max(1) as f64;
|
WM_XBUTTONUP => {
|
||||||
self.renderer
|
let nav_dir = match wparam.hiword() {
|
||||||
.borrow_mut()
|
XBUTTON1 => Some(NavigationDirection::Back),
|
||||||
.update_drawable_size(Size { width, height });
|
XBUTTON2 => Some(NavigationDirection::Forward),
|
||||||
let width = width.into();
|
_ => None,
|
||||||
let height = height.into();
|
};
|
||||||
self.size.set(Size { width, height });
|
|
||||||
let mut callbacks = self.callbacks.borrow_mut();
|
if let Some(nav_dir) = nav_dir {
|
||||||
if let Some(callback) = callbacks.resize.as_mut() {
|
self.handle_mouse_up_msg(MouseButton::Navigate(nav_dir), lparam)
|
||||||
callback(
|
} else {
|
||||||
Size {
|
LRESULT(1)
|
||||||
width: Pixels(width.0),
|
|
||||||
height: Pixels(height.0),
|
|
||||||
},
|
|
||||||
1.0,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
WM_PAINT => {
|
WM_MOUSEWHEEL => self.handle_mouse_wheel_msg(wparam, lparam),
|
||||||
let mut callbacks = self.callbacks.borrow_mut();
|
WM_MOUSEHWHEEL => self.handle_mouse_horizontal_wheel_msg(wparam, lparam),
|
||||||
if let Some(callback) = callbacks.request_frame.as_mut() {
|
WM_CHAR | WM_SYSCHAR => self.handle_char_msg(wparam),
|
||||||
callback()
|
// These events are handled by the immediate handler
|
||||||
}
|
WM_KEYDOWN | WM_SYSKEYDOWN | WM_KEYUP | WM_SYSKEYUP => LRESULT(0),
|
||||||
}
|
_ => unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) },
|
||||||
WM_CLOSE => {
|
}
|
||||||
let mut callbacks: std::cell::RefMut<'_, Callbacks> = self.callbacks.borrow_mut();
|
}
|
||||||
if let Some(callback) = callbacks.should_close.as_mut() {
|
|
||||||
if callback() {
|
fn handle_move_msg(&self, lparam: LPARAM) -> LRESULT {
|
||||||
return LRESULT(0);
|
let x = lparam.signed_loword() as f64;
|
||||||
}
|
let y = lparam.signed_hiword() as f64;
|
||||||
}
|
self.origin.set(Point::new(x.into(), y.into()));
|
||||||
drop(callbacks);
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) };
|
if let Some(callback) = callbacks.moved.as_mut() {
|
||||||
}
|
callback()
|
||||||
WM_DESTROY => {
|
|
||||||
let mut callbacks: std::cell::RefMut<'_, Callbacks> = self.callbacks.borrow_mut();
|
|
||||||
if let Some(callback) = callbacks.close.take() {
|
|
||||||
callback()
|
|
||||||
}
|
|
||||||
let mut window_handles = self.platform_inner.window_handles.borrow_mut();
|
|
||||||
window_handles.remove(&self.handle);
|
|
||||||
if window_handles.is_empty() {
|
|
||||||
self.platform_inner
|
|
||||||
.foreground_executor
|
|
||||||
.spawn(async {
|
|
||||||
unsafe { PostQuitMessage(0) };
|
|
||||||
})
|
|
||||||
.detach();
|
|
||||||
}
|
|
||||||
return LRESULT(1);
|
|
||||||
}
|
|
||||||
_ => return unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) },
|
|
||||||
}
|
}
|
||||||
LRESULT(0)
|
LRESULT(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn handle_size_msg(&self, lparam: LPARAM) -> LRESULT {
|
||||||
|
let width = lparam.loword().max(1) as f64;
|
||||||
|
let height = lparam.hiword().max(1) as f64;
|
||||||
|
self.renderer
|
||||||
|
.borrow_mut()
|
||||||
|
.update_drawable_size(Size { width, height });
|
||||||
|
let width = width.into();
|
||||||
|
let height = height.into();
|
||||||
|
self.size.set(Size { width, height });
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.resize.as_mut() {
|
||||||
|
callback(
|
||||||
|
Size {
|
||||||
|
width: Pixels(width.0),
|
||||||
|
height: Pixels(height.0),
|
||||||
|
},
|
||||||
|
1.0,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_paint_msg(&self) -> LRESULT {
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.request_frame.as_mut() {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
LRESULT(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_close_msg(&self, msg: u32, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.should_close.as_mut() {
|
||||||
|
if callback() {
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
drop(callbacks);
|
||||||
|
unsafe { DefWindowProcW(self.hwnd, msg, wparam, lparam) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_destroy_msg(&self) -> LRESULT {
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.close.take() {
|
||||||
|
callback()
|
||||||
|
}
|
||||||
|
let mut window_handles = self.platform_inner.window_handles.borrow_mut();
|
||||||
|
window_handles.remove(&self.handle);
|
||||||
|
if window_handles.is_empty() {
|
||||||
|
self.platform_inner
|
||||||
|
.foreground_executor
|
||||||
|
.spawn(async {
|
||||||
|
unsafe { PostQuitMessage(0) };
|
||||||
|
})
|
||||||
|
.detach();
|
||||||
|
}
|
||||||
|
LRESULT(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mouse_move_msg(&self, lparam: LPARAM, wparam: WPARAM) -> LRESULT {
|
||||||
|
let x = Pixels::from(lparam.signed_loword() as f32);
|
||||||
|
let y = Pixels::from(lparam.signed_hiword() as f32);
|
||||||
|
self.mouse_position.set(Point { x, y });
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
|
let pressed_button = match MODIFIERKEYS_FLAGS(wparam.loword() as u32) {
|
||||||
|
flags if flags.contains(MK_LBUTTON) => Some(MouseButton::Left),
|
||||||
|
flags if flags.contains(MK_RBUTTON) => Some(MouseButton::Right),
|
||||||
|
flags if flags.contains(MK_MBUTTON) => Some(MouseButton::Middle),
|
||||||
|
flags if flags.contains(MK_XBUTTON1) => {
|
||||||
|
Some(MouseButton::Navigate(NavigationDirection::Back))
|
||||||
|
}
|
||||||
|
flags if flags.contains(MK_XBUTTON2) => {
|
||||||
|
Some(MouseButton::Navigate(NavigationDirection::Forward))
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
let event = MouseMoveEvent {
|
||||||
|
position: Point { x, y },
|
||||||
|
pressed_button,
|
||||||
|
modifiers: self.current_modifiers(),
|
||||||
|
};
|
||||||
|
if callback(PlatformInput::MouseMove(event)) {
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LRESULT(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_key_msg_keystroke(&self, wparam: WPARAM) -> Option<Keystroke> {
|
||||||
|
let vk_code = wparam.loword();
|
||||||
|
|
||||||
|
// 0-9 https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||||
|
if vk_code >= 0x30 && vk_code <= 0x39 {
|
||||||
|
let modifiers = self.current_modifiers();
|
||||||
|
|
||||||
|
if modifiers.shift {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let digit_char = (b'0' + ((vk_code - 0x30) as u8)) as char;
|
||||||
|
return Some(Keystroke {
|
||||||
|
modifiers,
|
||||||
|
key: digit_char.to_string(),
|
||||||
|
ime_key: Some(digit_char.to_string()),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// A-Z https://learn.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
|
||||||
|
if vk_code >= 0x41 && vk_code <= 0x5A {
|
||||||
|
let offset = (vk_code - 0x41) as u8;
|
||||||
|
let alpha_char = (b'a' + offset) as char;
|
||||||
|
let alpha_char_upper = (b'A' + offset) as char;
|
||||||
|
let modifiers = self.current_modifiers();
|
||||||
|
return Some(Keystroke {
|
||||||
|
modifiers,
|
||||||
|
key: alpha_char.to_string(),
|
||||||
|
ime_key: Some(if modifiers.shift {
|
||||||
|
alpha_char_upper.to_string()
|
||||||
|
} else {
|
||||||
|
alpha_char.to_string()
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if vk_code >= VK_F1.0 && vk_code <= VK_F24.0 {
|
||||||
|
let offset = vk_code - VK_F1.0;
|
||||||
|
return Some(Keystroke {
|
||||||
|
modifiers: self.current_modifiers(),
|
||||||
|
key: format!("f{}", offset + 1),
|
||||||
|
ime_key: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
let key = match VIRTUAL_KEY(vk_code) {
|
||||||
|
VK_SPACE => Some(("space", Some(" "))),
|
||||||
|
VK_TAB => Some(("tab", Some("\t"))),
|
||||||
|
VK_BACK => Some(("backspace", None)),
|
||||||
|
VK_RETURN => Some(("enter", None)),
|
||||||
|
VK_UP => Some(("up", None)),
|
||||||
|
VK_DOWN => Some(("down", None)),
|
||||||
|
VK_RIGHT => Some(("right", None)),
|
||||||
|
VK_LEFT => Some(("left", None)),
|
||||||
|
VK_HOME => Some(("home", None)),
|
||||||
|
VK_END => Some(("end", None)),
|
||||||
|
VK_PRIOR => Some(("pageup", None)),
|
||||||
|
VK_NEXT => Some(("pagedown", None)),
|
||||||
|
VK_ESCAPE => Some(("escape", None)),
|
||||||
|
VK_INSERT => Some(("insert", None)),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some((key, ime_key)) = key {
|
||||||
|
Some(Keystroke {
|
||||||
|
modifiers: self.current_modifiers(),
|
||||||
|
key: key.to_string(),
|
||||||
|
ime_key: ime_key.map(|k| k.to_string()),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_keydown_msg(&self, wparam: WPARAM) -> CallbackResult {
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
let keystroke = self.parse_key_msg_keystroke(wparam);
|
||||||
|
if let Some(keystroke) = keystroke {
|
||||||
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
|
let ime_key = keystroke.ime_key.clone();
|
||||||
|
let event = KeyDownEvent {
|
||||||
|
keystroke,
|
||||||
|
is_held: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
if callback(PlatformInput::KeyDown(event)) {
|
||||||
|
if let Some(request_frame) = callbacks.request_frame.as_mut() {
|
||||||
|
request_frame();
|
||||||
|
}
|
||||||
|
CallbackResult::Handled { by_callback: true }
|
||||||
|
} else if let Some(mut input_handler) = self.input_handler.take() {
|
||||||
|
if let Some(ime_key) = ime_key {
|
||||||
|
input_handler.replace_text_in_range(None, &ime_key);
|
||||||
|
}
|
||||||
|
self.input_handler.set(Some(input_handler));
|
||||||
|
if let Some(request_frame) = callbacks.request_frame.as_mut() {
|
||||||
|
request_frame();
|
||||||
|
}
|
||||||
|
CallbackResult::Handled { by_callback: true }
|
||||||
|
} else {
|
||||||
|
CallbackResult::Handled { by_callback: false }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CallbackResult::Handled { by_callback: false }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CallbackResult::Unhandled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_keyup_msg(&self, wparam: WPARAM) -> CallbackResult {
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
let keystroke = self.parse_key_msg_keystroke(wparam);
|
||||||
|
if let Some(keystroke) = keystroke {
|
||||||
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
|
let event = KeyUpEvent { keystroke };
|
||||||
|
CallbackResult::Handled {
|
||||||
|
by_callback: callback(PlatformInput::KeyUp(event)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CallbackResult::Handled { by_callback: false }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
CallbackResult::Unhandled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
let keystroke = Keystroke {
|
||||||
|
modifiers,
|
||||||
|
key: msg_char.to_string(),
|
||||||
|
ime_key: Some(msg_char.to_string()),
|
||||||
|
};
|
||||||
|
let ime_key = keystroke.ime_key.clone();
|
||||||
|
let event = KeyDownEvent {
|
||||||
|
keystroke,
|
||||||
|
is_held: false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if callback(PlatformInput::KeyDown(event)) {
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(mut input_handler) = self.input_handler.take() {
|
||||||
|
if let Some(ime_key) = ime_key {
|
||||||
|
input_handler.replace_text_in_range(None, &ime_key);
|
||||||
|
}
|
||||||
|
self.input_handler.set(Some(input_handler));
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return LRESULT(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mouse_down_msg(&self, button: MouseButton, lparam: LPARAM) -> LRESULT {
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
|
let x = Pixels::from(lparam.signed_loword() as f32);
|
||||||
|
let y = Pixels::from(lparam.signed_hiword() as f32);
|
||||||
|
let event = MouseDownEvent {
|
||||||
|
button,
|
||||||
|
position: Point { x, y },
|
||||||
|
modifiers: self.current_modifiers(),
|
||||||
|
click_count: 1,
|
||||||
|
};
|
||||||
|
if callback(PlatformInput::MouseDown(event)) {
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LRESULT(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mouse_up_msg(&self, button: MouseButton, lparam: LPARAM) -> LRESULT {
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
|
let x = Pixels::from(lparam.signed_loword() as f32);
|
||||||
|
let y = Pixels::from(lparam.signed_hiword() as f32);
|
||||||
|
let event = MouseUpEvent {
|
||||||
|
button,
|
||||||
|
position: Point { x, y },
|
||||||
|
modifiers: self.current_modifiers(),
|
||||||
|
click_count: 1,
|
||||||
|
};
|
||||||
|
if callback(PlatformInput::MouseUp(event)) {
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LRESULT(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mouse_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
|
let x = Pixels::from(lparam.signed_loword() as f32);
|
||||||
|
let y = Pixels::from(lparam.signed_hiword() as f32);
|
||||||
|
let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
|
||||||
|
* self.platform_inner.settings.borrow().wheel_scroll_lines as f32;
|
||||||
|
let event = crate::ScrollWheelEvent {
|
||||||
|
position: Point { x, y },
|
||||||
|
delta: ScrollDelta::Lines(Point {
|
||||||
|
x: 0.0,
|
||||||
|
y: wheel_distance,
|
||||||
|
}),
|
||||||
|
modifiers: self.current_modifiers(),
|
||||||
|
touch_phase: TouchPhase::Moved,
|
||||||
|
};
|
||||||
|
if callback(PlatformInput::ScrollWheel(event)) {
|
||||||
|
if let Some(request_frame) = callbacks.request_frame.as_mut() {
|
||||||
|
request_frame();
|
||||||
|
}
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LRESULT(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_mouse_horizontal_wheel_msg(&self, wparam: WPARAM, lparam: LPARAM) -> LRESULT {
|
||||||
|
let mut callbacks = self.callbacks.borrow_mut();
|
||||||
|
if let Some(callback) = callbacks.input.as_mut() {
|
||||||
|
let x = Pixels::from(lparam.signed_loword() as f32);
|
||||||
|
let y = Pixels::from(lparam.signed_hiword() as f32);
|
||||||
|
let wheel_distance = (wparam.signed_hiword() as f32 / WHEEL_DELTA as f32)
|
||||||
|
* self.platform_inner.settings.borrow().wheel_scroll_chars as f32;
|
||||||
|
let event = crate::ScrollWheelEvent {
|
||||||
|
position: Point { x, y },
|
||||||
|
delta: ScrollDelta::Lines(Point {
|
||||||
|
x: wheel_distance,
|
||||||
|
y: 0.0,
|
||||||
|
}),
|
||||||
|
modifiers: self.current_modifiers(),
|
||||||
|
touch_phase: TouchPhase::Moved,
|
||||||
|
};
|
||||||
|
if callback(PlatformInput::ScrollWheel(event)) {
|
||||||
|
if let Some(request_frame) = callbacks.request_frame.as_mut() {
|
||||||
|
request_frame();
|
||||||
|
}
|
||||||
|
return LRESULT(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
LRESULT(1)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
@ -512,6 +894,16 @@ unsafe extern "system" fn wnd_proc(
|
|||||||
r
|
r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn try_get_window_inner(hwnd: HWND) -> Option<Rc<WindowsWindowInner>> {
|
||||||
|
let ptr = unsafe { get_window_long(hwnd, GWLP_USERDATA) } as *mut Weak<WindowsWindowInner>;
|
||||||
|
if !ptr.is_null() {
|
||||||
|
let inner = unsafe { &*ptr };
|
||||||
|
inner.upgrade()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize {
|
unsafe fn get_window_long(hwnd: HWND, nindex: WINDOW_LONG_PTR_INDEX) -> isize {
|
||||||
#[cfg(target_pointer_width = "64")]
|
#[cfg(target_pointer_width = "64")]
|
||||||
unsafe {
|
unsafe {
|
||||||
|
Loading…
Reference in New Issue
Block a user