Project

General

Profile

Bug #9571

Swing incorrectly calculates usable area with multiple devices (monitors)

Added by Roger Borrello over 1 year ago. Updated over 1 year ago.

Status:
Test
Priority:
Low
Target version:
-
Start date:
Due date:
% Done:

100%

billable:
No
vendor_id:
GCD
case_num:
version_reported:
version_resolved:
reviewer:
production:
No
env_name:
topics:

9571a.diff Magnifier (1.39 KB) Hynek Cihlar, 01/30/2025 09:32 AM

History

#1 Updated by Roger Borrello over 1 year ago

Was testing Swing client for Hotel GUI. I have 2 monitors. I receive an NPE in p2j.ui.client.gui.driver.swing.SwingEmulatedWindow.getDisplayWorkArea().

   @Override
   public NativeRectangle getDisplayWorkArea()
   {
      synchronized(lock())
      {
         // get the default screen
         GraphicsEnvironment ge = GraphicsEnvironment.getLocalGraphicsEnvironment();
         GraphicsDevice[] devices = ge.getScreenDevices();

         GraphicsConfiguration targetConfig = null;
         Rectangle targetBounds = null;
         if (devices.length == 1)
         {
            targetConfig = devices[0].getDefaultConfiguration();
            targetBounds = targetConfig.getBounds();
         }
         else
         {
            int maxArea = 0;
            Rectangle windowBounds = window.getBounds();
            for (GraphicsDevice device : devices)
            {
               GraphicsConfiguration config = device.getDefaultConfiguration();
               Rectangle bounds = config.getBounds();

               Rectangle intersection = windowBounds.intersection(bounds);
               int area = intersection.width * intersection.height;
               if (area > maxArea)
               {
                  maxArea = area;
                  targetConfig = config;
                  targetBounds = bounds;
               }
            }
         }

         Insets i = window.getToolkit().getScreenInsets(targetConfig);
         // screen insets must be taken into account
         return new NativeRectangle(new NativePoint(targetBounds.x + i.left, targetBounds.y + i.top),
                                    new NativeDimension(targetBounds.width - i.left - i.right,
                                                        targetBounds.height - i.top - i.bottom));
      }
   }

The area is calculated as negative, so the targetConfig is never moved off null. The Rectangle intersection = windowBounds.intersection(bounds); returns with a negative coordinate, so the product is negative.

#2 Updated by Roger Borrello over 1 year ago

  • Status changed from New to WIP
  • Assignee set to Roger Borrello
  • % Done changed from 0 to 100
  • reviewer Hynek Cihlar added

The algorithm for computing the usable area would only break in certain monitor configurations, where the base monitor was to the "left" of the primary monitor. In those cases, the area would never be computed as greater than maxArea, since it would be negative.

Specifically (in my setup):

In all cases, windowBounds has height=546, width=402, x=0, y=0.

With external monitor primary (device=0), and laptop (device=1) on right:

bounds[0].height=2160, bounds[0].width=3840, x[0]=3072, y[0]=0
bounds[1].height=1728, bounds[1].width=3072, x[1]=0, y[1]=931

With external monitor primary (device=0) and laptop (device=1) on left:
bounds[0].height=2160, bounds[0].width=3840, x[0]=0, y[0]=0
bounds[1].height=1728, bounds[1].width=3072, x[1]=3840, y[1]=1220

With laptop as primary (device=0) and external monitor (device=1), and laptop on right:

bounds[0].height=1728, bounds[0].width=3072, x[0]=3840, y[0]=1220
bounds[1].height=2160, bounds[1].width=3840, x[1]=0, y[1]=0

With laptop primary (device=0) and external monitor (device=1) and laptop on left:
bounds[0].height=1728, bounds[0].width=3072, x[0]=0, y[0]=1262
bounds[1].height=2160, bounds[1].width=3840, x[1]=072, y[1]=0

When bounds = java.awt.Rectangle[height=1728, width=3072, x=0, y=1262] for the laptop, the intersection = java.awt.Rectangle[height=-716, width=402, x=0, y=1262].

When bounds = java.awt.Rectangle[height=2160, width=3840, x=3072, y=0] for the monitor, the intersection = java.awt.Rectangle[height=546, width=-2670, x=3072, y=0]

It will always compute negative in those cases.

So I found a better approach where we simply determine which monitor contains the center point of the window being investigated, and use the monitor that contains that center point.

I created branch 9571a and revision 15666 contains this update.

#3 Updated by Roger Borrello over 1 year ago

  • Status changed from WIP to Review

#4 Updated by Hynek Cihlar over 1 year ago

Roger, while the center point window idea will certainly work in most cases, there will be edge cases, where wrong device will be picked.

Consider a three monitors setup. A window spanning all the three monitors, the center point being in the middle monitor. Even if the center monitor is smaller and the window spans the least area there it would still be picked unexpectedly as the window's device.

I think the greatest window area approach makes the most sense as it always yields the expected result. What do you think?

#5 Updated by Roger Borrello over 1 year ago

  • % Done changed from 100 to 90
  • Status changed from Review to WIP
  • Assignee deleted (Roger Borrello)
  • Priority changed from Normal to Low

Hynek Cihlar wrote:

Roger, while the center point window idea will certainly work in most cases, there will be edge cases, where wrong device will be picked.

Consider a three monitors setup. A window spanning all the three monitors, the center point being in the middle monitor. Even if the center monitor is smaller and the window spans the least area there it would still be picked unexpectedly as the window's device.

I think the greatest window area approach makes the most sense as it always yields the expected result. What do you think?

Really I don't understand why there was an algorithm in this method at all. Clearly the primary display is the display that any user would prefer the application windows appear on. I was just trying to make a compromise, but I do see your point.

I'll leave the status of this task up to Greg, since it only seems to affect Swing when the primary monitor is on the right. There may be cases when it could be above or below, as well, or many more monitors can have other outcomes.

#6 Updated by Greg Shah over 1 year ago

When using our Swing client, a user would expect that the application will start on the current monitor (regardless of how large it is). This is how the GUI client works in OE.

#7 Updated by Hynek Cihlar over 1 year ago

The method where Roger identified the issues is used in multiple use cases. To illustrate one of the use cases is maximizing a window. If an unmaximized window spans multiple screens maximizing it should make the window take the whole area of a screen it should belong to. The algorithm chooses this screen based on the max area the window occupies on any of the available screens.

#8 Updated by Roger Borrello over 1 year ago

This might just be my opinion, but if I am maximizing a window, I'd want it to occupy the monitor which I set as primary.

#9 Updated by Hynek Cihlar over 1 year ago

Roger Borrello wrote:

This might just be my opinion, but if I am maximizing a window, I'd want it to occupy the monitor which I set as primary.

That would be very confusing and bad UX.

#10 Updated by Hynek Cihlar over 1 year ago

Hynek Cihlar wrote:

Roger Borrello wrote:

This might just be my opinion, but if I am maximizing a window, I'd want it to occupy the monitor which I set as primary.

That would be very confusing and bad UX.

Anyway, this is how modern desktops behave (including Windows).

#11 Updated by Roger Borrello over 1 year ago

Hynek Cihlar wrote:

Roger Borrello wrote:

This might just be my opinion, but if I am maximizing a window, I'd want it to occupy the monitor which I set as primary.

That would be very confusing and bad UX.

:-) I can be very confused... In reality, with my multi-monitor configuration, if I expanded an application on my secondary monitor, yes, I'd want it to maximize there. It is a compromise to determine where the center point of that window is, versus where the majority of the window is. I am just not smart enough to adjust the coordinates of multiple monitors to make the accurate determination.

#12 Updated by Greg Shah over 1 year ago

Let's avoid the abend. We don't need to make deeper changes.

#13 Updated by Roger Borrello over 1 year ago

  • Assignee set to Roger Borrello
  • % Done changed from 90 to 100
  • Status changed from WIP to Review

Greg Shah wrote:

Let's avoid the abend. We don't need to make deeper changes.

I put things back and to avoid the abend by falling back to the primary if the computation cannot determine a proper intersection. Upon testing, no matter the configuration (laptop display primary or external display primary, laptop configured to the left of external display or to the right), we always pop the window on the primary display. Not only that, if I move the initial logon screen from the primary to my secondary and then log into hotel. the main hotel window jumps back to the primary screen. Horrible UX, as Hynek noted.

I did a little debug, the main hotel window comes in with x=0/y=0, which computes to the primary display.

So my guess is that setWindowLocation (even though after moving, the left(x)/top(y) are new values), this isn't used as the basis for the next window when it is created.

   public void setWindowLocation(int left, int top, WidgetLocationRef relativeTo)
   {
      SwingUtilities.invokeLater(() -> {
         if (relativeTo == WidgetLocationRef.RELATIVE_TO_CURSOR)
         {
            Point point = SwingMouseHandler.getPointerLocation();
            SwingGuiDriver sd = (SwingGuiDriver) ThinClient.getInstance()
                                                           .getOutputManager()
                                                           .getInstanceDriver();

            Tuple<String, Rectangle> adjustmentInfo = sd.getAdjustmentInfo(point, window.getSize());
            Rectangle screenBounds = adjustmentInfo.getValue();
            //check for edge cases
            switch (adjustmentInfo.getKey())
            {
               case "right":
                  window.setLocation(screenBounds.width + screenBounds.x - window.getWidth(), point.y + 16);
                  break;
               case "bottom":
                  window.setLocation(point.x + 16, screenBounds.height - window.getHeight());
                  break; 
               case "rightbottom":
                  window.setLocation(screenBounds.width + screenBounds.x - window.getWidth(), 
                                     point.y - window.getHeight() - 4);
                  break;
               default:
                  window.setLocation(point.x, point.y + 16);
                  break;
            }   
         }
         else
         {
            window.setLocation(left, top);            
         }
      });
   }

This happens whether or note I use the area technique or the center of the window technique. We just don't honor the new coordinates of the parent window.

I committed the most minimalist update to prevent the NPE in revision 15675. But there is more to do in this area.

#14 Updated by Hynek Cihlar over 1 year ago

Roger Borrello wrote:

Greg Shah wrote:

Let's avoid the abend. We don't need to make deeper changes.

I put things back and to avoid the abend by falling back to the primary if the computation cannot determine a proper intersection. Upon testing, no matter the configuration (laptop display primary or external display primary, laptop configured to the left of external display or to the right), we always pop the window on the primary display. Not only that, if I move the initial logon screen from the primary to my secondary and then log into hotel. the main hotel window jumps back to the primary screen. Horrible UX, as Hynek noted.

This is how the native OE apps behave, too, btw. Top-level windows show up at a default location. This would be typically the primary screen or some 0,0 location. In Win32 apps do their window management themselves.

I committed the most minimalist update to prevent the NPE in revision 15675. But there is more to do in this area.

Please create a branch and check in the changes.

#15 Updated by Roger Borrello over 1 year ago

Hynek Cihlar wrote:

Please create a branch and check in the changes.

9571a is where I put the revision with the current window's center point as the decider, then replaced with the original version, but initializing the configs to the primary display so no NPE.

#16 Updated by Hynek Cihlar over 1 year ago

  • Status changed from Review to WIP
  • % Done changed from 100 to 90

Roger, while your change does prevent the NPE, it doesn't resolve the resolution of the window's display.

I think it makes sense to fix the area calculation. This will address the NPE as well as the correct display resolved for the window.

#17 Updated by Roger Borrello over 1 year ago

  • Assignee deleted (Roger Borrello)

Hynek Cihlar wrote:

Roger, while your change does prevent the NPE, it doesn't resolve the resolution of the window's display.

Did you indicate in #9571-14 this is how OE behaves?

I think it makes sense to fix the area calculation. This will address the NPE as well as the correct display resolved for the window.

I may not be the right resource for that.

#18 Updated by Hynek Cihlar over 1 year ago

Roger Borrello wrote:

Hynek Cihlar wrote:

Roger, while your change does prevent the NPE, it doesn't resolve the resolution of the window's display.

Did you indicate in #9571-14 this is how OE behaves?

Yes, the device with the greatest area should be selected.

I think it makes sense to fix the area calculation. This will address the NPE as well as the correct display resolved for the window.

I may not be the right resource for that.

The change should be simple enough. Keep the code as is and apply the attached diff. It provides correct detection of the window and screen intersection.

#19 Updated by Roger Borrello over 1 year ago

  • Assignee set to Roger Borrello
  • Status changed from WIP to Review
  • % Done changed from 90 to 100

The code works fine. Committed in 15679 (after branch was rebased).

#20 Updated by Roger Borrello over 1 year ago

Rebased branch to trunk revision 15684 and is at revision 15687.

#21 Updated by Roger Borrello over 1 year ago

Rebased branch to trunk revision 15686 and is at revision 15689.

#22 Updated by Hynek Cihlar over 1 year ago

  • Status changed from Review to Internal Test

Code review 9571a.

The changes look good, just please remove the obsolete history entry "When multiple monitors are in use, the usable area is not being calculated...".

Please regression test the changes, Hotel GUI and the customer applications in Swing GUI should be enough.

#23 Updated by Roger Borrello over 1 year ago

Hynek Cihlar wrote:

Code review 9571a.

The changes look good, just please remove the obsolete history entry "When multiple monitors are in use, the usable area is not being calculated...".

Committed in revision 15692 (and rebased)

Please regression test the changes, Hotel GUI and the customer applications in Swing GUI should be enough.

Testing is completed. We are avoiding the abend.

To go a little deeper, in the case where we are positioning the external monitor to the right of the internal monitor (regardless of which is primary), the UX is poor, because the window positioning is off the viewable screen area, and you have to fish for it with the hotkey to bring up the window controls ("Alt-Space" in my case). This was what I mentioned in #9571-13

#24 Updated by Hynek Cihlar over 1 year ago

Roger Borrello wrote:

To go a little deeper, in the case where we are positioning the external monitor to the right of the internal monitor (regardless of which is primary), the UX is poor, because the window positioning is off the viewable screen area, and you have to fish for it with the hotkey to bring up the window controls ("Alt-Space" in my case). This was what I mentioned in #9571-13

The question is whether OE behaves the same. According to some of my tests, it does.

#25 Updated by Roger Borrello over 1 year ago

Hynek Cihlar wrote:

Roger Borrello wrote:

To go a little deeper, in the case where we are positioning the external monitor to the right of the internal monitor (regardless of which is primary), the UX is poor, because the window positioning is off the viewable screen area, and you have to fish for it with the hotkey to bring up the window controls ("Alt-Space" in my case). This was what I mentioned in #9571-13

The question is whether OE behaves the same. According to some of my tests, it does.

That's good to know... the UX is as bad on OE :-)

This has been rebased to the latest trunk.

#26 Updated by Hynek Cihlar over 1 year ago

Code review 9571a, the changes are good.

#27 Updated by Roger Borrello over 1 year ago

Rebased branch to trunk revision 15698 and is pushed up to revision 15702.

#28 Updated by Hynek Cihlar over 1 year ago

  • Status changed from Internal Test to Merge Pending

Please merge to trunk after 9317a.

#29 Updated by Roger Borrello over 1 year ago

  • Status changed from Merge Pending to Test

Branch 9571a was merged to trunk as revision 15709 and archived.

Also available in: Atom PDF