/*
 * Copyright © 2016 Red Hat, Inc
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

#if defined (_WIN32_WINNT) && WIN32_WINNT < 0x0601
#  undef _WIN32_WINNT

#  define _WIN32_WINNT 0x0601
#  ifdef WINVER
#    undef WINVER
#  endif
#  define WINVER _WIN32_WINNT
#elif !defined (_WIN32_WINNT)
#  define _WIN32_WINNT 0x0601
#  ifdef WINVER
#    undef WINVER
#  endif
#  define WINVER _WIN32_WINNT
#endif

#include "config.h"

#include "gdkprivate-win32.h"
#include "gdkdisplay-win32.h"
#include "gdkmonitor-win32.h"

#include <glib.h>
#include <gio/gio.h>

#include <cfgmgr32.h>
#include <devpropdef.h>
#include <setupapi.h>

#include "gdkprivate-win32.h"

G_DEFINE_TYPE (GdkWin32Monitor, gdk_win32_monitor, GDK_TYPE_MONITOR)

/* MinGW-w64 carelessly put DISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER = -1 into this
 * enum, as documented by MSDN. However, with
 * DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL = 0x80000000 and
 * DISPLAYCONFIG_OUTPUT_TECHNOLOGY_FORCE_UINT32 = 0xFFFFFFFF
 * this had the effect of increasing enum size from 4 to 8 bytes,
 * when compiled by GCC (MSVC doesn't have this problem), breaking ABI.
 * At the moment of writing MinGW-w64 headers are still broken.
 * When they are fixed, replace 9999 with actual version numbers.
 * The fix below is not necessarily correct, but it works.
 */
#if SIZEOF_DISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY == 4
#  define fixedDISPLAYCONFIG_PATH_INFO DISPLAYCONFIG_PATH_INFO
#  define fixedDISPLAYCONFIG_TARGET_DEVICE_NAME DISPLAYCONFIG_TARGET_DEVICE_NAME
#else
typedef enum {
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_OTHER                 = (int) -1,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_HD15                  = (int) 0,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_SVIDEO                = (int) 1,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPOSITE_VIDEO       = (int) 2,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_COMPONENT_VIDEO       = (int) 3,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_DVI                   = (int) 4,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI                  = (int) 5,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_LVDS                  = (int) 6,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_D_JPN                 = (int) 8,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDI                   = (int) 9,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EXTERNAL  = (int) 10,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_DISPLAYPORT_EMBEDDED  = (int) 11,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EXTERNAL          = (int) 12,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_UDI_EMBEDDED          = (int) 13,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_SDTVDONGLE            = (int) 14,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL              = (int) 0x80000000,
  fixedDISPLAYCONFIG_OUTPUT_TECHNOLOGY_FORCE_UINT32          = (int) 0xFFFFFFFF
} fixedDISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY;

typedef struct fixedDISPLAYCONFIG_PATH_TARGET_INFO {
  LUID                                       adapterId;
  UINT32                                     id;
  UINT32                                     modeInfoIdx;
  fixedDISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY outputTechnology;
  DISPLAYCONFIG_ROTATION                     rotation;
  DISPLAYCONFIG_SCALING                      scaling;
  DISPLAYCONFIG_RATIONAL                     refreshRate;
  DISPLAYCONFIG_SCANLINE_ORDERING            scanLineOrdering;
  BOOL                                       targetAvailable;
  UINT32                                     statusFlags;
} fixedDISPLAYCONFIG_PATH_TARGET_INFO;

typedef struct fixedDISPLAYCONFIG_PATH_INFO
{
  DISPLAYCONFIG_PATH_SOURCE_INFO      sourceInfo;
  fixedDISPLAYCONFIG_PATH_TARGET_INFO targetInfo;
  UINT32                              flags;
} fixedDISPLAYCONFIG_PATH_INFO;

typedef struct fixedDISPLAYCONFIG_TARGET_DEVICE_NAME
{
  DISPLAYCONFIG_DEVICE_INFO_HEADER            header;
  DISPLAYCONFIG_TARGET_DEVICE_NAME_FLAGS      flags;
  fixedDISPLAYCONFIG_VIDEO_OUTPUT_TECHNOLOGY  outputTechnology;
  UINT16                                      edidManufactureId;
  UINT16                                      edidProductCodeId;
  UINT32                                      connectorInstance;
  WCHAR                                       monitorFriendlyDeviceName[64];
  WCHAR                                       monitorDevicePath[128];
} fixedDISPLAYCONFIG_TARGET_DEVICE_NAME;

#endif


/* MinGW-w64 does not have these functions in its import libraries
 * at the moment of writing.
 * Also, Windows Vista doesn't have these functions at all
 * (according to MSDN it does, but that is a lie), so we'd have
 * to load them manually anyway (otherwise GTK apps won't even start
 * on Vista).
 */
typedef LONG
(WINAPI *funcGetDisplayConfigBufferSizes) (UINT32 flags,
                                           UINT32* numPathArrayElements,
                                           UINT32* numModeInfoArrayElements);

typedef LONG
(WINAPI *funcQueryDisplayConfig) (UINT32 flags,
                                  UINT32* numPathArrayElements,
                                  fixedDISPLAYCONFIG_PATH_INFO* pathArray,
                                  UINT32* numModeInfoArrayElements,
                                  DISPLAYCONFIG_MODE_INFO* modeInfoArray,
                                  DISPLAYCONFIG_TOPOLOGY_ID* currentTopologyId);

typedef LONG
(WINAPI *funcDisplayConfigGetDeviceInfo) (DISPLAYCONFIG_DEVICE_INFO_HEADER* requestPacket);

#ifndef MONITORINFOF_PRIMARY
#define MONITORINFOF_PRIMARY 1
#endif

/* MinGW-w64 does not have a prototype for function in its headers
 * at the moment of writing.
 */
#if !defined (HAVE_SETUP_DI_GET_DEVICE_PROPERTY_W)
BOOL WINAPI SetupDiGetDevicePropertyW (HDEVINFO          DeviceInfoSet,
                                       PSP_DEVINFO_DATA  DeviceInfoData,
                                       const DEVPROPKEY *PropertyKey,
                                       DEVPROPTYPE      *PropertyType,
                                       PBYTE             PropertyBuffer,
                                       DWORD             PropertyBufferSize,
                                       PDWORD            RequiredSize,
                                       DWORD             Flags);
#endif

#define G_GUID_FORMAT "%08lX-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X"
#define g_format_guid(guid) (guid)->Data1, \
                            (guid)->Data2, \
                            (guid)->Data3, \
                            (guid)->Data4[0], \
                            (guid)->Data4[1], \
                            (guid)->Data4[2], \
                            (guid)->Data4[3], \
                            (guid)->Data4[4], \
                            (guid)->Data4[5], \
                            (guid)->Data4[6], \
                            (guid)->Data4[7]

static gboolean
get_device_property (HDEVINFO          device_infoset,
                     SP_DEVINFO_DATA  *device_info_data,
                     DEVPROPKEY       *property_key,
                     gpointer         *r_buffer,
                     gsize            *r_buffer_size,
                     DEVPROPTYPE      *r_property_type,
                     GError          **error)
{
  DEVPROPTYPE property_type;
  gpointer property;
  DWORD property_size;

  property = NULL;
  property_size = 0;

  if (!SetupDiGetDevicePropertyW (device_infoset,
                                  device_info_data,
                                  property_key,
                                  &property_type,
                                  property,
                                  property_size,
                                  &property_size,
                                  0))
    {
      DWORD error_code = GetLastError ();

      if (error_code != ERROR_INSUFFICIENT_BUFFER)
        {
          gchar *emsg = g_win32_error_message (error_code);
          g_warning ("Failed to get device node property {" G_GUID_FORMAT "},%lu size: %s",
                     g_format_guid (&property_key->fmtid),
                     property_key->pid,
                     emsg);
          g_free (emsg);

          return FALSE;
        }
    }

  if (r_buffer)
    {
      property = g_malloc (property_size);

      if (!SetupDiGetDevicePropertyW (device_infoset,
                                      device_info_data,
                                      property_key,
                                      &property_type,
                                      property,
                                      property_size,
                                      &property_size,
                                      0))
        {
          DWORD error_code = GetLastError ();

          gchar *emsg = g_win32_error_message (error_code);
          g_warning ("Failed to get device node property {" G_GUID_FORMAT "},%lu: %s",
                     g_format_guid (&property_key->fmtid),
                     property_key->pid,
                     emsg);
          g_free (emsg);

          return FALSE;
        }

      *r_buffer = property;
    }

  if (r_buffer_size)
    *r_buffer_size = property_size;

  if (r_property_type)
    *r_property_type = property_type;

  return TRUE;
}

static GPtrArray *
get_monitor_devices (GdkWin32Display *win32_display)
{
  GPtrArray *monitor_array;
  HDEVINFO device_infoset;
  SP_DEVINFO_DATA device_info_data;
  DWORD device_index;
  GUID device_interface_monitor = {0xe6f07b5f, 0xee97, 0x4a90, {0xb0, 0x76, 0x33, 0xf5, 0x7b, 0xf4, 0xea, 0xa7}};
  DEVPROPKEY pkey_device_instance_id = {{0x78C34FC8, 0x104A, 0x4ACA, {0x9E, 0xA4, 0x52, 0x4D, 0x52, 0x99, 0x6E, 0x57}}, 256};
  DEVPROPKEY pkey_manufacturer = {{0xA45C254E, 0xDF1C, 0x4EFD, {0x80, 0x20, 0x67, 0xD1, 0x46, 0xA8, 0x50, 0xE0}}, 13};
  DEVPROPKEY pkey_display_name = {{0xB725F130, 0x47EF, 0x101A, {0xA5, 0xF1, 0x02, 0x60, 0x8C, 0x9E, 0xEB, 0xAC}}, 10};

  monitor_array = g_ptr_array_new_with_free_func (g_object_unref);

  device_infoset = SetupDiGetClassDevs (&device_interface_monitor, 0, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);

  if (device_infoset == INVALID_HANDLE_VALUE)
    return monitor_array;

  for (device_index = 0; TRUE; device_index++)
    {
      gunichar2 *p;
      gchar *instance_path;
      gunichar2 *prop;
      DWORD proptype;
      HKEY device_registry_key;
      GdkWin32Monitor *w32mon;
      GdkMonitor *mon;
      unsigned char *edid;
      DWORD edid_size;
      DWORD edid_type;

      memset (&device_info_data, 0, sizeof (device_info_data));
      device_info_data.cbSize = sizeof (device_info_data);

      if (!SetupDiEnumDeviceInfo (device_infoset, device_index, &device_info_data))
        {
          DWORD error_code = GetLastError ();

          if (error_code == ERROR_NO_MORE_ITEMS)
            break;

          g_warning ("SetupDiEnumDeviceInfo() failed: %lu\n", error_code);
          break;
        }

      if (!get_device_property (device_infoset,
                                &device_info_data,
                                &pkey_device_instance_id,
                                (gpointer *) &prop,
                                NULL,
                                &proptype,
                                NULL))
        continue;

      if (proptype != DEVPROP_TYPE_STRING)
        {
          g_free (prop);
          continue;
        }

      w32mon = g_object_new (GDK_TYPE_WIN32_MONITOR, "display", win32_display, NULL);
      mon = GDK_MONITOR (w32mon);

      g_ptr_array_add (monitor_array, w32mon);

      /* Half-initialized monitors are candidates for removal */
      w32mon->remove = TRUE;

      /* device instance ID looks like: DISPLAY\FOO\X&XXXXXXX&X&UIDXXX */
      for (p = prop; p[0]; p++)
        if (p[0] == L'\\')
          p[0] = L'#';
      /* now device instance ID looks like: DISPLAY#FOO#X&XXXXXXX&X&UIDXXX */

      /* instance path looks like: \\?\DISPLAY#FOO#X&XXXXXXX&X&UIDXXX#{e6f07b5f-ee97-4a90-b076-33f57bf4eaa7} */
      instance_path = g_strdup_printf ("\\\\?\\%ls#{" G_GUID_FORMAT "}",
                                       prop,
                                       g_format_guid (&device_interface_monitor));
      w32mon->instance_path = g_utf8_strdown (instance_path, -1);
      g_free (instance_path);
      g_free (prop);

      if (get_device_property (device_infoset,
                               &device_info_data,
                               &pkey_manufacturer,
                               (gpointer *) &prop,
                               NULL, &proptype, NULL))
        {
          if (proptype == DEVPROP_TYPE_STRING)
            {
              gchar *manufacturer = g_utf16_to_utf8 (prop, -1, NULL, NULL, NULL);
              gdk_monitor_set_manufacturer (mon, manufacturer);
              g_free (manufacturer);
            }

          g_free (prop);
        }

      if (get_device_property (device_infoset,
                               &device_info_data,
                               &pkey_display_name,
                               (gpointer *) &prop,
                               NULL, &proptype, NULL))
        {
          if (proptype == DEVPROP_TYPE_STRING)
            {
              gchar *name = g_utf16_to_utf8 (prop, -1, NULL, NULL, NULL);
              gdk_monitor_set_model (mon, name);
              g_free (name);
            }

          g_free (prop);
        }

      device_registry_key = SetupDiOpenDevRegKey (device_infoset, &device_info_data, DICS_FLAG_GLOBAL, 0, DIREG_DEV, KEY_READ);

      if (device_registry_key == NULL || device_registry_key == INVALID_HANDLE_VALUE)
        continue;

      edid = NULL;
      edid_size = 0;


      if (RegQueryValueExW (device_registry_key, L"EDID",
                            NULL, &edid_type,
                            edid, &edid_size) == ERROR_SUCCESS)
        {
          edid = g_malloc (edid_size);

          if (RegQueryValueExW (device_registry_key, L"EDID",
                                NULL, &edid_type,
                                edid, &edid_size) == ERROR_SUCCESS)
            {
              gdk_monitor_set_physical_size (mon,
                                             ((edid[68] & 0x00F0) << 4) + edid[66],
                                             ((edid[68] & 0x000F) << 8) + edid[67]);
            }

          g_free (edid);
        }

      RegCloseKey (device_registry_key);
    }

  SetupDiDestroyDeviceInfoList (device_infoset);

  return monitor_array;
}

static void
populate_monitor_devices_from_display_config (GPtrArray *monitors)
{
  HMODULE user32;
  LONG return_code;
  funcGetDisplayConfigBufferSizes getDisplayConfigBufferSizes;
  funcQueryDisplayConfig queryDisplayConfig;
  funcDisplayConfigGetDeviceInfo displayConfigGetDeviceInfo;
  UINT32 dispconf_mode_count;
  UINT32 dispconf_path_count;
  fixedDISPLAYCONFIG_PATH_INFO *dispconf_paths;
  DISPLAYCONFIG_MODE_INFO *dispconf_modes;
  gint path_index;

  user32 = LoadLibraryA ("user32.dll");

  if (user32 == NULL)
    return;

  getDisplayConfigBufferSizes = (funcGetDisplayConfigBufferSizes) GetProcAddress (user32,
                                                                                  "GetDisplayConfigBufferSizes");
  queryDisplayConfig = (funcQueryDisplayConfig) GetProcAddress (user32,
                                                                "QueryDisplayConfig");
  displayConfigGetDeviceInfo = (funcDisplayConfigGetDeviceInfo) GetProcAddress (user32,
                                                                                "DisplayConfigGetDeviceInfo");
  if (getDisplayConfigBufferSizes == NULL ||
      queryDisplayConfig == NULL ||
      displayConfigGetDeviceInfo == NULL)
    {
      /* This does happen on Windows Vista, so don't warn about this */
      FreeLibrary (user32);

      return;
    }

  return_code = getDisplayConfigBufferSizes (QDC_ONLY_ACTIVE_PATHS,
                                         &dispconf_path_count,
                                         &dispconf_mode_count);

  if (return_code != ERROR_SUCCESS)
    {
      g_warning ("Can't get displayconfig buffer size: 0x%lx\n", return_code);
      FreeLibrary (user32);

      return;
    }

  dispconf_paths = g_new (fixedDISPLAYCONFIG_PATH_INFO, dispconf_path_count);
  dispconf_modes = g_new (DISPLAYCONFIG_MODE_INFO, dispconf_mode_count);

  return_code = queryDisplayConfig (QDC_ONLY_ACTIVE_PATHS,
                                    &dispconf_path_count,
                                    dispconf_paths,
                                    &dispconf_mode_count,
                                    dispconf_modes,
                                    NULL);

  if (return_code != ERROR_SUCCESS)
    {
      g_free (dispconf_paths);
      g_free (dispconf_modes);
      FreeLibrary (user32);

      return;
    }

  for (path_index = 0; path_index < dispconf_path_count; path_index++)
    {
      fixedDISPLAYCONFIG_TARGET_DEVICE_NAME tdn;
      gint i;
      GdkWin32Monitor *w32mon;
      GdkMonitor *mon;
      gchar *path, *path_lower;
      DISPLAYCONFIG_RATIONAL *refresh;

      if ((dispconf_paths[path_index].flags & DISPLAYCONFIG_PATH_ACTIVE) == 0)
        continue;

      tdn.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
      tdn.header.size = sizeof (tdn);
      tdn.header.adapterId = dispconf_paths[path_index].targetInfo.adapterId;
      tdn.header.id = dispconf_paths[path_index].targetInfo.id;

      return_code = displayConfigGetDeviceInfo (&tdn.header);

      if (return_code != ERROR_SUCCESS)
        continue;

      path = g_utf16_to_utf8 (tdn.monitorDevicePath, -1, NULL, NULL, NULL);

      if (path == NULL)
        continue;

      path_lower = g_utf8_strdown (path, -1);

      g_free (path);

      for (i = 0, w32mon = NULL; i < monitors->len; i++)
        {
          GdkWin32Monitor *m = g_ptr_array_index (monitors, i);

          if (g_strcmp0 (m->instance_path, path_lower) != 0)
            continue;

          w32mon = m;
          break;
       }

      g_free (path_lower);

      if (w32mon == NULL)
        continue;

      mon = GDK_MONITOR (w32mon);

      if (!tdn.flags.friendlyNameForced)
        {
          /* monitorFriendlyDeviceName is usually nicer */
          gchar *name = g_utf16_to_utf8 (tdn.monitorFriendlyDeviceName, -1, NULL, NULL, NULL);
          gdk_monitor_set_model (mon, name);
          g_free (name);
        }

      refresh = &dispconf_paths[path_index].targetInfo.refreshRate;
      gdk_monitor_set_refresh_rate (mon,
                                    refresh->Numerator * 1000 / refresh->Denominator);
    }

  g_free (dispconf_paths);
  g_free (dispconf_modes);

  FreeLibrary (user32);
}

typedef struct {
  GPtrArray *monitors;
  gboolean have_monitor_devices;
  GdkWin32Display *display;
} EnumMonitorData;

static BOOL CALLBACK
enum_monitor (HMONITOR hmonitor,
              HDC      hdc,
              LPRECT   rect,
              LPARAM   param)
{
  EnumMonitorData *data = (EnumMonitorData *) param;
  MONITORINFOEXW monitor_info;
  DWORD i_adapter;

  /* Grab monitor_info for this logical monitor */
  monitor_info.cbSize = sizeof (MONITORINFOEXW);
  GetMonitorInfoW (hmonitor, (MONITORINFO *) &monitor_info);

  /* Sidestep to enumerate display adapters */
  for (i_adapter = 0; TRUE; i_adapter++)
    {
      DISPLAY_DEVICEW dd;
      DEVMODEW dm;
      DWORD i_monitor;
      DWORD frequency;

      memset (&dd, 0, sizeof (dd));
      dd.cb = sizeof (dd);

      /* Get i_adapter'th adapter */
      if (!EnumDisplayDevicesW (NULL, i_adapter, &dd, EDD_GET_DEVICE_INTERFACE_NAME))
        break;

      if ((dd.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) == 0)
        continue;

      /* Match this display adapter to one for which we've got monitor_info
       * (logical monitor == adapter)
       */
      if (wcscmp (dd.DeviceName, monitor_info.szDevice) != 0)
        continue;

      dm.dmSize = sizeof (dm);

      /* Grab refresh rate for this adapter while we're at it */
      if (EnumDisplaySettingsW (dd.DeviceName, ENUM_CURRENT_SETTINGS, &dm))
        frequency = dm.dmDisplayFrequency;
      else
        frequency = 0;

      /* Enumerate monitors connected to this display adapter */
      for (i_monitor = 0; TRUE; i_monitor++)
        {
          DISPLAY_DEVICEW dd_monitor;
          gchar *device_id_lower, *tmp;
          DWORD i;
          GdkWin32Monitor *w32mon;
          GdkMonitor *mon;
          GdkRectangle rect;
          guint scale;

          memset (&dd_monitor, 0, sizeof (dd_monitor));
          dd_monitor.cb = sizeof (dd_monitor);

          if (data->have_monitor_devices)
            {
              /* Get i_monitor'th monitor */
              if (!EnumDisplayDevicesW (dd.DeviceName, i_monitor, &dd_monitor, EDD_GET_DEVICE_INTERFACE_NAME))
                break;

              tmp = g_utf16_to_utf8 (dd_monitor.DeviceID, -1, NULL, NULL, NULL);

              if (tmp == NULL)
                continue;

              device_id_lower = g_utf8_strdown (tmp, -1);
              g_free (tmp);

              /* Match this monitor to one of the monitor devices we found earlier */
              for (i = 0, w32mon = NULL; i < data->monitors->len; i++)
                {
                  GdkWin32Monitor *m = g_ptr_array_index (data->monitors, i);

                  if (g_strcmp0 (device_id_lower, m->instance_path) != 0)
                    continue;

                  w32mon = m;
                  break;
                }

              g_free (device_id_lower);

              if (w32mon == NULL)
                continue;
            }
          else
            {
              /* Headless PC or a virtual machine, it has no monitor devices.
               * Make one up.
               */
              w32mon = g_object_new (GDK_TYPE_WIN32_MONITOR, "display", data->display, NULL);
              g_ptr_array_add (data->monitors, w32mon);
              i = data->monitors->len - 1;
              w32mon->madeup = TRUE;
            }

          mon = GDK_MONITOR (w32mon);

          if (gdk_monitor_get_model (mon) == NULL)
            {
              
              gchar *name = NULL;

              /* Only use dd.DeviceName as a last resort, as it is just
               * \\.\DISPLAYX\MonitorY (for some values of X and Y).
               */
              if (dd_monitor.DeviceName[0] != L'\0')
                name = g_utf16_to_utf8 (dd_monitor.DeviceName, -1, NULL, NULL, NULL);
              else if (dd.DeviceName[0] != L'\0')
                name = g_utf16_to_utf8 (dd.DeviceName, -1, NULL, NULL, NULL);

              if (name != NULL)
                gdk_monitor_set_model (mon, name);

              g_free (name);
            }

          /* GetDeviceCaps seems to provide a wild guess, prefer more precise EDID info */
          if (gdk_monitor_get_width_mm (mon) == 0 &&
              gdk_monitor_get_height_mm (mon) == 0)
            {
              HDC hDC = CreateDCW (L"DISPLAY", monitor_info.szDevice, NULL, NULL);

              gdk_monitor_set_physical_size (mon,
                                             GetDeviceCaps (hDC, HORZSIZE),
                                             GetDeviceCaps (hDC, VERTSIZE));
              DeleteDC (hDC);
            }

          /* frequency is in Hz and is unsigned long,
           * prefer more precise refresh_rate found earlier,
           * which comes as a Numerator & Denominator pair and is more precise.
           */
          if (gdk_monitor_get_refresh_rate (mon) == 0)
            gdk_monitor_set_refresh_rate (mon, frequency * 1000);

          /* This is the reason this function exists. This data is not available
           * via other functions.
           */
          rect.x = monitor_info.rcWork.left;
          rect.y = monitor_info.rcWork.top;
          rect.width = (monitor_info.rcWork.right - monitor_info.rcWork.left);
          rect.height = (monitor_info.rcWork.bottom - monitor_info.rcWork.top);
          /* This is temporary, scale will be applied below */
          w32mon->work_rect = rect;

          if (data->display->has_fixed_scale)
            scale = data->display->window_scale;
          else
            {
              /* First acquire the scale using the current screen */
              scale = _gdk_win32_display_get_monitor_scale_factor (data->display, NULL, NULL, NULL);

              /* acquire the scale using the monitor which the window is nearest on Windows 8.1+ */
              if (data->display->have_at_least_win81)
                {
                  HMONITOR hmonitor;
                  POINT pt;

                  /* Not subtracting _gdk_offset_x and _gdk_offset_y because they will only
                   * be added later on, in _gdk_win32_display_get_monitor_list().
                   */
                  pt.x = w32mon->work_rect.x + w32mon->work_rect.width / 2;
                  pt.y = w32mon->work_rect.y + w32mon->work_rect.height / 2;
                  hmonitor = MonitorFromPoint (pt, MONITOR_DEFAULTTONEAREST);
                  scale = _gdk_win32_display_get_monitor_scale_factor (data->display, hmonitor, NULL, NULL);
                }
            }

          gdk_monitor_set_scale_factor (mon, scale);
          /* Now apply the scale to the work rectangle */
          w32mon->work_rect.x /= scale;
          w32mon->work_rect.y /= scale;
          w32mon->work_rect.width /= scale;
          w32mon->work_rect.height /= scale;

          rect.x = monitor_info.rcMonitor.left / scale;
          rect.y = monitor_info.rcMonitor.top / scale;
          rect.width = (monitor_info.rcMonitor.right - monitor_info.rcMonitor.left) / scale;
          rect.height = (monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top) / scale;
          gdk_monitor_set_position (mon, rect.x, rect.y);
          gdk_monitor_set_size (mon, rect.width, rect.height);

          if (monitor_info.dwFlags & MONITORINFOF_PRIMARY && i != 0)
            {
              /* Put primary monitor at index 0, just in case somebody needs
               * to know which one is the primary.
               */
              GdkWin32Monitor *temp = g_ptr_array_index (data->monitors, 0);
              g_ptr_array_index (data->monitors, 0) = w32mon;
              g_ptr_array_index (data->monitors, i) = temp;
            }

          /* Work area is the most important component, actively used by GTK,
           * but our initial list of monitor devices did not have it.
           * Any monitor devices not matched in this functions will have
           * 0-filled work area and will therefore be useless, so let them
           * keep remove == TRUE and be removed further up the stack.
           */
          w32mon->remove = FALSE;

          /* One virtual monitor per display adapter */
          if (w32mon->madeup)
            break;
        }
    }

  return TRUE;
}

static void
prune_monitors (EnumMonitorData *data)
{
  gint i;

  for (i = 0; i < data->monitors->len; i++)
    {
      GdkWin32Monitor *m;

      m = g_ptr_array_index (data->monitors, i);

      if (m->remove)
        {
          g_ptr_array_remove_index (data->monitors, i);
          continue;
        }
    }
}

GPtrArray *
_gdk_win32_display_get_monitor_list (GdkWin32Display *win32_display)
{
  EnumMonitorData data;
  gint i;

  data.display = win32_display;
  data.monitors = get_monitor_devices (win32_display);

  if (data.monitors->len != 0)
    {
      populate_monitor_devices_from_display_config (data.monitors);
      data.have_monitor_devices = TRUE;
    }
  else
    {
      data.have_monitor_devices = FALSE;
    }

  EnumDisplayMonitors (NULL, NULL, enum_monitor, (LPARAM) &data);

  prune_monitors (&data);

  if (data.monitors->len == 0 && data.have_monitor_devices)
    {
      /* We thought we had monitors, but enumeration eventually failed, and
       * we have none. Try again, this time making stuff up as we go.
       */
      data.have_monitor_devices = FALSE;
      EnumDisplayMonitors (NULL, NULL, enum_monitor, (LPARAM) &data);
      prune_monitors (&data);
    }

  _gdk_offset_x = G_MININT;
  _gdk_offset_y = G_MININT;

  for (i = 0; i < data.monitors->len; i++)
    {
      GdkWin32Monitor *m;
      GdkRectangle rect;

      m = g_ptr_array_index (data.monitors, i);

      /* Calculate offset */
      gdk_monitor_get_geometry (GDK_MONITOR (m), &rect);
      _gdk_offset_x = MAX (_gdk_offset_x, -rect.x);
      _gdk_offset_y = MAX (_gdk_offset_y, -rect.y);
    }

  GDK_NOTE (MISC, g_print ("Multi-monitor offset: (%d,%d)\n",
                           _gdk_offset_x, _gdk_offset_y));

  /* Translate monitor coords into GDK coordinate space */
  for (i = 0; i < data.monitors->len; i++)
    {
      GdkWin32Monitor *m;
      GdkRectangle rect;

      m = g_ptr_array_index (data.monitors, i);

      gdk_monitor_get_geometry (GDK_MONITOR (m), &rect);
      rect.x += _gdk_offset_x;
      rect.y += _gdk_offset_y;
      gdk_monitor_set_position (GDK_MONITOR (m), rect.x, rect.y);

      m->work_rect.x += _gdk_offset_x;
      m->work_rect.y += _gdk_offset_y;

      GDK_NOTE (MISC, g_print ("Monitor %d: %dx%d@%+d%+d\n", i,
                               rect.width, rect.height, rect.x, rect.y));
    }

  return data.monitors;
}

static void
gdk_win32_monitor_finalize (GObject *object)
{
  GdkWin32Monitor *win32_monitor = GDK_WIN32_MONITOR (object);

  g_free (win32_monitor->instance_path);

  G_OBJECT_CLASS (gdk_win32_monitor_parent_class)->finalize (object);
}

int
_gdk_win32_monitor_compare (GdkWin32Monitor *a,
                            GdkWin32Monitor *b)
{
  if (a->instance_path != NULL &&
      b->instance_path != NULL)
    return g_strcmp0 (a->instance_path, b->instance_path);

  return a == b ? 0 : a < b ? -1 : 1;
}

static void
gdk_win32_monitor_get_workarea (GdkMonitor   *monitor,
                                GdkRectangle *dest)
{
  GdkWin32Monitor *win32_monitor = GDK_WIN32_MONITOR (monitor);

  *dest = win32_monitor->work_rect;
}

static void
gdk_win32_monitor_init (GdkWin32Monitor *monitor)
{
}

static void
gdk_win32_monitor_class_init (GdkWin32MonitorClass *class)
{
  G_OBJECT_CLASS (class)->finalize = gdk_win32_monitor_finalize;

  GDK_MONITOR_CLASS (class)->get_workarea = gdk_win32_monitor_get_workarea;
}
