Looking at the problem, it seems to be a defect in ms-windows, and tk
not using the known workaround for it.
Looking at the source code for tk
, in the file win/tkWinX.c
the function TkWinDisplayChanged
uses the windows API call GetDeviceCaps
to get the screen width and height with the parameters HORZRES
and VERTRES
.
Unfortunately, this page states (emphasis mine):
Note GetDeviceCaps
reports info that the display driver provides. If the display driver declines to report any info, GetDeviceCaps
calculates the info based on fixed calculations. If the display driver reports invalid info, GetDeviceCaps returns the invalid info. Also, if the display driver declines to report info, GetDeviceCaps might calculate incorrect info because it assumes either fixed DPI (96 DPI) or a fixed size (depending on the info that the display driver did and didn’t provide). Unfortunately, a display driver that is implemented to the Windows Display Driver Model (WDDM) (introduced in Windows Vista) causes GDI to not get the info, so GetDeviceCaps must always calculate the info.
I would rephrase that as follows; "after windows Vista, display information from GetDeviceCaps
is not to be trusted".
There there is this page about high-dpi displays.
This page uses some other GetDeviceCaps
values, LOGPIXELSX
and LOGPIXELSY
(the numbers of pixels per "logical inch" in x and y) to calculate the real window size.
The "default" DPI that GetDeviceCaps
seems to use is 96.
Look at this fragment of tk
code from win/tkWinX.c
, you can see that tk
uses LOGPIXELSX
and LOGPIXELSY
to calculate the screen size in mm.
void
TkWinDisplayChanged(
Display *display)
{
HDC dc;
Screen *screen;
if (display == NULL || display->screens == NULL) {
return;
}
screen = display->screens;
dc = GetDC(NULL);
screen->width = GetDeviceCaps(dc, HORZRES);
screen->height = GetDeviceCaps(dc, VERTRES);
screen->mwidth = MulDiv(screen->width, 254,
GetDeviceCaps(dc, LOGPIXELSX) * 10);
screen->mheight = MulDiv(screen->height, 254,
GetDeviceCaps(dc, LOGPIXELSY) * 10);
This value seems to be used to calculate winfo_fpixels()
So I think that if you use root.winfo_fpixels('1i')
you will get a reliable DPI value.
So, try this:
import tkinter as tk
root = tk.Tk()
width = root.winfo_screenwidth()
height = root.winfo_screenheight()
dpi = root.winfo_fpixels('1i')
real_width = int(width * dpi / 96)
real_height = int(height * dpi / 96)
print('real width should be', real_width)
print('real height should be', real_height)
Edit A workaround is to set tkinter
's scaling factor:
factor = dpi / 72
root.tk.call('tk', 'scaling', factor)