[Win32] [Hi-DPI] Non-deterministic Display.getBounds() / no way to create a consistent screenshot without internal API
**Describe the bug**
The behavior of `Display.getBounds()` is currently non-deterministic, as this internally uses `DPIUtil.getDeviceZoom()`, which means _the autoscaling zoom for the shell whose zoom has last been changed (via moving the shell to another monitor or changing the zoom of a monitor)_.
Due to this it's pretty much impossible to deterministically create a screenshot of the whole `Display` (all monitors).
**To Reproduce**
- Set up a multi-monitor setup as follows:
<img width="701" height="205" alt="Image" src="https://github.com/user-attachments/assets/565a59f3-c3d4-4eaa-af3b-4d048e07d680" />
- Run the snippet
```java
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Objects;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionListener;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.internal.DPIUtil;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Monitor;
import org.eclipse.swt.widgets.Shell;
public final class ScreenshotProblemDisplayBounds {
public static void main(String[] args) throws IOException {
Display display = Display.getDefault();
Shell shell = new Shell();
shell.setSize(300, 200);
shell.setLayout(new GridLayout(1, false));
Button buttonPrintBounds = new Button(shell, SWT.NONE);
buttonPrintBounds.setText("Print Bounds");
buttonPrintBounds.addSelectionListener(SelectionListener.widgetSelectedAdapter(event -> {
System.out.println("display bounds: " + display.getBounds());
System.out.println("shell bounds: " + shell.getBounds());
Monitor primaryMonitor = display.getPrimaryMonitor();
for (int i = 0; i < display.getMonitors().length; i++) {
Monitor monitor = display.getMonitors()[i];
System.out.println("monitor" + i + " bounds: " + monitor.getBounds() + " zoom: " + monitor.getZoom()
+ (Objects.equals(monitor, primaryMonitor) ? " [primary]" : ""));
}
}));
Button buttonScreenshot1 = new Button(shell, SWT.NONE);
buttonScreenshot1.setText("Screenshot (without workaround)");
Button buttonScreenshot2 = new Button(shell, SWT.NONE);
buttonScreenshot2.setText("Screenshot (with workaround)");
SelectionListener widgetSelectedAdapter = SelectionListener.widgetSelectedAdapter(event -> {
Path tempFile;
try {
tempFile = Files.createTempFile("screenshot", ".png");
} catch (IOException e) {
throw new IllegalStateException(e);
}
Rectangle rectangle = display.getBounds();
int factor = 1;
if ("win32".equals(SWT.getPlatform()) && event.getSource() == buttonScreenshot2) {
// display.getBounds(); on windows currently is non-deterministic, but uses DPIUtil.getDeviceZoom(); internally
// which itself depends refers to the "zoom of the last Shell that changed zoom"
// but gc.copyArea() lateron works with the zoom of the primary monitor.
// we have to workaround that
factor = DPIUtil.getDeviceZoom() / display.getPrimaryMonitor().getZoom();
}
Image image = new Image(display, rectangle.width * factor, rectangle.height * factor);
GC gc = new GC(display);
gc.copyArea(image, rectangle.x * factor, rectangle.y * factor);
ImageData imageData = image.getImageData();
image.dispose();
gc.dispose();
ImageLoader imageLoader = new ImageLoader();
imageLoader.data = new ImageData[] { imageData };
imageLoader.save(tempFile.toAbsolutePath().toString(), SWT.IMAGE_PNG);
System.out.println(tempFile + " (" + imageData.width + "x" + imageData.height + ")");
});
buttonScreenshot1.addSelectionListener(widgetSelectedAdapter);
buttonScreenshot2.addSelectionListener(widgetSelectedAdapter);
shell.open();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
display.dispose();
}
}
```
- Move the opened shell to the primary (100%) monitor.
- Press _Print Bounds_ / _Screenshot (without workaround)_ / _Screenshot (with workaround)_ buttons once each
- Move the opened shell to the secondary (200%) monitor.
- Press _Print Bounds_ / _Screenshot (without workaround)_ / _Screenshot (with workaround)_ buttons once each again
Check console:
```
display bounds: Rectangle {0, 0, 5360, 1440}
shell bounds: Rectangle {1559, 498, 300, 200}
monitor0 bounds: Rectangle {3440, 236, 960, 600} zoom: 200
monitor1 bounds: Rectangle {0, 0, 3440, 1440} zoom: 100 [primary]
C:\Users\user\AppData\Local\Temp\screenshot925734453198280273.png (5360x1440)
C:\Users\user\AppData\Local\Temp\screenshot287834727171878990.png (5360x1440)
display bounds: Rectangle {0, 0, 2680, 720}
shell bounds: Rectangle {3822, 403, 300, 200}
monitor0 bounds: Rectangle {3440, 236, 960, 600} zoom: 200
monitor1 bounds: Rectangle {0, 0, 3440, 1440} zoom: 100 [primary]
C:\Users\user\AppData\Local\Temp\screenshot64546334595754013261.png (2680x720)
C:\Users\user\AppData\Local\Temp\screenshot14362251863936518615.png (5360x1440)
```
When using the _Screenshot (without workaround)_ button, while the little helper program's shell is currently on the 2nd monitor, the screenshot created is only half the expected size and therefore cut-off.
**Expected behavior**
Screenshots should always be created with size 5360x1440, covering the whole virtual screen area.
`Display.getBounds()` should return consistent values - no matter which Shell was last moved where, as the `Display` logically comprises **all** monitors and there is no notion of such a last-used Shell in that context.
Maybe
https://github.com/eclipse-platform/eclipse.platform.swt/blob/3db6f5d5ecf81d5b217f3af56ddeb09d0ae64b25/bundles/org.eclipse.swt/Eclipse%20SWT/win32/org/eclipse/swt/widgets/Display.java#L1595
should be replaced with something like
```java
return Win32DPIUtils.pixelToPoint(getBoundsInPixels(), getPrimaryMonitor().getZoom());
```
to be consistent and compatible with the coordinate system used by `GC.copyArea()`?
The workaround applied in the snippet uses internal API.
**Environment:**
1. Select the platform(s) on which the behavior is seen:
- - [ ] All OS
- - [x] Windows
- - [ ] Linux
- - [ ] macOS
2. Additional OS info (e.g. OS version, Linux Desktop, etc)
3. JRE/JDK version
**Version since**
Tested against 4.41 I-builds.
2 条评论