From fd0d3a4d707f060c4df3c60bbab3634b694a75ef Mon Sep 17 00:00:00 2001 From: Alex Charlton Date: Fri, 22 May 2026 22:06:39 -0700 Subject: [PATCH 1/4] Win: Fix drag mouse position for parented windows --- src/platform/win/drop_target.rs | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/platform/win/drop_target.rs b/src/platform/win/drop_target.rs index 5d79e6b2..b11f65ee 100644 --- a/src/platform/win/drop_target.rs +++ b/src/platform/win/drop_target.rs @@ -11,6 +11,7 @@ use windows::Win32::System::SystemServices::MODIFIERKEYS_FLAGS; use windows_core::Ref; use windows_sys::Win32::{ Foundation::POINT, Graphics::Gdi::ScreenToClient, UI::Shell::DragQueryFileW, + UI::WindowsAndMessaging::GetCursorPos, }; use crate::{DropData, DropEffect, Event, EventStatus, MouseEvent, PhyPoint, Point}; @@ -58,14 +59,25 @@ impl DropTarget { } } - fn parse_coordinates(&self, pt: POINTL) { + fn parse_coordinates(&self, _pt: POINTL) { let Some(window_state) = self.window_state.upgrade() else { return; }; - let mut pt = POINT { x: pt.x, y: pt.y }; - unsafe { ScreenToClient(window_state.hwnd, &mut pt as *mut POINT) }; - let phy_point = PhyPoint::new(pt.x, pt.y); - self.drag_position.set(phy_point.to_logical(&window_state.window_info())); + // OLE-supplied points can disagree with the actual cursor position for embedded + // child windows (DPI virtualization / DragEnter quirks). Query the cursor directly + // so drag coordinates match WM_MOUSEMOVE. + let mut pt = POINT { x: 0, y: 0 }; + unsafe { + GetCursorPos(&mut pt as *mut POINT); + ScreenToClient(window_state.hwnd, &mut pt as *mut POINT); + } + let logical_point = if window_state.has_parent() { + // If the window has a parent, the coordinates are already in logical coordinates + Point::new(pt.x as f64, pt.y as f64) + } else { + PhyPoint::new(pt.x, pt.y).to_logical(&window_state.window_info()) + }; + self.drag_position.set(logical_point); } fn parse_drop_data(&self, data_object: &IDataObject) { From 111d4a30458af0146d8adecd4a6c3a6365222880 Mon Sep 17 00:00:00 2001 From: Alex Charlton Date: Tue, 26 May 2026 14:46:57 -0700 Subject: [PATCH 2/4] Fix inconsistent scale --- src/platform/win/drop_target.rs | 70 +++++++++++++++++++++++++-------- 1 file changed, 54 insertions(+), 16 deletions(-) diff --git a/src/platform/win/drop_target.rs b/src/platform/win/drop_target.rs index b11f65ee..47a6612f 100644 --- a/src/platform/win/drop_target.rs +++ b/src/platform/win/drop_target.rs @@ -10,8 +10,10 @@ use windows::Win32::System::Ole::*; use windows::Win32::System::SystemServices::MODIFIERKEYS_FLAGS; use windows_core::Ref; use windows_sys::Win32::{ - Foundation::POINT, Graphics::Gdi::ScreenToClient, UI::Shell::DragQueryFileW, - UI::WindowsAndMessaging::GetCursorPos, + Foundation::{POINT, RECT}, + Graphics::Gdi::ScreenToClient, + UI::Shell::DragQueryFileW, + UI::WindowsAndMessaging::{GetClientRect, GetCursorPos}, }; use crate::{DropData, DropEffect, Event, EventStatus, MouseEvent, PhyPoint, Point}; @@ -26,6 +28,10 @@ pub(crate) struct DropTarget { // and handling drag move events gets awkward on the client end otherwise drag_position: Cell, drop_data: RefCell, + /// Whether drag client coordinates are physical pixels (`true`) or already logical + /// (`false`). Cached when `is_drag_coords_physical` is called, and cleared on `DragLeave` + /// and `Drop`. + drag_coords_physical: Cell>, } impl DropTarget { @@ -34,6 +40,7 @@ impl DropTarget { window_state, drag_position: Cell::new(Point::new(0.0, 0.0)), drop_data: RefCell::new(DropData::None), + drag_coords_physical: Cell::new(None), } } @@ -59,23 +66,52 @@ impl DropTarget { } } - fn parse_coordinates(&self, _pt: POINTL) { + /// Returns `true` when client coordinates from `GetCursorPos`/`ScreenToClient` are physical + /// pixels and should be scaled with [`PhyPoint::to_logical`]. Returns `false` when they are + /// already in logical space. + /// + /// For some reason, this can vary based on the combination of parent window AND drag source. + /// Most of the time the coordinates are physical, but logical coordinates have been observed + /// with Bitwig as the parent and Windows Explorer as the drag source. + /// + /// Cached on self.drag_coords_physical. + fn is_drag_coords_physical(&self, window_state: &WindowState) -> bool { + match self.drag_coords_physical.get() { + Some(physical) => physical, + None => { + let mut rect = RECT { left: 0, top: 0, right: 0, bottom: 0 }; + unsafe { GetClientRect(window_state.hwnd, &mut rect) }; + let client_w = (rect.right - rect.left) as u32; + let physical_w = window_state.window_info().physical_size().width; + let logical_w = window_state.window_info().logical_size().width as u32; + let physical = client_w.abs_diff(physical_w) < client_w.abs_diff(logical_w); + + self.drag_coords_physical.set(Some(physical)); + physical + } + } + } + + fn end_drag_session(&self) { + self.drag_coords_physical.set(None); + } + + fn parse_coordinates(&self) { let Some(window_state) = self.window_state.upgrade() else { return; }; - // OLE-supplied points can disagree with the actual cursor position for embedded - // child windows (DPI virtualization / DragEnter quirks). Query the cursor directly - // so drag coordinates match WM_MOUSEMOVE. + + // Some parents pass weird coordinates via OLE `pt`. Query the cursor directly instead. let mut pt = POINT { x: 0, y: 0 }; unsafe { GetCursorPos(&mut pt as *mut POINT); ScreenToClient(window_state.hwnd, &mut pt as *mut POINT); } - let logical_point = if window_state.has_parent() { - // If the window has a parent, the coordinates are already in logical coordinates - Point::new(pt.x as f64, pt.y as f64) - } else { + + let logical_point = if self.is_drag_coords_physical(&window_state) { PhyPoint::new(pt.x, pt.y).to_logical(&window_state.window_info()) + } else { + Point::new(pt.x as f64, pt.y as f64) }; self.drag_position.set(logical_point); } @@ -123,7 +159,7 @@ impl DropTarget { #[allow(non_snake_case)] impl IDropTarget_Impl for DropTarget_Impl { fn DragEnter( - &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, pt: &POINTL, + &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, _pt: &POINTL, pdweffect: *mut DROPEFFECT, ) -> windows_core::Result<()> { let Some(window_state) = self.window_state.upgrade() else { @@ -133,7 +169,7 @@ impl IDropTarget_Impl for DropTarget_Impl { let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfkeystate.0 as usize); - self.parse_coordinates(*pt); + self.parse_coordinates(); self.parse_drop_data(pdataobj.unwrap()); let event = MouseEvent::DragEntered { @@ -147,7 +183,7 @@ impl IDropTarget_Impl for DropTarget_Impl { } fn DragOver( - &self, grfkeystate: MODIFIERKEYS_FLAGS, pt: &POINTL, pdweffect: *mut DROPEFFECT, + &self, grfkeystate: MODIFIERKEYS_FLAGS, _pt: &POINTL, pdweffect: *mut DROPEFFECT, ) -> windows_core::Result<()> { let Some(window_state) = self.window_state.upgrade() else { return Err(E_UNEXPECTED.into()); @@ -156,7 +192,7 @@ impl IDropTarget_Impl for DropTarget_Impl { let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfkeystate.0 as usize); - self.parse_coordinates(*pt); + self.parse_coordinates(); let event = MouseEvent::DragMoved { position: self.drag_position.get(), @@ -169,12 +205,13 @@ impl IDropTarget_Impl for DropTarget_Impl { } fn DragLeave(&self) -> windows_core::Result<()> { + self.end_drag_session(); self.on_event(None, MouseEvent::DragLeft); Ok(()) } fn Drop( - &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, pt: &POINTL, + &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, _pt: &POINTL, pdweffect: *mut DROPEFFECT, ) -> windows_core::Result<()> { let Some(window_state) = self.window_state.upgrade() else { @@ -184,7 +221,7 @@ impl IDropTarget_Impl for DropTarget_Impl { let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfkeystate.0 as usize); - self.parse_coordinates(*pt); + self.parse_coordinates(); self.parse_drop_data(pdataobj.unwrap()); let event = MouseEvent::DragDropped { @@ -194,6 +231,7 @@ impl IDropTarget_Impl for DropTarget_Impl { }; self.on_event(Some(pdweffect), event); + self.end_drag_session(); Ok(()) } } From 0da2ffd80206990e68e29f9a63212b1078b6d871 Mon Sep 17 00:00:00 2001 From: Alex Charlton Date: Fri, 19 Jun 2026 13:09:21 -0700 Subject: [PATCH 3/4] Revert --- src/platform/win/drop_target.rs | 74 ++++++--------------------------- 1 file changed, 12 insertions(+), 62 deletions(-) diff --git a/src/platform/win/drop_target.rs b/src/platform/win/drop_target.rs index 47a6612f..5d79e6b2 100644 --- a/src/platform/win/drop_target.rs +++ b/src/platform/win/drop_target.rs @@ -10,10 +10,7 @@ use windows::Win32::System::Ole::*; use windows::Win32::System::SystemServices::MODIFIERKEYS_FLAGS; use windows_core::Ref; use windows_sys::Win32::{ - Foundation::{POINT, RECT}, - Graphics::Gdi::ScreenToClient, - UI::Shell::DragQueryFileW, - UI::WindowsAndMessaging::{GetClientRect, GetCursorPos}, + Foundation::POINT, Graphics::Gdi::ScreenToClient, UI::Shell::DragQueryFileW, }; use crate::{DropData, DropEffect, Event, EventStatus, MouseEvent, PhyPoint, Point}; @@ -28,10 +25,6 @@ pub(crate) struct DropTarget { // and handling drag move events gets awkward on the client end otherwise drag_position: Cell, drop_data: RefCell, - /// Whether drag client coordinates are physical pixels (`true`) or already logical - /// (`false`). Cached when `is_drag_coords_physical` is called, and cleared on `DragLeave` - /// and `Drop`. - drag_coords_physical: Cell>, } impl DropTarget { @@ -40,7 +33,6 @@ impl DropTarget { window_state, drag_position: Cell::new(Point::new(0.0, 0.0)), drop_data: RefCell::new(DropData::None), - drag_coords_physical: Cell::new(None), } } @@ -66,54 +58,14 @@ impl DropTarget { } } - /// Returns `true` when client coordinates from `GetCursorPos`/`ScreenToClient` are physical - /// pixels and should be scaled with [`PhyPoint::to_logical`]. Returns `false` when they are - /// already in logical space. - /// - /// For some reason, this can vary based on the combination of parent window AND drag source. - /// Most of the time the coordinates are physical, but logical coordinates have been observed - /// with Bitwig as the parent and Windows Explorer as the drag source. - /// - /// Cached on self.drag_coords_physical. - fn is_drag_coords_physical(&self, window_state: &WindowState) -> bool { - match self.drag_coords_physical.get() { - Some(physical) => physical, - None => { - let mut rect = RECT { left: 0, top: 0, right: 0, bottom: 0 }; - unsafe { GetClientRect(window_state.hwnd, &mut rect) }; - let client_w = (rect.right - rect.left) as u32; - let physical_w = window_state.window_info().physical_size().width; - let logical_w = window_state.window_info().logical_size().width as u32; - let physical = client_w.abs_diff(physical_w) < client_w.abs_diff(logical_w); - - self.drag_coords_physical.set(Some(physical)); - physical - } - } - } - - fn end_drag_session(&self) { - self.drag_coords_physical.set(None); - } - - fn parse_coordinates(&self) { + fn parse_coordinates(&self, pt: POINTL) { let Some(window_state) = self.window_state.upgrade() else { return; }; - - // Some parents pass weird coordinates via OLE `pt`. Query the cursor directly instead. - let mut pt = POINT { x: 0, y: 0 }; - unsafe { - GetCursorPos(&mut pt as *mut POINT); - ScreenToClient(window_state.hwnd, &mut pt as *mut POINT); - } - - let logical_point = if self.is_drag_coords_physical(&window_state) { - PhyPoint::new(pt.x, pt.y).to_logical(&window_state.window_info()) - } else { - Point::new(pt.x as f64, pt.y as f64) - }; - self.drag_position.set(logical_point); + let mut pt = POINT { x: pt.x, y: pt.y }; + unsafe { ScreenToClient(window_state.hwnd, &mut pt as *mut POINT) }; + let phy_point = PhyPoint::new(pt.x, pt.y); + self.drag_position.set(phy_point.to_logical(&window_state.window_info())); } fn parse_drop_data(&self, data_object: &IDataObject) { @@ -159,7 +111,7 @@ impl DropTarget { #[allow(non_snake_case)] impl IDropTarget_Impl for DropTarget_Impl { fn DragEnter( - &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, _pt: &POINTL, + &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, pt: &POINTL, pdweffect: *mut DROPEFFECT, ) -> windows_core::Result<()> { let Some(window_state) = self.window_state.upgrade() else { @@ -169,7 +121,7 @@ impl IDropTarget_Impl for DropTarget_Impl { let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfkeystate.0 as usize); - self.parse_coordinates(); + self.parse_coordinates(*pt); self.parse_drop_data(pdataobj.unwrap()); let event = MouseEvent::DragEntered { @@ -183,7 +135,7 @@ impl IDropTarget_Impl for DropTarget_Impl { } fn DragOver( - &self, grfkeystate: MODIFIERKEYS_FLAGS, _pt: &POINTL, pdweffect: *mut DROPEFFECT, + &self, grfkeystate: MODIFIERKEYS_FLAGS, pt: &POINTL, pdweffect: *mut DROPEFFECT, ) -> windows_core::Result<()> { let Some(window_state) = self.window_state.upgrade() else { return Err(E_UNEXPECTED.into()); @@ -192,7 +144,7 @@ impl IDropTarget_Impl for DropTarget_Impl { let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfkeystate.0 as usize); - self.parse_coordinates(); + self.parse_coordinates(*pt); let event = MouseEvent::DragMoved { position: self.drag_position.get(), @@ -205,13 +157,12 @@ impl IDropTarget_Impl for DropTarget_Impl { } fn DragLeave(&self) -> windows_core::Result<()> { - self.end_drag_session(); self.on_event(None, MouseEvent::DragLeft); Ok(()) } fn Drop( - &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, _pt: &POINTL, + &self, pdataobj: Ref, grfkeystate: MODIFIERKEYS_FLAGS, pt: &POINTL, pdweffect: *mut DROPEFFECT, ) -> windows_core::Result<()> { let Some(window_state) = self.window_state.upgrade() else { @@ -221,7 +172,7 @@ impl IDropTarget_Impl for DropTarget_Impl { let modifiers = window_state.keyboard_state().get_modifiers_from_mouse_wparam(grfkeystate.0 as usize); - self.parse_coordinates(); + self.parse_coordinates(*pt); self.parse_drop_data(pdataobj.unwrap()); let event = MouseEvent::DragDropped { @@ -231,7 +182,6 @@ impl IDropTarget_Impl for DropTarget_Impl { }; self.on_event(Some(pdweffect), event); - self.end_drag_session(); Ok(()) } } From e0dce59cc01808f5ec43998c1494b4698d4906d0 Mon Sep 17 00:00:00 2001 From: Alex Charlton Date: Fri, 19 Jun 2026 13:10:09 -0700 Subject: [PATCH 4/4] DPI scaling before and after ScreenToClient --- src/platform/win/drop_target.rs | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/platform/win/drop_target.rs b/src/platform/win/drop_target.rs index 5d79e6b2..489a6aec 100644 --- a/src/platform/win/drop_target.rs +++ b/src/platform/win/drop_target.rs @@ -10,7 +10,12 @@ use windows::Win32::System::Ole::*; use windows::Win32::System::SystemServices::MODIFIERKEYS_FLAGS; use windows_core::Ref; use windows_sys::Win32::{ - Foundation::POINT, Graphics::Gdi::ScreenToClient, UI::Shell::DragQueryFileW, + Foundation::POINT, + Graphics::Gdi::ScreenToClient, + UI::{ + HiDpi::{LogicalToPhysicalPointForPerMonitorDPI, PhysicalToLogicalPointForPerMonitorDPI}, + Shell::DragQueryFileW, + }, }; use crate::{DropData, DropEffect, Event, EventStatus, MouseEvent, PhyPoint, Point}; @@ -63,7 +68,19 @@ impl DropTarget { return; }; let mut pt = POINT { x: pt.x, y: pt.y }; - unsafe { ScreenToClient(window_state.hwnd, &mut pt as *mut POINT) }; + + // ScreenToClient isn't DPI-aware + if unsafe { PhysicalToLogicalPointForPerMonitorDPI(null_mut(), &mut pt) == 0 } { + return; + } + + if unsafe { ScreenToClient(window_state.hwnd, &mut pt) == 0 } { + return; + } + + if unsafe { LogicalToPhysicalPointForPerMonitorDPI(null_mut(), &mut pt) == 0 } { + return; + } let phy_point = PhyPoint::new(pt.x, pt.y); self.drag_position.set(phy_point.to_logical(&window_state.window_info())); }