Uploaded image for project: 'JDK'
  1. JDK
  2. JDK-8211999

Window positioning bugs due to overlapping GraphicsDevice bounds (Windows/HiDPI)

    XMLWordPrintable

Details

    • b24
    • x86_64
    • windows_10

    Backports

      Description

        ADDITIONAL SYSTEM INFORMATION :
        Tested on Java 10.0.2 and Java 12-ea on Windows 10. (Also tested on MacOS on Java 10.0.2; the bug is _not_ present there.)
        Multi-monitor setup as follows:
        Main monitor:
          1920x1080 external monitor, non-HiDPI (100% scaling)
        Secondary monitor (arranged to the right and slightly down of the main monitor):
          2560x1440 Thinkpad X1 Carbon laptop screen at 200% HiDPI scaling


        A DESCRIPTION OF THE PROBLEM :
        In a multi-monitor setting involving one HiDPI screen placed to the right of one regular monitor, on Windows 10, the bounds returned by GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()[x].getDefaultConfiguration().getBounds() are overlapping. This causes various secondary bugs, such as JDialog or JPopupMenu windows popping up on the wrong screen, or at half or double size (if they end up on a screen with a different DPI scaling than the intended one). See for instance the logic in JPopupMenu.getCurrentGraphicsConfiguration(Point), which depends on the various GraphicsConfiguration bounds to be non-overlapping.

        Overlapping GraphicsConfiguration bounds make it impossible to reliably position a JPopupMenu or Window, because a given screen coordinate may exist on more than one monitor. Testing on both MacOS and Windows, with similar monitor configurations, reveals the overlap behavior to exist only on Windows. Thus, the GraphicsConfiguration overlap is likely a bug rather than an intentional design choice. (One theory is that the GraphicsConfiguration's bounds' position is accidentally changed when the GraphicsConfiguration's defaultTransform is applied to convert its device resolution to a logical resolution.)

        This bug affects, in particular, the NetBeans IDE, where dialogs opened on the HiDPI monitor may appear on the non-HiDPI monitor at 50% of their correct size, making their contents unreadable.

        STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
        1) Find a laptop with Windows 10 and a HiDPI screen. Attach an external monitor.
        2) In the Windows 10 "Rearrange Multiple Displays" app, configure the external monitor to be the main monitor, and arrange the laptop monitor to be located on the _right-hand_ side of the main monitor. Under "Scale and layout", set the main monitor's scaling level to 100% and the laptop monitor's scaling level to 200%. 3) Run the attached ScreenCoordinateAmbiguityTester application.
        4) Move the mouse around the two screens. The TestFrame should follow the mouse pointer but instead may jump around or follow the mouse pointer from a long distance away, depending on which monitor the TestFrame is currently located at.

        EXPECTED VERSUS ACTUAL BEHAVIOR :
        EXPECTED -
        The TestFrame should follow the mouse pointer. I'd EXPECT the GraphicsConfiguration bounds to be as follows:

        Rectangle[x=0,y=0,width=1920,height=1080] (for the regular display to the left)
        Rectangle[x=1920,y=140,width=1280,height=720] (for the HiDPI display to the right--I've filled in the expected x=1920 here)
        ACTUAL -
        The TestFrame may jump around or follow the mouse pointer from a long distance away, possibly on the other monitor, depending on which monitor the TestFrame is currently located at. The ACTUAL GraphicsConfiguration bounds are as follows:

        Rectangle[x=0,y=0,width=1920,height=1080] (for the regular display to the left)
        Rectangle[x=960,y=70,width=1280,height=720] (for the HiDPI display to the right--overlapping the bounds of the display to the left!)

        ---------- BEGIN SOURCE ----------
        import java.awt.*;
        import java.awt.geom.*;
        import javax.swing.*;

        /**
         * Exhibit for a JDK bug on Windows in multi-monitor environments that include a HiDPI screen. In
         * certain configurations, this can cause
         * {@code GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()} to return
         * {@link GraphicsConfiguration} instances with overlapping bounds, leading to various bugs, e.g.
         * JPopupMenu or JDialog appearing in the wrong place and with the wrong size.
         *
         * <p>To test, run this application and move the mouse around and across the different monitors. The
         * TestFrame should follow the mouse pointer. When the bug is present, the TestFrame may jump around
         * or follow the mouse pointer from a long distance away, depending on which monitor the TestFrame
         * is currently located on. Moving the mouse from a non-HiDPI monitor to a HiDPI monitor, or vice
         * versa, may lead to a large jump in screen coordinates, and the same screen coordinate may be
         * printed for two different positions (on different monitors).
         *
         * <p>Tested as follows:

        <pre>
        ==== MacOS (correct behavior) ==================================================
        Main monitor:
          1440x900 MacBook Air laptop screen, non-retina (100% scaling)
        Secondary monitor (arranged to the right and slightly down of the main monitor):
          1920x1080 external monitor forced to HiDPI (retina) mode (200% scaling)

        Initial output from ScreenCoordinateAmbiguityTester:
          Java Version: 10.0.2
          bounds=java.awt.Rectangle[x=1440,y=81,width=960,height=540], transformed bounds=java.awt.geom.Rectangle2D$Double[x=2880.0,y=162.0,w=1920.0,h=1080.0]
          bounds=java.awt.Rectangle[x=0,y=0,width=1440,height=900], transformed bounds=java.awt.geom.Rectangle2D$Double[x=0.0,y=0.0,w=1440.0,h=900.0]

        (GraphicsConfiguration bounds are not overlapping)

        ==== Windows (incorrect behavior) ==============================================

        Main monitor:
          1920x1080 external monitor, non-HiDPI (100% scaling)
        Secondary monitor (arranged to the right and slightly down of the main monitor):
          2560x1440 Thinkpad X1 Carbon laptop screen at 200% HiDPI scaling

        Initial output from ScreenCoordinateAmbiguityTester:
          Java version: 10.0.2
          bounds=java.awt.Rectangle[x=960,y=70,width=1280,height=720], transformed bounds=java.awt.geom.Rectangle2D$Double[x=1920.0,y=140.0,w=2560.0,h=1440.0]
          bounds=java.awt.Rectangle[x=0,y=0,width=1920,height=1080], transformed bounds=java.awt.geom.Rectangle2D$Double[x=0.0,y=0.0,w=1920.0,h=1080.0]

        (GraphicsConfiguration bounds are overlapping! Also tested on Java 12-ea.)
        </pre>

         */
        public class ScreenCoordinateAmbiguityTester {
          private final Window popup = new JFrame("TestFrame");

          public ScreenCoordinateAmbiguityTester() {
            popup.setSize(300, 200);
            popup.setVisible(true);
            new Thread(new Runnable() {
              @Override
              public void run() {
                while (true) {
                  try {
                    Thread.sleep(200);
                  } catch (InterruptedException ex) {
                  }
                  SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                      PointerInfo pi = MouseInfo.getPointerInfo();
                      /* Note that MouseInfo.getPointerInfo().getDevice() will frequently return the wrong
                      result, probably as a result of the same bug. */
                      System.out.println(pi == null ? "null" :
                          (pi.getDevice().getIDstring() + ": " + pi.getLocation()));
                      if (pi != null) {
                        System.out.println(popup.getGraphicsConfiguration().getDevice().getIDstring());
                        popup.setLocation(pi.getLocation());
                      }
                    }
                  });
                }
              }
            }).start();
          }

          public static final void main(String args[]) {
            System.out.println("Java version: " + System.getProperty("java.version"));
            try {
              UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (
                ClassNotFoundException | IllegalAccessException |
                InstantiationException | UnsupportedLookAndFeelException e)
            {
            }
            for (GraphicsDevice graphicsDevice :
                GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices())
            {
              if (graphicsDevice.getType() == GraphicsDevice.TYPE_RASTER_SCREEN) {
                final GraphicsConfiguration gc = graphicsDevice.getDefaultConfiguration();
                Rectangle bounds = gc.getBounds();
                Rectangle2D boundsTX = transformRect(bounds, gc.getDefaultTransform());
                System.out.println("bounds=" + bounds + ", transformed bounds=" + boundsTX);
              }
            }
            SwingUtilities.invokeLater(new Runnable() {
              @Override
              public void run() {
                new ScreenCoordinateAmbiguityTester();
              }
            });
          }

          private static Rectangle2D transformRect(Rectangle2D rect, AffineTransform tx) {
            Point2D p1 = tx.transform(new Point2D.Double(rect.getMinX(), rect.getMinY()), null);
            Point2D p2 = tx.transform(new Point2D.Double(rect.getMaxX(), rect.getMaxY()), null);
            double minX = Math.min(p1.getX(), p2.getX());
            double minY = Math.min(p1.getY(), p2.getY());
            double maxX = Math.max(p1.getX(), p2.getX());
            double maxY = Math.max(p1.getY(), p2.getY());
            return new Rectangle2D.Double(minX, minY, maxX - minX, maxY - minY);
          }
        }

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

        CUSTOMER SUBMITTED WORKAROUND :
        None known. In particular, I spent several hours trying to find a way to reliably place a JFrame on a given screen and screen coordinate; I found no workaround that did not have significant bugs.

        FREQUENCY : always


        Attachments

          Issue Links

            Activity

              People

                serb Sergey Bylokhov
                webbuggrp Webbug Group
                Votes:
                0 Vote for this issue
                Watchers:
                9 Start watching this issue

                Dates

                  Created:
                  Updated:
                  Resolved: