feat: 大屏桌面壳(Tauri 2)
This commit is contained in:
+251
@@ -0,0 +1,251 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
// A silly implementation of file drop handling for Windows!
|
||||
|
||||
use crate::DragDropEvent;
|
||||
|
||||
use std::{
|
||||
cell::UnsafeCell,
|
||||
ffi::OsString,
|
||||
os::{raw::c_void, windows::ffi::OsStringExt},
|
||||
path::PathBuf,
|
||||
ptr,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use windows::{
|
||||
core::{implement, BOOL},
|
||||
Win32::{
|
||||
Foundation::{DRAGDROP_E_INVALIDHWND, HWND, LPARAM, POINT, POINTL},
|
||||
Graphics::Gdi::ScreenToClient,
|
||||
System::{
|
||||
Com::{IDataObject, DVASPECT_CONTENT, FORMATETC, TYMED_HGLOBAL},
|
||||
Ole::{
|
||||
IDropTarget, IDropTarget_Impl, RegisterDragDrop, RevokeDragDrop, CF_HDROP, DROPEFFECT,
|
||||
DROPEFFECT_COPY, DROPEFFECT_NONE,
|
||||
},
|
||||
SystemServices::MODIFIERKEYS_FLAGS,
|
||||
},
|
||||
UI::{
|
||||
Shell::{DragFinish, DragQueryFileW, HDROP},
|
||||
WindowsAndMessaging::EnumChildWindows,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct DragDropController {
|
||||
drop_targets: Vec<IDropTarget>,
|
||||
}
|
||||
|
||||
impl DragDropController {
|
||||
#[inline]
|
||||
pub(crate) fn new(hwnd: HWND, handler: Box<dyn Fn(DragDropEvent) -> bool>) -> Self {
|
||||
let mut controller = DragDropController::default();
|
||||
|
||||
let handler = Rc::new(handler);
|
||||
|
||||
// Enumerate child windows to find the WebView2 "window" and override!
|
||||
{
|
||||
let mut callback = |hwnd| controller.inject_in_hwnd(hwnd, handler.clone());
|
||||
let mut trait_obj: &mut dyn FnMut(HWND) -> bool = &mut callback;
|
||||
let closure_pointer_pointer: *mut c_void = unsafe { std::mem::transmute(&mut trait_obj) };
|
||||
let lparam = LPARAM(closure_pointer_pointer as _);
|
||||
unsafe extern "system" fn enumerate_callback(hwnd: HWND, lparam: LPARAM) -> BOOL {
|
||||
let closure = &mut *(lparam.0 as *mut c_void as *mut &mut dyn FnMut(HWND) -> bool);
|
||||
closure(hwnd).into()
|
||||
}
|
||||
let _ = unsafe { EnumChildWindows(Some(hwnd), Some(enumerate_callback), lparam) };
|
||||
}
|
||||
|
||||
controller
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inject_in_hwnd(&mut self, hwnd: HWND, handler: Rc<dyn Fn(DragDropEvent) -> bool>) -> bool {
|
||||
let drag_drop_target: IDropTarget = DragDropTarget::new(hwnd, handler).into();
|
||||
if unsafe { RevokeDragDrop(hwnd) } != Err(DRAGDROP_E_INVALIDHWND.into())
|
||||
&& unsafe { RegisterDragDrop(hwnd, &drag_drop_target) }.is_ok()
|
||||
{
|
||||
self.drop_targets.push(drag_drop_target);
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[implement(IDropTarget)]
|
||||
pub struct DragDropTarget {
|
||||
hwnd: HWND,
|
||||
listener: Rc<dyn Fn(DragDropEvent) -> bool>,
|
||||
cursor_effect: UnsafeCell<DROPEFFECT>,
|
||||
enter_is_valid: UnsafeCell<bool>, /* If the currently hovered item is not valid there must not be any `HoveredFileCancelled` emitted */
|
||||
}
|
||||
|
||||
impl DragDropTarget {
|
||||
pub fn new(hwnd: HWND, listener: Rc<dyn Fn(DragDropEvent) -> bool>) -> DragDropTarget {
|
||||
Self {
|
||||
hwnd,
|
||||
listener,
|
||||
cursor_effect: DROPEFFECT_NONE.into(),
|
||||
enter_is_valid: false.into(),
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn iterate_filenames<F>(
|
||||
data_obj: windows_core::Ref<'_, IDataObject>,
|
||||
mut callback: F,
|
||||
) -> Option<HDROP>
|
||||
where
|
||||
F: FnMut(PathBuf),
|
||||
{
|
||||
let drop_format = FORMATETC {
|
||||
cfFormat: CF_HDROP.0,
|
||||
ptd: ptr::null_mut(),
|
||||
dwAspect: DVASPECT_CONTENT.0,
|
||||
lindex: -1,
|
||||
tymed: TYMED_HGLOBAL.0 as u32,
|
||||
};
|
||||
|
||||
match data_obj
|
||||
.as_ref()
|
||||
.expect("Received null IDataObject")
|
||||
.GetData(&drop_format)
|
||||
{
|
||||
Ok(medium) => {
|
||||
let hdrop = HDROP(medium.u.hGlobal.0 as _);
|
||||
|
||||
// The second parameter (0xFFFFFFFF) instructs the function to return the item count
|
||||
let item_count = DragQueryFileW(hdrop, 0xFFFFFFFF, None);
|
||||
|
||||
for i in 0..item_count {
|
||||
// Get the length of the path string NOT including the terminating null character.
|
||||
// Previously, this was using a fixed size array of MAX_PATH length, but the
|
||||
// Windows API allows longer paths under certain circumstances.
|
||||
let character_count = DragQueryFileW(hdrop, i, None) as usize;
|
||||
|
||||
// Fill path_buf with the null-terminated file name
|
||||
let str_len = character_count + 1;
|
||||
let mut path_buf = vec![0; str_len];
|
||||
DragQueryFileW(hdrop, i, Some(&mut path_buf));
|
||||
callback(OsString::from_wide(&path_buf[0..character_count]).into());
|
||||
}
|
||||
|
||||
Some(hdrop)
|
||||
}
|
||||
Err(_error) => {
|
||||
#[cfg(feature = "tracing")]
|
||||
tracing::warn!(
|
||||
"{}",
|
||||
match _error.code() {
|
||||
windows::Win32::Foundation::DV_E_FORMATETC => {
|
||||
// If the dropped item is not a file this error will occur.
|
||||
// In this case it is OK to return without taking further action.
|
||||
"Error occurred while processing dropped/hovered item: item is not a file."
|
||||
}
|
||||
_ => "Unexpected error occurred while processing dropped/hovered item.",
|
||||
}
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
impl IDropTarget_Impl for DragDropTarget_Impl {
|
||||
fn DragEnter(
|
||||
&self,
|
||||
pDataObj: windows_core::Ref<'_, IDataObject>,
|
||||
_grfKeyState: MODIFIERKEYS_FLAGS,
|
||||
pt: &POINTL,
|
||||
pdwEffect: *mut DROPEFFECT,
|
||||
) -> windows::core::Result<()> {
|
||||
let mut pt = POINT { x: pt.x, y: pt.y };
|
||||
let _ = unsafe { ScreenToClient(self.hwnd, &mut pt) };
|
||||
|
||||
let mut paths = Vec::new();
|
||||
let hdrop = unsafe { DragDropTarget::iterate_filenames(pDataObj, |path| paths.push(path)) };
|
||||
|
||||
let enter_is_valid = hdrop.is_some();
|
||||
|
||||
if !enter_is_valid {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*self.enter_is_valid.get() = enter_is_valid;
|
||||
}
|
||||
|
||||
(self.listener)(DragDropEvent::Enter {
|
||||
paths,
|
||||
position: (pt.x as _, pt.y as _),
|
||||
});
|
||||
|
||||
let cursor_effect = if enter_is_valid {
|
||||
DROPEFFECT_COPY
|
||||
} else {
|
||||
DROPEFFECT_NONE
|
||||
};
|
||||
|
||||
unsafe {
|
||||
*pdwEffect = cursor_effect;
|
||||
*self.cursor_effect.get() = cursor_effect;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn DragOver(
|
||||
&self,
|
||||
_grfKeyState: MODIFIERKEYS_FLAGS,
|
||||
pt: &POINTL,
|
||||
pdwEffect: *mut DROPEFFECT,
|
||||
) -> windows::core::Result<()> {
|
||||
if unsafe { *self.enter_is_valid.get() } {
|
||||
let mut pt = POINT { x: pt.x, y: pt.y };
|
||||
let _ = unsafe { ScreenToClient(self.hwnd, &mut pt) };
|
||||
(self.listener)(DragDropEvent::Over {
|
||||
position: (pt.x as _, pt.y as _),
|
||||
});
|
||||
}
|
||||
|
||||
unsafe { *pdwEffect = *self.cursor_effect.get() };
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn DragLeave(&self) -> windows::core::Result<()> {
|
||||
if unsafe { *self.enter_is_valid.get() } {
|
||||
(self.listener)(DragDropEvent::Leave);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn Drop(
|
||||
&self,
|
||||
pDataObj: windows_core::Ref<'_, IDataObject>,
|
||||
_grfKeyState: MODIFIERKEYS_FLAGS,
|
||||
pt: &POINTL,
|
||||
_pdwEffect: *mut DROPEFFECT,
|
||||
) -> windows::core::Result<()> {
|
||||
if unsafe { *self.enter_is_valid.get() } {
|
||||
let mut pt = POINT { x: pt.x, y: pt.y };
|
||||
let _ = unsafe { ScreenToClient(self.hwnd, &mut pt) };
|
||||
|
||||
let mut paths = Vec::new();
|
||||
let hdrop = unsafe { DragDropTarget::iterate_filenames(pDataObj, |path| paths.push(path)) };
|
||||
(self.listener)(DragDropEvent::Drop {
|
||||
paths,
|
||||
position: (pt.x as _, pt.y as _),
|
||||
});
|
||||
|
||||
if let Some(hdrop) = hdrop {
|
||||
unsafe { DragFinish(hdrop) };
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
+1854
File diff suppressed because it is too large
Load Diff
+101
@@ -0,0 +1,101 @@
|
||||
// Copyright 2020-2023 Tauri Programme within The Commons Conservancy
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use windows::{
|
||||
core::{HRESULT, HSTRING, PCSTR},
|
||||
Win32::{
|
||||
Foundation::{FARPROC, HWND, S_OK},
|
||||
Graphics::Gdi::{
|
||||
GetDC, GetDeviceCaps, MonitorFromWindow, HMONITOR, LOGPIXELSX, MONITOR_DEFAULTTONEAREST,
|
||||
},
|
||||
System::LibraryLoader::{GetProcAddress, LoadLibraryW},
|
||||
UI::{
|
||||
HiDpi::{MDT_EFFECTIVE_DPI, MONITOR_DPI_TYPE},
|
||||
WindowsAndMessaging::IsProcessDPIAware,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
fn get_function_impl(library: &str, function: &str) -> FARPROC {
|
||||
let library = HSTRING::from(library);
|
||||
assert_eq!(function.chars().last(), Some('\0'));
|
||||
|
||||
// Library names we will use are ASCII so we can use the A version to avoid string conversion.
|
||||
let module = unsafe { LoadLibraryW(&library) }.unwrap_or_default();
|
||||
if module.is_invalid() {
|
||||
return None;
|
||||
}
|
||||
|
||||
unsafe { GetProcAddress(module, PCSTR::from_raw(function.as_ptr())) }
|
||||
}
|
||||
|
||||
macro_rules! get_function {
|
||||
($lib:expr, $func:ident) => {
|
||||
crate::webview2::util::get_function_impl($lib, concat!(stringify!($func), '\0'))
|
||||
.map(|f| unsafe { std::mem::transmute::<_, $func>(f) })
|
||||
};
|
||||
}
|
||||
|
||||
pub type GetDpiForWindow = unsafe extern "system" fn(hwnd: HWND) -> u32;
|
||||
pub type GetDpiForMonitor = unsafe extern "system" fn(
|
||||
hmonitor: HMONITOR,
|
||||
dpi_type: MONITOR_DPI_TYPE,
|
||||
dpi_x: *mut u32,
|
||||
dpi_y: *mut u32,
|
||||
) -> HRESULT;
|
||||
|
||||
static GET_DPI_FOR_WINDOW: Lazy<Option<GetDpiForWindow>> =
|
||||
Lazy::new(|| get_function!("user32.dll", GetDpiForWindow));
|
||||
static GET_DPI_FOR_MONITOR: Lazy<Option<GetDpiForMonitor>> =
|
||||
Lazy::new(|| get_function!("shcore.dll", GetDpiForMonitor));
|
||||
|
||||
pub const BASE_DPI: u32 = 96;
|
||||
pub fn dpi_to_scale_factor(dpi: u32) -> f64 {
|
||||
dpi as f64 / BASE_DPI as f64
|
||||
}
|
||||
|
||||
#[allow(non_snake_case)]
|
||||
pub unsafe fn hwnd_dpi(hwnd: HWND) -> u32 {
|
||||
if let Some(GetDpiForWindow) = *GET_DPI_FOR_WINDOW {
|
||||
// We are on Windows 10 Anniversary Update (1607) or later.
|
||||
match GetDpiForWindow(hwnd) {
|
||||
0 => BASE_DPI, // 0 is returned if hwnd is invalid
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
dpi => dpi as u32,
|
||||
}
|
||||
} else if let Some(GetDpiForMonitor) = *GET_DPI_FOR_MONITOR {
|
||||
// We are on Windows 8.1 or later.
|
||||
let monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||||
if monitor.is_invalid() {
|
||||
return BASE_DPI;
|
||||
}
|
||||
|
||||
let mut dpi_x = 0;
|
||||
let mut dpi_y = 0;
|
||||
#[allow(clippy::unnecessary_cast)]
|
||||
if GetDpiForMonitor(monitor, MDT_EFFECTIVE_DPI, &mut dpi_x, &mut dpi_y) == S_OK {
|
||||
dpi_x as u32
|
||||
} else {
|
||||
BASE_DPI
|
||||
}
|
||||
} else {
|
||||
let hdc = GetDC(Some(hwnd));
|
||||
if hdc.is_invalid() {
|
||||
return BASE_DPI;
|
||||
}
|
||||
|
||||
// We are on Vista or later.
|
||||
if IsProcessDPIAware().as_bool() {
|
||||
// If the process is DPI aware, then scaling must be handled by the application using
|
||||
// this DPI value.
|
||||
GetDeviceCaps(Some(hdc), LOGPIXELSX) as u32
|
||||
} else {
|
||||
// If the process is DPI unaware, then scaling is performed by the OS; we thus return
|
||||
// 96 (scale factor 1.0) to prevent the window from being re-scaled by both the
|
||||
// application and the WM.
|
||||
BASE_DPI
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user