Commit 6abd65c8 authored by Chun-wei Fan's avatar Chun-wei Fan
Browse files

GDK-Win32/4.0: Enable HiDPI support for Windows

This enables HiDPI support for GTK+ on Windows, so that the
fonts and window look better on HiDPI displays.  Notes for the current
work:

-The DPI awareness enabling can be disabled if and only if an application
 manifest is not embedded in the app to enable DPI awareness AND a user
 compatibility setting is not set to limit DPI awareness for the app, via
 the envvar GDK_WIN32_DISABLE_HIDPI.  The app manifest/user setting for
 DPI awareness will always win against the envvar, and so the HiDPI items
 will be always setup in such scenarios, unless DPI awareness is disabled.

-Both automatic detection for the scaling factor and setting the scale
 factor using the GDK_SCALE envvar are supported, where the envvar takes
 precedence, which will therefore disable automatic scaling when
 resolution changes.

-We now default to a per-system DPI awareness model, which means that we
 do not handle WM_DPICHANGED, unless one sets the
 GDK_WIN32_PER_MONITOR_HIDPI envvar, where notes for it are in the
 following point.

-Automatic scaling during WM_DISPLAYCHANGE is handled (DPI setting change of
 current monitor) is now supported.  WM_DPICHANGED is handled as well,
 except that the window positioning during the change of scaling still
 needs to be refined, a change in GDK itself may be required for this.

-I am unable to test the wintab items because I don't have such devices
 around.

https://bugzilla.gnome.org/show_bug.cgi?id=768081
parent 3baa4a97
......@@ -111,25 +111,27 @@ gdk_device_win32_query_state (GdkDevice *device,
GdkScreen *screen;
POINT point;
HWND hwnd, hwndc;
GdkWindowImplWin32 *impl;
screen = gdk_window_get_screen (window);
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
hwnd = GDK_WINDOW_HWND (window);
GetCursorPos (&point);
if (root_x)
*root_x = point.x;
*root_x = point.x / impl->window_scale;
if (root_y)
*root_y = point.y;
*root_y = point.y / impl->window_scale;
ScreenToClient (hwnd, &point);
if (win_x)
*win_x = point.x;
*win_x = point.x / impl->window_scale;
if (win_y)
*win_y = point.y;
*win_y = point.y / impl->window_scale;
if (window == gdk_screen_get_root_window (screen))
{
......@@ -197,6 +199,7 @@ _gdk_device_win32_window_at_position (GdkDevice *device,
gboolean get_toplevel)
{
GdkWindow *window = NULL;
GdkWindowImplWin32 *impl = NULL;
POINT screen_pt, client_pt;
HWND hwnd, hwndc;
RECT rect;
......@@ -249,12 +252,15 @@ _gdk_device_win32_window_at_position (GdkDevice *device,
/* If we didn't hit any window at that point, return the desktop */
if (hwnd == NULL)
{
window = gdk_get_default_root_window ();
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
if (win_x)
*win_x = screen_pt.x + _gdk_offset_x;
*win_x = (screen_pt.x + _gdk_offset_x) / impl->window_scale;
if (win_y)
*win_y = screen_pt.y + _gdk_offset_y;
*win_y = (screen_pt.y + _gdk_offset_y) / impl->window_scale;
return gdk_get_default_root_window ();
return window;
}
window = gdk_win32_handle_table_lookup (hwnd);
......@@ -262,10 +268,12 @@ _gdk_device_win32_window_at_position (GdkDevice *device,
if (window && (win_x || win_y))
{
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
if (win_x)
*win_x = client_pt.x;
*win_x = client_pt.x / impl->window_scale;
if (win_y)
*win_y = client_pt.y;
*win_y = client_pt.y / impl->window_scale;
}
return window;
......
......@@ -121,26 +121,28 @@ gdk_device_wintab_query_state (GdkDevice *device,
GdkScreen *screen;
POINT point;
HWND hwnd, hwndc;
GdkWindowImplWin32 *impl;
device_wintab = GDK_DEVICE_WINTAB (device);
screen = gdk_window_get_screen (window);
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
hwnd = GDK_WINDOW_HWND (window);
GetCursorPos (&point);
if (root_x)
*root_x = point.x;
*root_x = point.x / impl->window_scale;
if (root_y)
*root_y = point.y;
*root_y = point.y / impl->window_scale;
ScreenToClient (hwnd, &point);
if (win_x)
*win_x = point.x;
*win_x = point.x / impl->window_scale;
if (win_y)
*win_y = point.y;
*win_y = point.y / impl->window_scale;
if (window == gdk_get_default_root_window ())
{
......
......@@ -889,6 +889,7 @@ gdk_input_other_event (GdkDisplay *display,
GdkEventMask masktest;
guint key_state;
POINT pt;
GdkWindowImplWin32 *impl;
PACKET packet;
gint root_x, root_y;
......@@ -1034,15 +1035,17 @@ G_GNUC_END_IGNORE_DEPRECATIONS;
if (window->parent == gdk_get_default_root_window () || window->parent == NULL)
return FALSE;
pt.x = x;
pt.y = y;
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
pt.x = x * impl->window_scale;
pt.y = y * impl->window_scale;
ClientToScreen (GDK_WINDOW_HWND (window), &pt);
g_object_unref (window);
window = window->parent;
impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
g_object_ref (window);
ScreenToClient (GDK_WINDOW_HWND (window), &pt);
x = pt.x;
y = pt.y;
x = pt.x / impl->window_scale;
y = pt.y / impl->window_scale;
GDK_NOTE (EVENTS_OR_INPUT, g_print ("... propagating to %p %+d%+d\n",
GDK_WINDOW_HWND (window), x, y));
}
......
......@@ -195,6 +195,41 @@ _gdk_win32_display_init_monitors (GdkWin32Display *win32_display)
changed = TRUE;
}
for (i = 0; i < win32_display->monitors->len; i++)
{
GdkMonitor *monitor;
GdkWin32Monitor *win32_monitor;
monitor = GDK_MONITOR (g_ptr_array_index (win32_display->monitors, i));
if (win32_display->has_fixed_scale)
gdk_monitor_set_scale_factor (monitor, win32_display->window_scale);
else
{
/* First acquire the scale using the current screen */
GdkRectangle workarea;
POINT pt;
guint scale = _gdk_win32_display_get_monitor_scale_factor (win32_display, NULL, NULL, NULL);
gdk_monitor_get_workarea (monitor, &workarea);
workarea.x -= _gdk_offset_x;
workarea.y -= _gdk_offset_y;
workarea.x += workarea.width / scale;
workarea.y += workarea.height / scale;
pt.x = workarea.x;
pt.y = workarea.y;
/* acquire the scale using the monitor which the window is nearest on Windows 8.1+ */
if (win32_display->have_at_least_win81)
{
HMONITOR hmonitor = MonitorFromPoint (pt, MONITOR_DEFAULTTONEAREST);
scale = _gdk_win32_display_get_monitor_scale_factor (win32_display, hmonitor, NULL, NULL);
}
gdk_monitor_set_scale_factor (monitor, scale);
}
}
return changed;
}
......@@ -789,6 +824,15 @@ gdk_win32_display_dispose (GObject *object)
_hwnd_next_viewer = NULL;
}
if (display_win32->have_at_least_win81)
{
if (display_win32->shcore_funcs.hshcore != NULL)
{
FreeLibrary (display_win32->shcore_funcs.hshcore);
display_win32->shcore_funcs.hshcore = NULL;
}
}
G_OBJECT_CLASS (gdk_win32_display_parent_class)->dispose (object);
}
......@@ -805,10 +849,221 @@ gdk_win32_display_finalize (GObject *object)
G_OBJECT_CLASS (gdk_win32_display_parent_class)->finalize (object);
}
static void
_gdk_win32_enable_hidpi (GdkWin32Display *display)
{
gboolean check_for_dpi_awareness = FALSE;
gboolean have_hpi_disable_envvar = FALSE;
enum dpi_aware_status {
DPI_STATUS_PENDING,
DPI_STATUS_SUCCESS,
DPI_STATUS_DISABLED,
DPI_STATUS_FAILED
} status = DPI_STATUS_PENDING;
if (g_win32_check_windows_version (6, 3, 0, G_WIN32_OS_ANY))
{
/* If we are on Windows 8.1 or later, cache up functions from shcore.dll, by all means */
display->have_at_least_win81 = TRUE;
display->shcore_funcs.hshcore = LoadLibraryW (L"shcore.dll");
if (display->shcore_funcs.hshcore != NULL)
{
display->shcore_funcs.setDpiAwareFunc =
(funcSetProcessDpiAwareness) GetProcAddress (display->shcore_funcs.hshcore,
"SetProcessDpiAwareness");
display->shcore_funcs.getDpiAwareFunc =
(funcGetProcessDpiAwareness) GetProcAddress (display->shcore_funcs.hshcore,
"GetProcessDpiAwareness");
display->shcore_funcs.getDpiForMonitorFunc =
(funcGetDpiForMonitor) GetProcAddress (display->shcore_funcs.hshcore,
"GetDpiForMonitor");
}
}
else
{
/* Windows Vista through 8: use functions from user32.dll directly */
HMODULE user32;
display->have_at_least_win81 = FALSE;
user32 = GetModuleHandleW (L"user32.dll");
if (user32 != NULL)
{
display->user32_dpi_funcs.setDpiAwareFunc =
(funcSetProcessDPIAware) GetProcAddress (user32, "SetProcessDPIAware");
display->user32_dpi_funcs.isDpiAwareFunc =
(funcIsProcessDPIAware) GetProcAddress (user32, "IsProcessDPIAware");
}
}
if (g_getenv ("GDK_WIN32_DISABLE_HIDPI") == NULL)
{
/* For Windows 8.1 and later, use SetProcessDPIAwareness() */
if (display->have_at_least_win81)
{
/* then make the GDK-using app DPI-aware */
if (display->shcore_funcs.setDpiAwareFunc != NULL)
{
GdkWin32ProcessDpiAwareness hidpi_mode;
/* TODO: See how per-monitor DPI awareness is done by the Wayland backend */
if (g_getenv ("GDK_WIN32_PER_MONITOR_HIDPI") != NULL)
hidpi_mode = PROCESS_PER_MONITOR_DPI_AWARE;
else
hidpi_mode = PROCESS_SYSTEM_DPI_AWARE;
switch (display->shcore_funcs.setDpiAwareFunc (hidpi_mode))
{
case S_OK:
display->dpi_aware_type = hidpi_mode;
status = DPI_STATUS_SUCCESS;
break;
case E_ACCESSDENIED:
/* This means the app used a manifest to set DPI awareness, or a
DPI compatibility setting is used.
The manifest is the trump card in this game of bridge here. The
same applies if one uses the control panel or program properties to
force system DPI awareness */
check_for_dpi_awareness = TRUE;
break;
default:
display->dpi_aware_type = PROCESS_DPI_UNAWARE;
status = DPI_STATUS_FAILED;
break;
}
}
/* Should not get here! */
if (status == DPI_STATUS_PENDING)
{
g_assert_not_reached ();
display->dpi_aware_type = PROCESS_DPI_UNAWARE;
status = DPI_STATUS_FAILED;
}
}
else
{
/* For Windows Vista through 8, use SetProcessDPIAware() */
display->have_at_least_win81 = FALSE;
if (display->user32_dpi_funcs.setDpiAwareFunc != NULL)
{
if (display->user32_dpi_funcs.setDpiAwareFunc () != 0)
{
display->dpi_aware_type = PROCESS_SYSTEM_DPI_AWARE;
status = DPI_STATUS_SUCCESS;
}
else
{
check_for_dpi_awareness = TRUE;
}
}
else
{
display->dpi_aware_type = PROCESS_DPI_UNAWARE;
status = DPI_STATUS_FAILED;
}
}
}
else
{
/* if GDK_WIN32_DISABLE_HIDPI is set, check for any DPI
* awareness settings done via manifests or user settings
*/
check_for_dpi_awareness = TRUE;
have_hpi_disable_envvar = TRUE;
}
if (check_for_dpi_awareness)
{
if (display->have_at_least_win81)
{
if (display->shcore_funcs.getDpiAwareFunc != NULL)
{
display->shcore_funcs.getDpiAwareFunc (NULL, &display->dpi_aware_type);
if (display->dpi_aware_type != PROCESS_DPI_UNAWARE)
status = DPI_STATUS_SUCCESS;
else
/* This means the DPI awareness setting was forcefully disabled */
status = DPI_STATUS_DISABLED;
}
else
{
display->dpi_aware_type = PROCESS_DPI_UNAWARE;
status = DPI_STATUS_FAILED;
}
}
else
{
if (display->user32_dpi_funcs.isDpiAwareFunc != NULL)
{
/* This most probably means DPI awareness is set through
the manifest, or a DPI compatibility setting is used. */
display->dpi_aware_type = display->user32_dpi_funcs.isDpiAwareFunc () ?
PROCESS_SYSTEM_DPI_AWARE :
PROCESS_DPI_UNAWARE;
if (display->dpi_aware_type == PROCESS_SYSTEM_DPI_AWARE)
status = DPI_STATUS_SUCCESS;
else
status = DPI_STATUS_DISABLED;
}
else
{
display->dpi_aware_type = PROCESS_DPI_UNAWARE;
status = DPI_STATUS_FAILED;
}
}
if (have_hpi_disable_envvar &&
status == DPI_STATUS_SUCCESS)
{
/* The user setting or application manifest trumps over GDK_WIN32_DISABLE_HIDPI */
g_print ("Note: GDK_WIN32_DISABLE_HIDPI is ignored due to preset\n"
" DPI awareness settings in user settings or application\n"
" manifest, DPI awareness is still enabled.");
}
}
switch (status)
{
case DPI_STATUS_SUCCESS:
GDK_NOTE (MISC, g_message ("HiDPI support enabled, type: %s",
display->dpi_aware_type == PROCESS_PER_MONITOR_DPI_AWARE ? "per-monitor" : "system"));
break;
case DPI_STATUS_DISABLED:
GDK_NOTE (MISC, g_message ("HiDPI support disabled via manifest"));
break;
case DPI_STATUS_FAILED:
g_warning ("Failed to enable HiDPI support.");
}
}
static void
gdk_win32_display_init (GdkWin32Display *display)
{
const gchar *scale_str = g_getenv ("GDK_SCALE");
display->monitors = g_ptr_array_new_with_free_func (g_object_unref);
_gdk_win32_enable_hidpi (display);
/* if we have DPI awareness, set up fixed scale if set */
if (display->dpi_aware_type != PROCESS_DPI_UNAWARE &&
scale_str != NULL)
{
display->window_scale = atol (scale_str);
if (display->window_scale == 0)
display->window_scale = 1;
display->has_fixed_scale = TRUE;
}
else
display->window_scale = 1;
_gdk_win32_display_init_cursors (display);
gdk_win32_display_check_composited (display);
}
......@@ -896,6 +1151,89 @@ gdk_win32_display_get_primary_monitor (GdkDisplay *display)
return NULL;
}
guint
_gdk_win32_display_get_monitor_scale_factor (GdkWin32Display *win32_display,
HMONITOR hmonitor,
HWND hwnd,
gint *dpi)
{
gboolean is_scale_acquired = FALSE;
gboolean use_dpi_for_monitor = FALSE;
guint dpix, dpiy;
if (win32_display->have_at_least_win81)
{
if (hmonitor != NULL)
use_dpi_for_monitor = TRUE;
else
{
if (hwnd != NULL)
{
hmonitor = MonitorFromWindow (hwnd, MONITOR_DEFAULTTONEAREST);
use_dpi_for_monitor = TRUE;
}
}
}
if (use_dpi_for_monitor)
{
/* Use GetDpiForMonitor() for Windows 8.1+, when we have a HMONITOR */
if (win32_display->shcore_funcs.hshcore != NULL &&
win32_display->shcore_funcs.getDpiForMonitorFunc != NULL)
{
if (win32_display->shcore_funcs.getDpiForMonitorFunc (hmonitor,
MDT_EFFECTIVE_DPI,
&dpix,
&dpiy) == S_OK)
{
is_scale_acquired = TRUE;
}
}
}
else
{
/* Go back to GetDeviceCaps() for Windows 8 and earler, or when we don't
* have a HMONITOR nor a HWND
*/
HDC hdc = GetDC (hwnd);
/* in case we can't get the DC for the window, return 1 for the scale */
if (hdc == NULL)
{
if (dpi != NULL)
*dpi = USER_DEFAULT_SCREEN_DPI;
return 1;
}
dpix = GetDeviceCaps (hdc, LOGPIXELSX);
dpiy = GetDeviceCaps (hdc, LOGPIXELSY);
ReleaseDC (hwnd, hdc);
is_scale_acquired = TRUE;
}
if (is_scale_acquired)
/* USER_DEFAULT_SCREEN_DPI = 96, in winuser.h */
{
if (dpi != NULL)
*dpi = dpix;
if (win32_display->has_fixed_scale)
return win32_display->window_scale;
else
return dpix / USER_DEFAULT_SCREEN_DPI > 1 ? dpix / USER_DEFAULT_SCREEN_DPI : 1;
}
else
{
if (dpi != NULL)
*dpi = USER_DEFAULT_SCREEN_DPI;
return 1;
}
}
static void
gdk_win32_display_class_init (GdkWin32DisplayClass *klass)
{
......
......@@ -22,6 +22,39 @@
#ifndef __GDK_DISPLAY__WIN32_H__
#define __GDK_DISPLAY__WIN32_H__
/* Define values used to set DPI-awareness */
typedef enum _GdkWin32ProcessDpiAwareness {
PROCESS_DPI_UNAWARE = 0,
PROCESS_SYSTEM_DPI_AWARE = 1,
PROCESS_PER_MONITOR_DPI_AWARE = 2
} GdkWin32ProcessDpiAwareness;
/* APIs from shcore.dll */
typedef HRESULT (WINAPI *funcSetProcessDpiAwareness) (GdkWin32ProcessDpiAwareness value);
typedef HRESULT (WINAPI *funcGetProcessDpiAwareness) (HANDLE handle, GdkWin32ProcessDpiAwareness *awareness);
typedef HRESULT (WINAPI *funcGetDpiForMonitor) (HMONITOR monitor,
GdkWin32MonitorDpiType dpi_type,
UINT *dpi_x,
UINT *dpi_y);
typedef struct _GdkWin32ShcoreFuncs
{
HMODULE hshcore;
funcSetProcessDpiAwareness setDpiAwareFunc;
funcGetProcessDpiAwareness getDpiAwareFunc;
funcGetDpiForMonitor getDpiForMonitorFunc;
} GdkWin32ShcoreFuncs;
/* DPI awareness APIs from user32.dll */
typedef BOOL (WINAPI *funcSetProcessDPIAware) (void);
typedef BOOL (WINAPI *funcIsProcessDPIAware) (void);
typedef struct _GdkWin32User32DPIFuncs
{
funcSetProcessDPIAware setDpiAwareFunc;
funcIsProcessDPIAware isDpiAwareFunc;
} GdkWin32User32DPIFuncs;
struct _GdkWin32Display
{
GdkDisplay display;
......@@ -49,6 +82,15 @@ struct _GdkWin32Display
guint hasWglOMLSyncControl : 1;
guint hasWglARBPixelFormat : 1;
guint hasWglARBmultisample : 1;
/* HiDPI Items */
guint have_at_least_win81 : 1;
GdkWin32ProcessDpiAwareness dpi_aware_type;
guint has_fixed_scale : 1;
guint window_scale;
GdkWin32ShcoreFuncs shcore_funcs;
GdkWin32User32DPIFuncs user32_dpi_funcs;
};
struct _GdkWin32DisplayClass
......@@ -62,4 +104,9 @@ GPtrArray *_gdk_win32_display_get_monitor_list (GdkWin32Display *display);
void gdk_win32_display_check_composited (GdkWin32Display *display);
guint _gdk_win32_display_get_monitor_scale_factor (GdkWin32Display *win32_display,
HMONITOR hmonitor,
HWND hwnd,
gint *dpi);
#endif /* __GDK_DISPLAY__WIN32_H__ */
......@@ -44,9 +44,11 @@
#include "gdkprivate-win32.h"
#include <glib/gprintf.h>
#include <cairo-win32.h>
#include "gdk.h"
#include "gdkdisplayprivate.h"
#include "gdkmonitorprivate.h"
#include "gdkwin32.h"
#include "gdkkeysyms.h"
#include "gdkdevicemanager-win32.h"
......@@ -1059,17 +1061,17 @@ show_window_recurse (GdkWindow *window, gboolean hide_window)
{
if (gdk_window_get_state (window) & GDK_WINDOW_STATE_MAXIMIZED)
{
GtkShowWindow (GDK_WINDOW_HWND (window), SW_SHOWMAXIMIZED);
GtkShowWindow (window, SW_SHOWMAXIMIZED);
}
else
{
GtkShowWindow (GDK_WINDOW_HWND (window), SW_RESTORE);
GtkShowWindow (window, SW_RESTORE);
}
}
}
else
{
GtkShowWindow (GDK_WINDOW_HWND (window), SW_MINIMIZE);
GtkShowWindow (window, SW_MINIMIZE);
}
}
......@@ -1121,6 +1123,7 @@ send_crossing_event (GdkDisplay *display,
GdkDeviceGrabInfo *grab;
GdkDeviceManagerWin32 *device_manager;
POINT pt;
GdkWindowImplWin32 *impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
device_manager = GDK_DEVICE_MANAGER_WIN32 (gdk_display_get_device_manager (display));
......@@ -1142,10 +1145,10 @@ send_crossing_event (GdkDisplay *display,
event->crossing.window = window;
event->crossing.subwindow = subwindow;
event->crossing.time = _gdk_win32_get_next_tick (time_);
event->crossing.x = pt.x;
event->crossing.y = pt.y;
event->crossing.x_root = screen_pt->x + _gdk_offset_x;