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
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
|
return Win32DPIUtils.pixelToPoint(getBoundsInPixels(), DPIUtil.getDeviceZoom()); |
should be replaced with something like
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:
- Select the platform(s) on which the behavior is seen:
-
Additional OS info (e.g. OS version, Linux Desktop, etc)
-
JRE/JDK version
Version since
Tested against 4.41 I-builds.
Describe the bug
The behavior of
Display.getBounds()is currently non-deterministic, as this internally usesDPIUtil.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:

Run the snippet
Check console:
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 theDisplaylogically comprises all monitors and there is no notion of such a last-used Shell in that context.Maybe
eclipse.platform.swt/bundles/org.eclipse.swt/Eclipse SWT/win32/org/eclipse/swt/widgets/Display.java
Line 1595 in 3db6f5d
should be replaced with something like
to be consistent and compatible with the coordinate system used by
GC.copyArea()?The workaround applied in the snippet uses internal API.
Environment:
Additional OS info (e.g. OS version, Linux Desktop, etc)
JRE/JDK version
Version since
Tested against 4.41 I-builds.