Make FLTK Windows apps "Per-Monitor-V2 DPI Aware"

Per-Monitor V2 awareness mode is supported on Windows 10 1703 or above
and has window title bars correctly scaled on HighDPI screens.
Before this commit, FLTK Windows apps were "Per-Monitor-V1 DPI Aware".

FLTK apps detect at run-time whether the V2 mode is possible.
This commit is contained in:
ManoloFLTK 2019-06-16 12:00:38 +02:00
parent aa9f0a6962
commit 62bce5b50a
3 changed files with 68 additions and 53 deletions

View File

@ -88,8 +88,7 @@ void fl_cleanup_dc_list(void);
# include <wchar.h>
#endif
typedef HRESULT(WINAPI * SetProcessDpiAwareness_type)(int);
static SetProcessDpiAwareness_type fl_SetProcessDpiAwareness = NULL;
static bool is_dpi_aware = false;
extern bool fl_clipboard_notify_empty(void);
extern void fl_trigger_clipboard_notify(int source);
@ -542,13 +541,21 @@ void Fl_WinAPI_Screen_Driver::open_display_platform() {
return;
beenHereDoneThat = 1;
HMODULE hMod = LoadLibrary("Shcore.DLL");
if (hMod) {
fl_SetProcessDpiAwareness = (SetProcessDpiAwareness_type)GetProcAddress(hMod, "SetProcessDpiAwareness");
const int PROCESS_PER_MONITOR_DPI_AWARE = 2;
typedef void *fl_DPI_AWARENESS_CONTEXT;
typedef BOOL(WINAPI * SetProcessDpiAwarenessContext_type)(fl_DPI_AWARENESS_CONTEXT);
SetProcessDpiAwarenessContext_type fl_SetProcessDpiAwarenessContext =
(SetProcessDpiAwarenessContext_type)GetProcAddress(LoadLibrary("User32.DLL"), "SetProcessDpiAwarenessContext");
if (fl_SetProcessDpiAwarenessContext) {
const fl_DPI_AWARENESS_CONTEXT fl_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 = (fl_DPI_AWARENESS_CONTEXT)(-4);
is_dpi_aware = fl_SetProcessDpiAwarenessContext(fl_DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);
}
if (!is_dpi_aware) {
typedef HRESULT(WINAPI * SetProcessDpiAwareness_type)(int);
SetProcessDpiAwareness_type fl_SetProcessDpiAwareness =
(SetProcessDpiAwareness_type)GetProcAddress(LoadLibrary("Shcore.DLL"), "SetProcessDpiAwareness");
if (fl_SetProcessDpiAwareness) {
HRESULT hr = fl_SetProcessDpiAwareness(PROCESS_PER_MONITOR_DPI_AWARE);
if (hr != S_OK) fl_SetProcessDpiAwareness = NULL;
const int fl_PROCESS_PER_MONITOR_DPI_AWARE = 2;
if (fl_SetProcessDpiAwareness(fl_PROCESS_PER_MONITOR_DPI_AWARE) == S_OK) is_dpi_aware = true;
}
}
OleInitialize(0L);
@ -559,19 +566,18 @@ void Fl_WinAPI_Screen_Driver::open_display_platform() {
void Fl_WinAPI_Screen_Driver::desktop_scale_factor() {
typedef HRESULT(WINAPI * GetDpiForMonitor_type)(HMONITOR, int, UINT *, UINT *);
HMODULE hMod = LoadLibrary("Shcore.DLL");
GetDpiForMonitor_type fl_GetDpiForMonitor = NULL;
if (hMod && fl_SetProcessDpiAwareness)
fl_GetDpiForMonitor = (GetDpiForMonitor_type)GetProcAddress(hMod, "GetDpiForMonitor");
if (fl_GetDpiForMonitor) {
if (is_dpi_aware)
fl_GetDpiForMonitor = (GetDpiForMonitor_type)GetProcAddress(LoadLibrary("Shcore.DLL"), "GetDpiForMonitor");
for (int ns = 0; ns < screen_count(); ns++) {
HMONITOR hm = MonitorFromRect(&screens[ns], MONITOR_DEFAULTTONEAREST);
UINT dpiX, dpiY;
HRESULT r = fl_GetDpiForMonitor(hm, 0, &dpiX, &dpiY);
float f = (r == S_OK ? dpiX / 96. : 1);
scale(ns, f);
//fprintf(LOG, "desktop_scale_factor ns=%d factor=%.2f\n", ns, scale(ns));fflush(LOG);
}
HRESULT r = fl_GetDpiForMonitor ? fl_GetDpiForMonitor(hm, 0, &dpiX, &dpiY) : !S_OK;
if (r != S_OK) { dpiX = dpiY = 96; }
dpi[ns][0] = dpiX;
dpi[ns][1] = dpiY;
scale(ns, dpiX / 96.);
//fprintf(LOG, "desktop_scale_factor ns=%d factor=%.2f dpi=%.1f\n", ns, scale(ns), dpi[ns][0]);
}
}
@ -1196,12 +1202,14 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
switch (uMsg) {
case WM_DPICHANGED: { // 0x02E0
if (fl_SetProcessDpiAwareness && !Fl_WinAPI_Window_Driver::data_for_resize_window_between_screens_.busy) {
if (is_dpi_aware && !Fl_WinAPI_Window_Driver::data_for_resize_window_between_screens_.busy) {
RECT r;
Fl_WinAPI_Screen_Driver *sd = (Fl_WinAPI_Screen_Driver*)Fl::screen_driver();
int ns = Fl_Window_Driver::driver(window)->screen_num();
sd->dpi[ns][0] = sd->dpi[ns][1] = HIWORD(wParam);
float f = HIWORD(wParam) / 96.;
GetClientRect(hWnd, &r);
float old_f = float(r.right) / window->w();
int ns = Fl_Window_Driver::driver(window)->screen_num();
Fl::screen_driver()->scale(ns, f);
Fl_Window_Driver::driver(window)->resize_after_scale_change(ns, old_f, f);
}
@ -1657,6 +1665,24 @@ static LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPar
return DefWindowProcW(hWnd, uMsg, wParam, lParam);
}
/* Implementation note about the API to get the dimensions of the top/left borders and the title bar
Function fake_X_wm_style() below is used before calling CreateWindowExW() to create
a window and before calling SetWindowPos(). Both of these Windows functions need the window size
including borders and title bar. Function fake_X_wm_style() uses AdjustWindowRectExForDpi() or
AdjustWindowRectEx() to get the sizes of borders and title bar. The gotten values don't always match
what is seen on the display, but they are the **required** values so the subsequent calls to
CreateWindowExW() or SetWindowPos() correctly size the window.
The Windows doc of AdjustWindowRectExForDpi/AdjustWindowRectEx makes this very clear:
Calculates the required size of the window rectangle, based on the desired size of the client
rectangle [and the provided DPI]. This window rectangle can then be passed to the CreateWindowEx
function to create a window with a client area of the desired size.
Conversely, Fl_WinAPI_Window_Driver::border_width_title_bar_height() is used to get
the true sizes of borders and title bar of a mapped window. The correct API for that is
DwmGetWindowAttribute().
*/
////////////////////////////////////////////////////////////////
// This function gets the dimensions of the top/left borders and
// the title bar, if there is one, based on the FL_BORDER, FL_MODAL
@ -1676,18 +1702,7 @@ static int fake_X_wm_style(const Fl_Window *w, int &X, int &Y, int &bt, int &bx,
int fallback = 1;
float s = Fl::screen_driver()->scale(Fl_Window_Driver::driver(w)->screen_num());
if (!w->parent()) {
if (fl_xid(w)) {
Fl_WinAPI_Window_Driver *dr = Fl_WinAPI_Window_Driver::driver(w);
dr->border_width_title_bar_height(bx, by, bt);
xoff = bx;
yoff = by + bt;
dx = 2 * bx;
dy = 2 * by + bt;
X = w->x() * s - bx;
Y = w->y() * s - bt - by;
W = w->w() * s + dx;
H = w->h() * s + dy;
} else if (fl_xid(w) || style) {
if (fl_xid(w) || style) {
// The block below calculates the window borders by requesting the
// required decorated window rectangle for a desired client rectangle.
// If any part of the function above fails, we will drop to a
@ -1706,7 +1721,17 @@ static int fake_X_wm_style(const Fl_Window *w, int &X, int &Y, int &bt, int &bx,
r.right = (w->x() + w->w()) * s;
r.bottom = (w->y() + w->h()) * s;
// get the decoration rectangle for the desired client rectangle
BOOL ok = AdjustWindowRectEx(&r, style, FALSE, styleEx);
typedef BOOL(WINAPI* AdjustWindowRectExForDpi_type)(LPRECT, DWORD, BOOL, DWORD, UINT);
static AdjustWindowRectExForDpi_type fl_AdjustWindowRectExForDpi =
(AdjustWindowRectExForDpi_type)GetProcAddress(LoadLibrary("User32.DLL"), "AdjustWindowRectExForDpi");
BOOL ok;
if ( fl_AdjustWindowRectExForDpi) {
Fl_WinAPI_Screen_Driver *sd = (Fl_WinAPI_Screen_Driver*)Fl::screen_driver();
UINT dpi = sd->dpi[Fl_Window_Driver::driver(w)->screen_num()][0];
ok = fl_AdjustWindowRectExForDpi(&r, style, FALSE, styleEx, dpi);
} else
ok = AdjustWindowRectEx(&r, style, FALSE, styleEx);
if (ok) {
X = r.left;
Y = r.top;
@ -1803,6 +1828,7 @@ int Fl_WinAPI_Window_Driver::fake_X_wm(int &X, int &Y, int &bt, int &bx, int &by
////////////////////////////////////////////////////////////////
void Fl_WinAPI_Window_Driver::resize(int X, int Y, int W, int H) {
//fprintf(stderr, "resize w()=%d W=%d h()=%d H=%d\n",pWindow->w(), W,pWindow->h(), H);
UINT flags = SWP_NOSENDCHANGING | SWP_NOZORDER | SWP_NOACTIVATE | SWP_NOOWNERZORDER;
int is_a_resize = (W != w() || H != h() || is_a_rescale());
int resize_from_program = (pWindow != resize_bug_fix);
@ -1838,7 +1864,7 @@ void Fl_WinAPI_Window_Driver::resize(int X, int Y, int W, int H) {
int dummy_x, dummy_y, bt, bx, by;
// compute window position and size in scaled units
float s = Fl::screen_driver()->scale(screen_num());
int scaledX = ceil(X * s), scaledY = ceil(Y * s), scaledW = ceil(W * s), scaledH = ceil(H * s);
int scaledX = int(X * s), scaledY = int(Y * s), scaledW = int(W * s), scaledH = int(H * s);
// Ignore window managing when resizing, so that windows (and more
// specifically menus) can be moved offscreen.
if (fake_X_wm(dummy_x, dummy_y, bt, bx, by)) {

View File

@ -36,7 +36,6 @@ class FL_EXPORT Fl_WinAPI_Screen_Driver : public Fl_Screen_Driver
protected:
RECT screens[MAX_SCREENS];
RECT work_area[MAX_SCREENS];
float dpi[MAX_SCREENS][2];
float scale_of_screen[MAX_SCREENS];
static BOOL CALLBACK screen_cb(HMONITOR mon, HDC, LPRECT r, LPARAM);
@ -44,6 +43,7 @@ protected:
int get_mouse_unscaled(int &mx, int &my);
public:
float dpi[MAX_SCREENS][2];
Fl_WinAPI_Screen_Driver() : Fl_Screen_Driver() {
for (int i = 0; i < MAX_SCREENS; i++) scale_of_screen[i] = 1;
}

View File

@ -100,21 +100,6 @@ BOOL Fl_WinAPI_Screen_Driver::screen_cb(HMONITOR mon, HDC, LPRECT r)
screens[num_screens] = mi.rcMonitor;
// If we also want to record the work area, we would also store mi.rcWork at this point
work_area[num_screens] = mi.rcWork;
//extern FILE*LOG;fprintf(LOG,"screen_cb ns=%d\n",num_screens);fflush(LOG);
/*fl_alert("screen %d %d,%d,%d,%d work %d,%d,%d,%d",num_screens,
screens[num_screens].left,screens[num_screens].right,screens[num_screens].top,screens[num_screens].bottom,
work_area[num_screens].left,work_area[num_screens].right,work_area[num_screens].top,work_area[num_screens].bottom);
*/
// find the pixel size
if (mi.cbSize == sizeof(mi)) {
HDC screen = CreateDC(mi.szDevice, NULL, NULL, NULL);
if (screen) {
dpi[num_screens][0] = (float)GetDeviceCaps(screen, LOGPIXELSX);
dpi[num_screens][1] = (float)GetDeviceCaps(screen, LOGPIXELSY);
}
DeleteDC(screen);
}
num_screens++;
}
return TRUE;
@ -128,6 +113,7 @@ void Fl_WinAPI_Screen_Driver::init()
// we do a run-time check for the required functions...
HMODULE hMod = GetModuleHandle("USER32.DLL");
int old_num_screens = num_screens;
if (hMod) {
// check that EnumDisplayMonitors is available
fl_edm_func fl_edm = (fl_edm_func)GetProcAddress(hMod, "EnumDisplayMonitors");
@ -142,7 +128,7 @@ void Fl_WinAPI_Screen_Driver::init()
// NOTE: num_screens is incremented in screen_cb so we must first reset it here...
num_screens = 0;
fl_edm(0, 0, screen_cb, (LPARAM)this);
return;
goto way_out;
}
}
}
@ -155,6 +141,9 @@ void Fl_WinAPI_Screen_Driver::init()
screens[0].bottom = GetSystemMetrics(SM_CYSCREEN);
work_area[0] = screens[0];
scale_of_screen[0] = 1;
way_out:
// prevent desktop_scale_factor() from being called twice at app startup
if (old_num_screens >= 0) desktop_scale_factor();
}