Skip to content

Conversation

@jperedadnr
Copy link
Collaborator

@jperedadnr jperedadnr commented Nov 5, 2025

This PR adds the missing native implementation for Windows, GlassWindow::HandleDPIEvent, to notify the (Java) window when there is a DPI change event, which can happen when the user changes the resolution of the screen (via Settings -> System -> Display -> scale), while the JavaFX application is running, in a similar way as Java Desktop handles this DPI event.

When such WM_DPICHANGED event happens, GlassWindow::HandleDPIEvent notifies the (Java) window, which changes its platform scale via Window::notifyScaleChanged, and GlassScreen::HandleDisplayChange(); is needed too, to update the platform scale of the screen where the window is at as well.

The change in Screen:: notifySettingsChanged is needed in order to prevent disposing screens that are not referenced by windows, and only do it when the screen instance of a given window does change.

There are no tests added to this PR, since these would require manual intervention to change the resolution of the display. In any case, the test case added to the issue runs fine now when the app runs on a given screen and the user changes its resolution. It has also been tested that it doesn't have any side effect on macOS.

For the case when the app runs on a different display that the one that was changed, see https://bugs.openjdk.org/browse/JDK-8371302 and a possible fix with #1963.


Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8346281: [Windows] RenderScale doesn't update to HiDPI changes (Bug - P4)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jfx.git pull/1964/head:pull/1964
$ git checkout pull/1964

Update a local copy of the PR:
$ git checkout pull/1964
$ git pull https://git.openjdk.org/jfx.git pull/1964/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 1964

View PR using the GUI difftool:
$ git pr show -t 1964

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jfx/pull/1964.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link

bridgekeeper bot commented Nov 5, 2025

👋 Welcome back jpereda! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link

openjdk bot commented Nov 5, 2025

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk bot added the rfr Ready for review label Nov 5, 2025
@mlbridge
Copy link

mlbridge bot commented Nov 5, 2025

Webrevs

@kevinrushforth
Copy link
Member

I'd like to review and test this, both in isolation and in connection with #1963

/reviewers 2

@openjdk
Copy link

openjdk bot commented Nov 5, 2025

@kevinrushforth
The total number of required reviews for this PR (including the jcheck configuration and the last /reviewers command) is now set to 2 (with at least 1 Reviewer, 1 Author).

Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no ill effects on macOS 15.7.1, will test on windows shortly

void GlassWindow::HandleDPIEvent(WPARAM wParam, LPARAM lParam)
{
JNIEnv* env = GetEnv();
float scale = (float) LOWORD(wParam) / (float) USER_DEFAULT_SCREEN_DPI;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we simply declare USER_DEFAULT_SCREEN_DPI to be a float?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need, I've removed the casting, it wasn't needed.

Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle Nov 6, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if you declare this constant as float, you won't have to cast and it won't be converted each time it's used. saves 0.3 nanoseconds! :-)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The cast will almost certainly be done at compile time, so it is fine as is.

More importantly, I see that the constant is in an ifndef. Presuming that if the constant exists in the header file, it would be defined as an int, it would be wrong to define it as a float in the ifndef case.

Copy link
Contributor

@andy-goryachev-oracle andy-goryachev-oracle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested on windows in combination with #1963 , good job!

(I'll reapprove if/when you push the Set changes)

@jperedadnr
Copy link
Collaborator Author

(Sorry, pushed now)

@andy-goryachev-oracle
Copy link
Contributor

GHA failure in linux is intermittent https://bugs.openjdk.org/browse/JDK-8365551 , please ignore.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code changes look good. My initial testing looks good. I'll do some final testing tomorrow.

public static void notifySettingsChanged() {
// Save the old screens in order to dispose them later
List<Screen> oldScreens = screens;
Set<Screen> oldScreens = new HashSet<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does the order matter? I don't think so, but if it does, LinkedHashSet would preserve the order.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it doesn't matter.

The old screens are "disposed" just by setting their native pointer to 0L, so they can't be reused, while the new screens instances are passed to the windows, to keep an updated instance.

Note that even if old screen and new screen have the very same information (nothing changed for this particular screen), since WinWindow::notifyMoving uses the equality operator (screen 1 == screen 2), we need to keep a valid instance in Window::screen, and therefore Screen::dispose is just a way of invalidating old instances. Then, since they are no longer referenced by any window, they can be gc'ed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had exactly the same question, and looked into the implementation.
Is it likely the implementation would change and the order be important in the future?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so. And I would also argue that it would be weird, if not even bad if the order of disposing a Screen would matter

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. In this case I don't see how it could ever matter.

I only raised the question because whenever I see an ordinary HashSet being iterated, I always ask myself whether the iteration order matters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only raised the question because whenever I see an ordinary HashSet being iterated, I always ask myself whether the iteration order matters.

I agree 100%, especially since bugs in that case are really hard to find and debug.

I remember from my personal experience tests that sometimes failed, because the test did call iterator().next() on a Collection, which was a HashSet.😄

I just saw that I forgot to quote Andy on my previous answer. Just to emphasize this: I don't see how the implementation could be changed in such a way that the order would matter in the future.

Copy link
Member

@kevinrushforth kevinrushforth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All my testing is good. It fixes the bug and as far as I can tell, doesn't introduce new regressions.

My only question is that I'm still not sure why the changes in the shared code in Screen.java are needed.

Comment on lines +400 to 403
// Dispose the old screens, if any
for (Screen screen : oldScreens) {
screen.dispose();
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still a little puzzled as to why a change was needed in the class at all. Even with the change you made to the native Windows glass code, it seems that calling dispose for all of the old Java screen objects is what we want. Am I missing something?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a reason for it, otherwise I wouldn't have modified Screen.

If you run the test in the JBS issue, on Windows with two monitors (primary with scale > 100%), with the changes from GlassWindow.cpp only, without changing Screen, when you open the File menu of the window in the secondary display, the context menu is misplaced:

Screenshot 2025-11-08 143811

Adding some debugging info, it can be seen that, when the application starts (without doing any external change in Windows settings), there is a DPI event, that calls notifySettingsChanged, at a point where there is only a Window instance (the one in the primary screen), instead of two. The two screens get disposed (that is, Screen.ptr = 0), and later on, when the secondary window is created, it gets assigned the disposed secondary screen, with this ptr = 0.

When opening the File menu of the secondary window, another DPI event is triggered, notifySettingsChanged is called, but since there was no real change in the screens, the new screens are the same as the old ones (the valid ptr didn't change after the event). However, the valid secondary screen is not assigned to the secondary Window, because the if test, (ptr from old screen was 0, and new screen ptr >0 doesn't match), so it keeps the old screen instance with ptr = 0.

This causes some unexpected issues for instance in WinWindow::notifyMoving, as there is a mixture of valid screens (from Screen.getScreens()) and invalid one in Window::getScreen, and the equality checks (screen1 == screen2) fails, ultimately providing wrong screen information for the popup.

So the change in Screen that I propose in this PR removes the disposal of screens if these are not assigned to a window yet.

Does that make sense?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm. I see what you are saying, and can confirm the behavior. With your proposed fix, though, it seems like we will still have a situation where an in-use screen object is no longer in the list of screens. When this happens it will point to the same native screen object as one of the newly-created screens. This seems fragile.

Maybe we could still dispose all old screens, but do it in a way that allows it to later be mapped to a new screen? Or, thinking out loud, maybe "put back" the screens that didn't actually change as the result of the DPI change notification?

I'll want to think about this and take a closer look, but it will be a couple days before I can get to it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

rfr Ready for review

Development

Successfully merging this pull request may close these issues.

4 participants