JDK-8238575 : DragSourceEvent.getLocation() returns wrong value on HiDPI screens (Windows)
  • Type: Bug
  • Component: client-libs
  • Sub-Component: java.awt
  • Affected Version: 11,13,14,15
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2020-02-05
  • Updated: 2020-07-21
  • Resolved: 2020-04-27
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 11 JDK 13 JDK 15
11.0.8-oracleFixed 13.0.4Fixed 15 b22Fixed
Description
ADDITIONAL SYSTEM INFORMATION :
OpenJDK 64-Bit Server VM 13+33
Windows 10 with HiDPI scaling active (e.g. 150% or 200%)

A DESCRIPTION OF THE PROBLEM :
On Windows 10, on a monitor with HiDPI scaling active, DragSourceEvent.getLocation() will consistently return the wrong value.

=== Debugging notes: ===

Both MouseInfo and DragSourceEvent get their coordinates from a call to the "GetCursorPos" Windows API function. The latter returns device coordinates rather than logical coordinates for DPI-aware applications (including java.exe and the netbeans launcher since #883 ).

But in the implementation of MouseInfo.getPointerInfo(), extra code was added to convert the device coordinates to logical coordinates, as part of the original JDK patch which introduced HiDPI support on Windows. See Java_sun_awt_windows_WMouseInfoPeer_fillPointWithCoords in https://github.com/openjdk/jdk/blame/6bab0f539fba8fb441697846347597b4a0ade428/src/java.desktop/windows/native/libawt/windows/MouseInfo.cpp . This was forgotten in the code that initializes the coordinates for DragSourceEvent (see GetCursorPos call for call_dSCenter/call_dSCmotion in https://github.com/openjdk/jdk/blob/1440dc39400f89b7df8af2249f30eaf5092c1574/src/java.desktop/windows/native/libawt/windows/awt_DnDDS.cpp ).

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
On Windows 10, configure the display with a single monitor at 150% DPI scaling. Run the attached executable test case. Drag the selected text from the JTextField to the JLabel in the middle of the JFrame. While dragging, an "X" should be painted underneath the mouse cursor--but due to the bug, the X ends up in the wrong spot.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
While dragging, an "X" should be painted underneath the mouse cursor.
ACTUAL -
The X ends up in the wrong spot. (Because DragSourceEvent.getLocation() ends up returning device coordinates instead of logical coordinates.)

---------- BEGIN SOURCE ----------

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Point;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;
import javax.swing.TransferHandler;
import java.awt.EventQueue;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.dnd.DragSource;
import java.awt.dnd.DragSourceDragEvent;
import java.awt.dnd.DragSourceDropEvent;
import java.awt.dnd.DragSourceEvent;
import java.awt.dnd.DragSourceListener;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.List;
import java.util.Map.Entry;
import javax.swing.JLabel;
import javax.swing.SwingUtilities;

// See example at http://zetcode.com/tutorials/javaswingtutorial/draganddrop
public class DragSourceEventHiDPIBugExhibit extends JFrame {
  private final List<Entry<String,Point>> pointsToDraw = new ArrayList<>();
  private JTextField field;
  private JLabel label;

  public DragSourceEventHiDPIBugExhibit() {
    initComponents();
    DragSource ds = DragSource.getDefaultDragSource();
    ds.addDragSourceListener(new DragSourceListener() {
      private void reportEvent(DragSourceEvent dse, String method) {
        pointsToDraw.add(new SimpleEntry<>(method, new Point(dse.getX(), dse.getY())));
        if (pointsToDraw.size() > 100)
          pointsToDraw.remove(0);
        repaint();
      }

      @Override
      public void dragEnter(DragSourceDragEvent dsde) {
        reportEvent(dsde, "enter");
      }

      @Override
      public void dragOver(DragSourceDragEvent dsde) {
        reportEvent(dsde, "over");
      }

      @Override
      public void dropActionChanged(DragSourceDragEvent dsde) {
        reportEvent(dsde, "changed");
      }

      @Override
      public void dragExit(DragSourceEvent dse) {
        reportEvent(dse, "exit");
      }

      @Override
      public void dragDropEnd(DragSourceDropEvent dsde) {
        reportEvent(dsde, "end");
      }
    });
  }

  @Override
  public void paint(Graphics g) {
    super.paint(g);
    g.setColor(Color.BLACK);
    for (Entry<String,Point> entry : pointsToDraw) {
      Point p = entry.getValue();
      SwingUtilities.convertPointFromScreen(p, this);
      final int x = p.x;
      final int y = p.y;
      // Draw an "x" with the poitn in the middle
      g.drawLine(x - 3, y - 3, x + 3, y + 3);
      g.drawLine(x + 3, y - 3, x - 3, y + 3);
      g.setFont(new Font("Dialog", 0, 10));
      g.drawString(entry.getKey(), x + 5, y);
    }
  }

  private void initComponents() {
    setTitle("Exhibit");

    setLayout(new BorderLayout());
    field = new JTextField(15);
    field.setText("Drag selected text from the text field to the label below.");
    field.selectAll();

    field.setDragEnabled(true);
    label = new JLabel("During dragging, an X should be painted at the mouse location.");
    label.setOpaque(false);
    label.setTransferHandler(new TransferHandler("text"));
    add(field, BorderLayout.NORTH);
    add(label, BorderLayout.CENTER);
    pack();
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    setLocationRelativeTo(null);
  }

  public static void main(String[] args) {
    EventQueue.invokeLater(() -> {
      JFrame ex = new DragSourceEventHiDPIBugExhibit();
      ex.setSize(800, 600);
      ex.setVisible(true);
    });
  }
}

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
In the NetBeans IDE, a workaround was to use MouseInfo.getPointerInfo() instead of DragSourceEvent.getLocation(). See https://github.com/apache/netbeans/pull/1804 .

FREQUENCY : always



Comments
Submitter has confirmed the fix resolved the issue: "Yes, the bug JDK-8238575 is gone from JDK 11.0.8. Using the DragSourceEventHiDPIBugExhibit class, I can confirm that it is present in 10.0.2 and absent in 11.0.8. I also tested an older version of the NetBeans IDE which did not implement a workaround, and found the bug to be absent on 11.0.8. Tested on Windows 10 with two monitors at 150% HiDPI scaling, and with two monitors at 150%/200% HiDPI scaling. Thank you for fixing this!"
21-07-2020

Fix request (13u) Requesting backport to 13u for parity with 11u, applies cleanly.
10-06-2020

jdk11 backport request I would like to have the patch in OpenJDK11 as well (for better parity with 11.0.8_oracle). The patch needs some adjustment, review thread is here : https://mail.openjdk.java.net/pipermail/jdk-updates-dev/2020-May/003067.html .
12-05-2020

URL: https://hg.openjdk.java.net/jdk/jdk/rev/88f67bfdb84a User: psadhukhan Date: 2020-05-02 04:34:47 +0000
02-05-2020

URL: https://hg.openjdk.java.net/jdk/client/rev/88f67bfdb84a User: serb Date: 2020-04-27 10:28:46 +0000
27-04-2020

Pending review
16-04-2020

Additional Information: Note that the reason this seems to work on Java 8 is that Java 8 does not support HiDPI scaling on Windows 10. So in practice, when you run the sample on Java 8, it works as if the HiDPI scaling was 100%.
17-02-2020

Initially discussed here: https://twitter.com/serb___/status/1224766732968980481
06-02-2020

As per description, on a HiDPI scaling active monitor (Windows 10), DragSourceEvent.getLocation() will consistently return the wrong value. Additional debugging information provided in the description. Verified this with reported version and could confirm this as an issue with JDK 9 and above. This works fine with JDK 8u. Result: ====== 8u241: OK 9: Fail 11: Fail 13.0.2: Fail 14 ea b34: Fail 15 ea b8: Fail This seems a regression, refer to: https://github.com/openjdk/jdk/blame/6bab0f539fba8fb441697846347597b4a0ade428/src/java.desktop/windows/native/libawt/windows/MouseInfo.cpp https://github.com/openjdk/jdk/blob/1440dc39400f89b7df8af2249f30eaf5092c1574/src/java.desktop/windows/native/libawt/windows/awt_DnDDS.cpp
06-02-2020