/* GDK - The GIMP Drawing Kit
 * Copyright (C) 1995-1997 Peter Mattis, Spencer Kimball and Josh MacDonald
 * Copyright (C) 1998-2004 Tor Lillqvist
 * Copyright (C) 2001-2011 Hans Breuer
 * Copyright (C) 2007-2009 Cody Russell
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * Modified by the GTK+ Team and others 1997-2000.  See the AUTHORS
 * file for a list of people on the GTK+ Team.  See the ChangeLog
 * files for a list of changes.  These files are distributed with
 * GTK+ at ftp://ftp.gtk.org/pub/gtk/.
 */

#include "config.h"
#include <stdlib.h>

#include "gdk.h"
#include "gdkprivate-win32.h"
#include "gdkdeviceprivate.h"
#include "gdkdevicemanager-win32.h"
#include "gdkenumtypes.h"
#include "gdkwin32.h"
#include "gdkdisplayprivate.h"
#include "gdkdragsurfaceprivate.h"
#include "gdkframeclockidleprivate.h"
#include "gdkmonitorprivate.h"
#include "gdkpopupprivate.h"
#include "gdkseatprivate.h"
#include "gdksurfaceprivate.h"
#include "gdktoplevelprivate.h"
#include "gdkwin32surface.h"
#include "gdkwin32cursor.h"
#include "gdkinput-dmanipulation.h"
#include "gdkinput-winpointer.h"
#include "gdkglcontext-win32.h"
#include "gdkdisplay-win32.h"
#include "gdkdevice-win32.h"
#include "gdkcairocontext-win32.h"
#include "gdkmonitor-win32.h"

#include <cairo-win32.h>
#include <dwmapi.h>
#include <math.h>

/* forward declarations */
static void gdk_surface_win32_finalize (GObject *object);
static void compute_toplevel_size      (GdkSurface *surface,
                                        gboolean    update_geometry,
                                        int        *width,
                                        int        *height);

static void gdk_win32_toplevel_state_callback   (GdkSurface *surface);
static void gdk_win32_surface_set_transient_for (GdkSurface *surface,
                                                 GdkSurface *parent);
static void gdk_win32_push_modal_surface        (GdkSurface *surface);
static void gdk_win32_remove_modal_surface      (GdkSurface *surface);
static void gdk_win32_impl_frame_clock_after_paint (GdkFrameClock *clock,
                                                    GdkSurface    *surface);

static void gdk_win32_surface_maximize (GdkSurface *surface);
static void gdk_win32_surface_unmaximize (GdkSurface *surface);
static void gdk_win32_surface_minimize (GdkSurface *surface);

typedef struct _FullscreenInfo FullscreenInfo;

struct _FullscreenInfo
{
  RECT  r;
  guint hint_flags;
  LONG  style;
};

G_DEFINE_TYPE (GdkWin32Surface, gdk_win32_surface, GDK_TYPE_SURFACE)

static void
gdk_win32_surface_init (GdkWin32Surface *impl)
{
  impl->hicon_big = NULL;
  impl->hicon_small = NULL;
  impl->hint_flags = 0;
  impl->transient_owner = NULL;
  impl->transient_children = NULL;
  impl->num_transients = 0;
  impl->changing_state = FALSE;
  impl->surface_scale = 1;
}


static void
gdk_surface_win32_dispose (GObject *object)
{
  GdkWin32Surface *surface;

  g_return_if_fail (GDK_IS_WIN32_SURFACE (object));

  surface = GDK_WIN32_SURFACE (object);

  g_clear_object (&surface->cursor);

  G_OBJECT_CLASS (gdk_win32_surface_parent_class)->dispose (object);
}


static void
gdk_surface_win32_finalize (GObject *object)
{
  GdkWin32Surface *surface;

  g_return_if_fail (GDK_IS_WIN32_SURFACE (object));

  surface = GDK_WIN32_SURFACE (object);

  if (!GDK_SURFACE_DESTROYED (surface))
    {
      gdk_win32_display_handle_table_remove (gdk_surface_get_display (GDK_SURFACE (surface)),
                                             surface->handle);
    }

  if (surface->hicon_big != NULL)
    {
      GDI_CALL (DestroyIcon, (surface->hicon_big));
      surface->hicon_big = NULL;
    }

  if (surface->hicon_small != NULL)
    {
      GDI_CALL (DestroyIcon, (surface->hicon_small));
      surface->hicon_small = NULL;
    }

  _gdk_win32_surface_unregister_dnd (GDK_SURFACE (surface));

  g_assert (surface->transient_owner == NULL);
  g_assert (surface->transient_children == NULL);

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

void
_gdk_win32_get_window_client_area_rect (GdkSurface *surface,
                                        int         scale,
                                        RECT       *rect)
{
  int x, y, width, height;

  gdk_surface_get_geometry (surface, &x, &y, NULL, NULL);
  width = gdk_surface_get_width (surface);
  height = gdk_surface_get_height (surface);
  rect->left = x * scale;
  rect->top = y * scale;
  rect->right = rect->left + width * scale;
  rect->bottom = rect->top + height * scale;
}

static void
gdk_win32_impl_frame_clock_after_paint (GdkFrameClock *clock,
                                        GdkSurface    *surface)
{
  DWM_TIMING_INFO timing_info;
  LARGE_INTEGER tick_frequency;
  GdkFrameTimings *timings;

  timings = gdk_frame_clock_get_timings (clock, gdk_frame_clock_get_frame_counter (clock));

  if (timings)
    {
      timings->refresh_interval = 16667; /* default to 1/60th of a second */
      timings->presentation_time = 0;

      if (QueryPerformanceFrequency (&tick_frequency))
        {
          HRESULT hr;

          timing_info.cbSize = sizeof (timing_info);
          hr = DwmGetCompositionTimingInfo (NULL, &timing_info);

          if (SUCCEEDED (hr))
            {
              timings->refresh_interval = timing_info.qpcRefreshPeriod * (double)G_USEC_PER_SEC / tick_frequency.QuadPart;
              timings->presentation_time = timing_info.qpcCompose * (double)G_USEC_PER_SEC / tick_frequency.QuadPart;
            }
        }

      timings->complete = TRUE;
    }
}

void
_gdk_win32_adjust_client_rect (GdkSurface *surface,
                               RECT       *rect)
{
  LONG style, exstyle;

  style = GetWindowLong (GDK_SURFACE_HWND (surface), GWL_STYLE);
  exstyle = GetWindowLong (GDK_SURFACE_HWND (surface), GWL_EXSTYLE);
  API_CALL (AdjustWindowRectEx, (rect, style, FALSE, exstyle));
}

gboolean
_gdk_win32_surface_enable_transparency (GdkSurface *surface)
{
  DWM_BLURBEHIND blur_behind;
  HRGN empty_region;
  HRESULT call_result;
  HWND this_hwnd;

  if (surface == NULL || GDK_SURFACE_HWND (surface) == NULL)
    return FALSE;

  if (!gdk_display_is_composited (gdk_surface_get_display (surface)))
    return FALSE;

  this_hwnd = GDK_SURFACE_HWND (surface);

  empty_region = CreateRectRgn (0, 0, -1, -1);

  if (empty_region == NULL)
    return FALSE;

  memset (&blur_behind, 0, sizeof (blur_behind));
  blur_behind.dwFlags = DWM_BB_ENABLE | DWM_BB_BLURREGION;
  blur_behind.hRgnBlur = empty_region;
  blur_behind.fEnable = TRUE;
  call_result = DwmEnableBlurBehindWindow (this_hwnd, &blur_behind);

  if (!SUCCEEDED (call_result))
    g_warning ("%s: %s (%p) failed: %" G_GINT32_MODIFIER "x",
        G_STRLOC, "DwmEnableBlurBehindWindow", this_hwnd, (guint32) call_result);

  DeleteObject (empty_region);

  return SUCCEEDED (call_result);
}

static const char *
get_default_title (void)
{
  const char *title;
  title = g_get_application_name ();
  if (!title)
    title = g_get_prgname ();

  return title;
}

/* RegisterGdkClass
 *   is a wrapper function for RegisterWindowClassEx.
 *   It creates at least one unique class for every
 *   GdkSurfaceType. If support for single window-specific icons
 *   is ever needed (e.g Dialog specific), every such window should
 *   get its own class
 */
static ATOM
RegisterGdkClass (GType wtype)
{
  static ATOM klassTOPLEVEL = 0;
  static ATOM klassTEMP     = 0;
  static HICON hAppIcon = NULL;
  static HICON hAppIconSm = NULL;
  static WNDCLASSEXW wcl;
  ATOM klass = 0;

  wcl.cbSize = sizeof (WNDCLASSEXW);
  wcl.style = 0; /* DON'T set CS_<H,V>REDRAW. It causes total redraw
                  * on WM_SIZE and WM_MOVE. Flicker, Performance!
                  */
  wcl.lpfnWndProc = _gdk_win32_surface_procedure;
  wcl.cbClsExtra = 0;
  wcl.cbWndExtra = 0;
  wcl.hInstance = this_module ();
  wcl.hIcon = 0;
  wcl.hIconSm = 0;

  /* initialize once! */
  if (0 == hAppIcon && 0 == hAppIconSm)
    {
      wchar_t sLoc [MAX_PATH+1];

      // try to load first icon of executable program
      if (0 != GetModuleFileName (NULL, sLoc, MAX_PATH))
        {
          ExtractIconEx (sLoc, 0, &hAppIcon, &hAppIconSm, 1);

          if (0 == hAppIcon && 0 == hAppIconSm)
            {
              // fallback : load icon from GTK DLL
              if (0 != GetModuleFileName (this_module (), sLoc, MAX_PATH))
		{
		  ExtractIconEx (sLoc, 0, &hAppIcon, &hAppIconSm, 1);
		}
            }
        }

      if (0 == hAppIcon && 0 == hAppIconSm)
        {
          hAppIcon = LoadImage (NULL, IDI_APPLICATION, IMAGE_ICON,
                                GetSystemMetrics (SM_CXICON),
                                GetSystemMetrics (SM_CYICON), 0);
          hAppIconSm = LoadImage (NULL, IDI_APPLICATION, IMAGE_ICON,
                                  GetSystemMetrics (SM_CXSMICON),
                                  GetSystemMetrics (SM_CYSMICON), 0);
        }
    }

  if (0 == hAppIcon)
    hAppIcon = hAppIconSm;
  else if (0 == hAppIconSm)
    hAppIconSm = hAppIcon;

  wcl.lpszMenuName = NULL;

  /* initialize once per class */
  /*
   * HB: Setting the background brush leads to flicker, because we
   * don't get asked how to clear the background. This is not what
   * we want, at least not for input_only surfaces ...
   */
#define ONCE_PER_CLASS() \
  wcl.hIcon = CopyIcon (hAppIcon); \
  wcl.hIconSm = CopyIcon (hAppIconSm); \
  wcl.hbrBackground = NULL; \
  wcl.hCursor = LoadCursor (NULL, IDC_ARROW);

  /* MSDN: CS_OWNDC is needed for OpenGL contexts */
  wcl.style |= CS_OWNDC;

  if (wtype != GDK_TYPE_WIN32_DRAG_SURFACE)
    {
      if (0 == klassTOPLEVEL)
        {
          wcl.lpszClassName = L"gdkSurfaceToplevel";

          ONCE_PER_CLASS ();
          klassTOPLEVEL = RegisterClassExW (&wcl);
        }
      klass = klassTOPLEVEL;
    }
  else
    {
      if (klassTEMP == 0)
        {
          wcl.lpszClassName = L"gdkSurfaceTemp";
          wcl.style |= CS_SAVEBITS;
          ONCE_PER_CLASS ();
          klassTEMP = RegisterClassExW (&wcl);
        }

      klass = klassTEMP;
    }

  if (klass == 0)
    {
      WIN32_API_FAILED ("RegisterClassExW");
      g_error ("That is a fatal error");
    }
  return klass;
}

static void
gdk_win32_surface_constructed (GObject *object)
{
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (object);
  GdkSurface *surface = GDK_SURFACE (impl);
  GdkDisplay *display = gdk_surface_get_display (surface);
  GdkWin32Display *display_win32 = GDK_WIN32_DISPLAY (display);
  GdkFrameClock *frame_clock;
  HANDLE owner;
  ATOM klass = 0;
  DWORD dwStyle = 0, dwExStyle;
  RECT rect;
  const char *title;
  wchar_t *wtitle;

  impl->surface_scale = gdk_win32_display_get_monitor_scale_factor (display_win32, NULL, NULL);

  dwExStyle = 0;
  owner = NULL;

  /* MSDN: We need WS_CLIPCHILDREN and WS_CLIPSIBLINGS for GL Context Creation */
  dwStyle = WS_CLIPCHILDREN | WS_CLIPSIBLINGS;

  if (G_OBJECT_TYPE (impl) == GDK_TYPE_WIN32_TOPLEVEL)
    {
      dwStyle |= WS_OVERLAPPEDWINDOW;
      frame_clock = _gdk_frame_clock_idle_new ();
    }
  else if (G_OBJECT_TYPE (impl) == GDK_TYPE_WIN32_DRAG_SURFACE)
    {
      dwExStyle |= WS_EX_TOOLWINDOW | WS_EX_TOPMOST;
      dwStyle |= WS_POPUP;
      frame_clock = _gdk_frame_clock_idle_new ();
    }
  else if (G_OBJECT_TYPE (impl) == GDK_TYPE_WIN32_POPUP)
    {
      GdkSurface *parent = gdk_popup_get_parent (GDK_POPUP (impl));
      dwStyle |= WS_POPUP;
      owner = GDK_SURFACE_HWND (parent);
      frame_clock = g_object_ref (gdk_surface_get_frame_clock (parent));
    }
  else
    {
      g_assert_not_reached ();
    }

  gdk_surface_set_frame_clock (surface, frame_clock);
  g_object_unref (frame_clock);

  title = get_default_title ();
  if (!title || !*title)
    title = "";

  klass = RegisterGdkClass (G_OBJECT_TYPE (impl));

  wtitle = g_utf8_to_utf16 (title, -1, NULL, NULL, NULL);

  impl->handle = CreateWindowExW (dwExStyle,
                                  MAKEINTRESOURCEW (klass),
                                  wtitle,
                                  dwStyle,
                                  CW_USEDEFAULT, CW_USEDEFAULT,
                                  CW_USEDEFAULT, CW_USEDEFAULT,
                                  owner,
                                  NULL,
                                  this_module (),
                                  surface);
  if (impl->handle == NULL)
    {
      WIN32_API_FAILED ("CreateWindowExW");
      g_error ("Fatal error: CreateWindowExW failed.");
    }

  GetWindowRect (impl->handle, &rect);
  impl->initial_x = rect.left;
  impl->initial_y = rect.top;

  g_object_ref (impl);
  /* Take note: we're inserting a pointer into a heap-allocated
   * object (impl). Inserting a pointer to a stack variable
   * will break the logic, since stack variables are short-lived.
   * We insert a pointer to the handle instead of the handle itself
   * probably because we need to hash them differently depending
   * on the bitness of the OS. That pointer is still unique,
   * so this works out in the end.
   */
  gdk_win32_display_handle_table_insert (display, &GDK_SURFACE_HWND (impl), impl);

  g_free (wtitle);

  gdk_surface_set_egl_native_window (surface, (void *) impl->handle);

  if (G_OBJECT_TYPE (impl) != GDK_TYPE_WIN32_DRAG_SURFACE)
    {
      if (display_win32->tablet_input_api == GDK_WIN32_TABLET_INPUT_API_WINPOINTER)
        gdk_winpointer_initialize_surface (surface);

      gdk_dmanipulation_initialize_surface (surface);
    }

  _gdk_win32_surface_enable_transparency (surface);
  _gdk_win32_surface_register_dnd (surface);
  _gdk_win32_surface_update_style_bits (surface);

  g_signal_connect (frame_clock,
                    "after-paint",
                    G_CALLBACK (gdk_win32_impl_frame_clock_after_paint),
                    impl);

  impl->hdc = GetDC (impl->handle);
  impl->inhibit_configure = TRUE;

  G_OBJECT_CLASS (gdk_win32_surface_parent_class)->constructed (object);
}

static void
gdk_win32_surface_destroy (GdkSurface *surface,
                           gboolean    foreign_destroy)
{
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

  g_return_if_fail (GDK_IS_SURFACE (surface));

  GDK_NOTE (MISC, g_print ("gdk_win32_surface_destroy: %p\n",
			   GDK_SURFACE_HWND (surface)));

  /* Remove ourself from the modal stack */
  gdk_win32_remove_modal_surface (surface);

  g_signal_handlers_disconnect_by_func (gdk_surface_get_frame_clock (surface),
                                        gdk_win32_impl_frame_clock_after_paint,
                                        surface);

  /* Remove all our transient children */
  while (impl->transient_children != NULL)
    {
      GdkSurface *child = impl->transient_children->data;
      gdk_win32_surface_set_transient_for (child, NULL);
    }

  /* Remove ourself from our transient owner */
  if (impl->transient_owner != NULL)
    {
      gdk_win32_surface_set_transient_for (surface, NULL);
    }

  if (!foreign_destroy)
    {
      gdk_surface_set_egl_native_window (surface, NULL);
      surface->destroyed = TRUE;
      DestroyWindow (GDK_SURFACE_HWND (surface));
    }
}

/* This function is called when the surface really gone.
 */
static void
gdk_win32_surface_destroy_notify (GdkSurface *surface)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  GDK_NOTE (EVENTS,
	    g_print ("gdk_surface_destroy_notify: %p%s\n",
		     GDK_SURFACE_HWND (surface),
		     (GDK_SURFACE_DESTROYED (surface) ? " (destroyed)" : "")));

  if (!GDK_SURFACE_DESTROYED (surface))
    {
      g_warning ("surface %p unexpectedly destroyed",
                 GDK_SURFACE_HWND (surface));

      _gdk_surface_destroy (surface, TRUE);
    }

  gdk_win32_display_handle_table_remove (gdk_surface_get_display (surface),
                                         GDK_SURFACE_HWND (surface));
  g_object_unref (surface);
}

static void
get_outer_rect (GdkSurface *surface,
                int         width,
                int         height,
                RECT       *rect)
{
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

  rect->left = rect->top = 0;
  rect->right = width * impl->surface_scale;
  rect->bottom = height * impl->surface_scale;

  _gdk_win32_adjust_client_rect (surface, rect);
}

static void
gdk_win32_surface_fullscreen (GdkSurface *surface,
                              GdkMonitor *monitor);

static void
show_surface_internal (GdkSurface *surface,
                       gboolean    already_mapped,
                       gboolean    unminimize)
{
  GdkWin32Surface *impl;
  DWORD exstyle;

  if (surface->destroyed)
    return;

  GDK_NOTE (MISC, g_print ("show_surface_internal: %p: %s%s\n",
			   GDK_SURFACE_HWND (surface),
			   _gdk_win32_surface_state_to_string (surface->state),
			   (unminimize ? " unminimize" : "")));

  /* If asked to show (not unminimize) a withdrawn and iconified
   * surface, do that.
   */
  if (!unminimize &&
      !already_mapped &&
      (surface->state & GDK_TOPLEVEL_STATE_MINIMIZED))
    {
      GtkShowSurfaceHWND (surface, SW_SHOWMINNOACTIVE);
      return;
    }

  /* If asked to just show an iconified surface, do nothing. */
  if (!unminimize && (surface->state & GDK_TOPLEVEL_STATE_MINIMIZED))
    return;

  /* If asked to unminimize an already noniconified surface, do
   * nothing. (Especially, don't cause the surface to rise and
   * activate. There are different calls for that.)
   */
  if (unminimize && !(surface->state & GDK_TOPLEVEL_STATE_MINIMIZED))
    return;

  /* If asked to show (but not raise) a surface that is already
   * visible, do nothing.
   */
  if (!unminimize && !already_mapped && IsWindowVisible (GDK_SURFACE_HWND (surface)))
    return;

  /* For initial map of "normal" surfaces we want to emulate WM surface
   * positioning behaviour, which means:
   * + default to the initial CW_USEDEFAULT placement,
   *   no matter if the user moved the surface before showing it.
   * + Certain surface types and hints have more elaborate positioning
   *   schemes.
   */
  impl = GDK_WIN32_SURFACE (surface);
  if (!already_mapped &&
      GDK_IS_TOPLEVEL (surface))
    {
      gboolean center = FALSE;
      RECT hwnd_rect, center_on_rect;
      int x, y;

      x = impl->initial_x;
      y = impl->initial_y;

      if (FALSE)
	{
	  HMONITOR monitor;
	  MONITORINFO mi;

	  monitor = MonitorFromWindow (GDK_SURFACE_HWND (surface), MONITOR_DEFAULTTONEAREST);
	  mi.cbSize = sizeof (mi);
	  if (monitor && GetMonitorInfo (monitor, &mi))
	    center_on_rect = mi.rcMonitor;
	  else
	    {
	      center_on_rect.left = 0;
	      center_on_rect.top = 0;
	      center_on_rect.right = GetSystemMetrics (SM_CXSCREEN);
	      center_on_rect.bottom = GetSystemMetrics (SM_CYSCREEN);
	    }
	  center = TRUE;
	}
      else if (impl->transient_owner != NULL &&
	       GDK_SURFACE_IS_MAPPED (impl->transient_owner))
	{
	  GdkSurface *owner = impl->transient_owner;
	  /* Center on transient parent */
          center_on_rect.left = owner->x * impl->surface_scale;
          center_on_rect.top = owner->y * impl->surface_scale;
	  center_on_rect.right = center_on_rect.left + owner->width * impl->surface_scale;
	  center_on_rect.bottom = center_on_rect.top + owner->height * impl->surface_scale;

	  _gdk_win32_adjust_client_rect (GDK_SURFACE (owner), &center_on_rect);
	  center = TRUE;
	}

      if (center)
	{
	  GetWindowRect (GDK_SURFACE_HWND (surface), &hwnd_rect);

	  x = center_on_rect.left + ((center_on_rect.right - center_on_rect.left) - (hwnd_rect.right - hwnd_rect.left)) / 2;
	  y = center_on_rect.top + ((center_on_rect.bottom - center_on_rect.top) - (hwnd_rect.bottom - hwnd_rect.top)) / 2;
	}

      API_CALL (SetWindowPos, (GDK_SURFACE_HWND (surface),
			       SWP_NOZORDER_SPECIFIED,
			       x, y, 0, 0,
			       SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER));
    }

  if (!already_mapped && GDK_IS_TOPLEVEL (surface))
    {
      /* Ensure new surfaces are fully onscreen */
      RECT hwnd_rect;
      HMONITOR monitor;
      MONITORINFO mi;
      int x, y;

      GetWindowRect (GDK_SURFACE_HWND (surface), &hwnd_rect);

      monitor = MonitorFromWindow (GDK_SURFACE_HWND (surface), MONITOR_DEFAULTTONEAREST);
      mi.cbSize = sizeof (mi);
      if (monitor && GetMonitorInfo (monitor, &mi))
	{
	  x = hwnd_rect.left;
	  y = hwnd_rect.top;

	  if (hwnd_rect.right > mi.rcWork.right)
	    {
	      hwnd_rect.left -= (hwnd_rect.right - mi.rcWork.right);
	      hwnd_rect.right -= (hwnd_rect.right - mi.rcWork.right);
	    }

	  if (hwnd_rect.bottom > mi.rcWork.bottom)
	    {
	      hwnd_rect.top -= (hwnd_rect.bottom - mi.rcWork.bottom);
	      hwnd_rect.bottom -= (hwnd_rect.bottom - mi.rcWork.bottom);
	    }

	  if (hwnd_rect.left < mi.rcWork.left)
	    {
	      hwnd_rect.right += (mi.rcWork.left - hwnd_rect.left);
	      hwnd_rect.left += (mi.rcWork.left - hwnd_rect.left);
	    }

	  if (hwnd_rect.top < mi.rcWork.top)
	    {
	      hwnd_rect.bottom += (mi.rcWork.top - hwnd_rect.top);
	      hwnd_rect.top += (mi.rcWork.top - hwnd_rect.top);
	    }

	  if (x != hwnd_rect.left || y != hwnd_rect.top)
	    API_CALL (SetWindowPos, (GDK_SURFACE_HWND (surface),
				     SWP_NOZORDER_SPECIFIED,
				     hwnd_rect.left, hwnd_rect.top, 0, 0,
				     SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER));
	}
    }


  if (surface->state & GDK_TOPLEVEL_STATE_MAXIMIZED)
    {
      GtkShowSurfaceHWND (surface, SW_MAXIMIZE);
    }
  else if (surface->state & GDK_TOPLEVEL_STATE_MINIMIZED)
    {
      GtkShowSurfaceHWND (surface, SW_RESTORE);
    }
  else if (GDK_IS_DRAG_SURFACE (surface))
    {
      if (!IsWindowVisible (GDK_SURFACE_HWND (surface)))
        GtkShowSurfaceHWND (surface, SW_SHOWNOACTIVATE);
      else
        GtkShowSurfaceHWND (surface, SW_SHOWNA);
    }
  else if (!IsWindowVisible (GDK_SURFACE_HWND (surface)))
    {
      GtkShowSurfaceHWND (surface, SW_SHOWNORMAL);
    }
  else
    {
      GtkShowSurfaceHWND (surface, SW_SHOW);
    }

  exstyle = GetWindowLong (GDK_SURFACE_HWND (surface), GWL_EXSTYLE);

  /* Sync STATE_ABOVE to TOPMOST */
  if (!GDK_IS_DRAG_SURFACE (surface) &&
      (((surface->state & GDK_TOPLEVEL_STATE_ABOVE) &&
	!(exstyle & WS_EX_TOPMOST)) ||
       (!(surface->state & GDK_TOPLEVEL_STATE_ABOVE) &&
	(exstyle & WS_EX_TOPMOST))))
    {
      API_CALL (SetWindowPos, (GDK_SURFACE_HWND (surface),
			       (surface->state & GDK_TOPLEVEL_STATE_ABOVE)?HWND_TOPMOST:HWND_NOTOPMOST,
			       0, 0, 0, 0,
			       SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE));
    }
}

void
gdk_win32_surface_show (GdkSurface *surface,
		       gboolean already_mapped)
{
  show_surface_internal (surface, FALSE, FALSE);
}

static void
gdk_win32_surface_hide (GdkSurface *surface)
{
  if (surface->destroyed)
    return;

  GDK_NOTE (MISC, g_print ("gdk_win32_surface_hide: %p: %s\n",
			   GDK_SURFACE_HWND (surface),
			   _gdk_win32_surface_state_to_string (surface->state)));

  if (GDK_SURFACE_IS_MAPPED (surface))
    gdk_surface_set_is_mapped (surface, FALSE);

  _gdk_surface_clear_update_area (surface);

  GtkShowSurfaceHWND (surface, SW_HIDE);
}

static void
gdk_win32_surface_do_move (GdkSurface *surface,
                           int         x,
                           int         y)
{
  RECT outer_rect;
  GdkWin32Surface *impl;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  GDK_NOTE (MISC, g_print ("gdk_win32_surface_move: %p: %+d%+d\n",
                           GDK_SURFACE_HWND (surface), x, y));

  if (surface->state & GDK_TOPLEVEL_STATE_FULLSCREEN)
    return;

  impl = GDK_WIN32_SURFACE (surface);
  get_outer_rect (surface, surface->width, surface->height, &outer_rect);

  GDK_NOTE (MISC, g_print ("... SetWindowPos(%p,NULL,%d,%d,0,0,"
                           "NOACTIVATE|NOSIZE|NOZORDER)\n",
                           GDK_SURFACE_HWND (surface),
                           x * impl->surface_scale,
                           y * impl->surface_scale));

  API_CALL (SetWindowPos, (GDK_SURFACE_HWND (surface),
                           SWP_NOZORDER_SPECIFIED,
                           x * impl->surface_scale,
                           y * impl->surface_scale,
                           0, 0,
                           SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER));
}

void
gdk_win32_surface_resize (GdkSurface *surface,
                          int width,
                          int height)
{
  RECT outer_rect;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  if (width < 1)
    width = 1;
  if (height < 1)
    height = 1;

  GDK_NOTE (MISC, g_print ("gdk_win32_surface_resize: %p: %dx%d\n",
                           GDK_SURFACE_HWND (surface), width, height));

  if (surface->state & GDK_TOPLEVEL_STATE_FULLSCREEN)
    return;

  get_outer_rect (surface, width, height, &outer_rect);

  GDK_NOTE (MISC, g_print ("... SetWindowPos(%p,NULL,0,0,%ld,%ld,"
                           "NOACTIVATE|NOMOVE|NOZORDER)\n",
                           GDK_SURFACE_HWND (surface),
                           outer_rect.right - outer_rect.left,
                           outer_rect.bottom - outer_rect.top));

  API_CALL (SetWindowPos, (GDK_SURFACE_HWND (surface),
                           SWP_NOZORDER_SPECIFIED,
                           0, 0,
                           outer_rect.right - outer_rect.left,
                           outer_rect.bottom - outer_rect.top,
                           SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER));
  surface->resize_count += 1;

  if (!GDK_WIN32_SURFACE (surface)->force_recompute_size)
    gdk_surface_request_layout (surface);
}

static void
gdk_win32_surface_do_move_resize (GdkSurface *surface,
                                  int         x,
                                  int         y,
                                  int         width,
                                  int         height)
{
  RECT outer_rect;
  GdkWin32Surface *impl;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  if (width < 1)
    width = 1;
  if (height < 1)
    height = 1;

  if (surface->state & GDK_TOPLEVEL_STATE_FULLSCREEN)
    return;

  GDK_NOTE (MISC, g_print ("gdk_win32_surface_move_resize: %p: %dx%d@%+d%+d\n",
                           GDK_SURFACE_HWND (surface),
                           width, height, x, y));

  impl = GDK_WIN32_SURFACE (surface);

  get_outer_rect (surface, width, height, &outer_rect);

  GDK_NOTE (MISC, g_print ("... SetWindowPos(%p,NULL,%d,%d,%ld,%ld,"
                           "NOACTIVATE|NOZORDER)\n",
                           GDK_SURFACE_HWND (surface),
                           x * impl->surface_scale,
                           y * impl->surface_scale,
                           outer_rect.right - outer_rect.left,
                           outer_rect.bottom - outer_rect.top));

  API_CALL (SetWindowPos, (GDK_SURFACE_HWND (surface),
                           SWP_NOZORDER_SPECIFIED,
                           x * impl->surface_scale,
                           y * impl->surface_scale,
                           outer_rect.right - outer_rect.left,
                           outer_rect.bottom - outer_rect.top,
                           SWP_NOACTIVATE | SWP_NOZORDER));
}

static void
gdk_win32_surface_move_resize_internal (GdkSurface *surface,
                                        gboolean    with_move,
                                        int         x,
                                        int         y,
                                        int         width,
                                        int         height)
{
  /* We ignore changes to the surface being moved or resized by the
     user, as we don't want to fight the user */
  if (GDK_SURFACE_HWND (surface) == GDK_WIN32_DISPLAY (gdk_surface_get_display (surface))->display_surface_record->modal_move_resize_hwnd)
    goto out;

  if (with_move && (width < 0 && height < 0))
    {
      gdk_win32_surface_do_move (surface, x, y);
    }
  else
    {
      _gdk_win32_surface_invalidate_egl_framebuffer (surface);

      if (with_move)
        {
          gdk_win32_surface_do_move_resize (surface, x, y, width, height);
        }
      else
        {
          gdk_win32_surface_resize (surface, width, height);
        }
    }

 out:
  gdk_surface_request_layout (surface);
}

void
gdk_win32_surface_move_resize (GdkSurface *surface,
                               int         x,
                               int         y,
                               int         width,
                               int         height)
{
  gdk_win32_surface_move_resize_internal (surface, TRUE, x, y, width, height);
}

void
gdk_win32_surface_move (GdkSurface *surface,
                        int         x,
                        int         y)
{
  gdk_win32_surface_move_resize_internal (surface, TRUE, x, y, -1, -1);
}

static void gdk_win32_surface_set_shadow_width (GdkSurface *surface,
                                                int         left,
                                                int         right,
                                                int         top,
                                                int         bottom);

static void
gdk_win32_surface_layout_popup (GdkSurface     *surface,
                                int             width,
                                int             height,
                                GdkPopupLayout *layout)
{
  GdkMonitor *monitor;
  GdkRectangle bounds;
  GdkRectangle final_rect;
  int x, y;
  int shadow_left, shadow_right, shadow_top, shadow_bottom;

  monitor = gdk_surface_get_layout_monitor (surface, layout,
                                            gdk_win32_monitor_get_workarea);
  if (!monitor)
    {
      GdkDisplay *display = gdk_surface_get_display (surface);
      monitor = gdk_win32_display_get_primary_monitor (display);
    }
  gdk_win32_monitor_get_workarea (monitor, &bounds);

  gdk_popup_layout_get_shadow_width (layout,
                                     &shadow_left,
                                     &shadow_right,
                                     &shadow_top,
                                     &shadow_bottom);

  gdk_win32_surface_set_shadow_width (surface,
                                      shadow_left,
                                      shadow_right,
                                      shadow_top,
                                      shadow_bottom);

  gdk_surface_layout_popup_helper (surface,
                                   width,
                                   height,
                                   shadow_left,
                                   shadow_right,
                                   shadow_top,
                                   shadow_bottom,
                                   monitor,
                                   &bounds,
                                   layout,
                                   &final_rect);

  gdk_surface_get_origin (surface->parent, &x, &y);
  x += final_rect.x;
  y += final_rect.y;

  if (final_rect.width != surface->width ||
      final_rect.height != surface->height)
    {
      gdk_win32_surface_move_resize (surface,
                                     x,
                                     y,
                                     final_rect.width,
                                     final_rect.height);
    }
  else
    gdk_win32_surface_move (surface, x, y);
}

static void
maybe_notify_mapped (GdkSurface *surface)
{
  if (surface->destroyed)
    return;

  if (!GDK_SURFACE_IS_MAPPED (surface))
    {
      gdk_surface_set_is_mapped (surface, TRUE);
      gdk_surface_invalidate_rect (surface, NULL);
    }
}

static void
show_popup (GdkSurface *surface)
{
  gdk_win32_surface_raise (surface);
  maybe_notify_mapped (surface);
  show_surface_internal (surface, FALSE, FALSE);
  gdk_surface_invalidate_rect (surface, NULL);
}

static void
show_grabbing_popup (GdkSeat    *seat,
                     GdkSurface *surface,
                     gpointer    user_data)
{
  show_popup (surface);
}

static gboolean
gdk_win32_surface_present_popup (GdkSurface     *surface,
                                 int             width,
                                 int             height,
                                 GdkPopupLayout *layout)
{
  gdk_win32_surface_layout_popup (surface, width, height, layout);

  if (GDK_SURFACE_IS_MAPPED (surface))
    return TRUE;

  if (surface->autohide)
    {
      gdk_seat_grab (gdk_display_get_default_seat (surface->display),
                     surface,
                     GDK_SEAT_CAPABILITY_ALL,
                     TRUE,
                     NULL, NULL,
                     show_grabbing_popup, NULL);
    }
  else
    {
      show_popup (surface);
    }

  return GDK_SURFACE_IS_MAPPED (surface);
}

void
gdk_win32_surface_raise (GdkSurface *surface)
{
  if (!GDK_SURFACE_DESTROYED (surface))
    {
      GDK_NOTE (MISC, g_print ("gdk_win32_surface_raise: %p\n",
			       GDK_SURFACE_HWND (surface)));

      if (GDK_IS_DRAG_SURFACE (surface))
        API_CALL (SetWindowPos, (GDK_SURFACE_HWND (surface), HWND_TOPMOST,
	                         0, 0, 0, 0,
				 SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOOWNERZORDER));
         
      else if (GDK_IS_POPUP (surface))
        ShowWindow (GDK_SURFACE_HWND (surface), SW_SHOWNOACTIVATE);
      else
        /* Do not wrap this in an API_CALL macro as SetForegroundWindow might
         * fail when for example dragging a surface belonging to a different
         * application at the time of a gtk_window_present() call due to focus
         * stealing prevention. */
        SetForegroundWindow (GDK_SURFACE_HWND (surface));
    }
}

/**
 * gdk_win32_surface_set_urgency_hint:
 * @surface: (type GdkWin32Surface): a native `GdkSurface`.
 * @urgent: if %TRUE, flashes both the window and the taskbar button
 *          continuously.
 *
 * Flashes the specified @surface.
 */
void
gdk_win32_surface_set_urgency_hint (GdkSurface *surface,
                                    gboolean    urgent)
{
  FLASHWINFO flashwinfo;

  g_return_if_fail (GDK_IS_WIN32_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  flashwinfo.cbSize = sizeof (flashwinfo);
  flashwinfo.hwnd = GDK_SURFACE_HWND (surface);
  if (urgent)
    flashwinfo.dwFlags = FLASHW_ALL | FLASHW_TIMER;
  else
    flashwinfo.dwFlags = FLASHW_STOP;
  flashwinfo.uCount = 0;
  flashwinfo.dwTimeout = 0;

  FlashWindowEx (&flashwinfo);
}

static gboolean
get_effective_surface_decorations (GdkSurface       *surface,
                                  GdkWMDecoration  *decoration)
{
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

  *decoration = 0;

  if (!GDK_IS_TOPLEVEL (surface))
    return FALSE;

  /* we want to apply the "no decorations", if decorations are disabled */
  if (!GDK_WIN32_SURFACE (surface)->decorate_all)
    return TRUE;

  if ((impl->hint_flags & GDK_HINT_MIN_SIZE) &&
      (impl->hint_flags & GDK_HINT_MAX_SIZE) &&
      impl->hints.min_width == impl->hints.max_width &&
      impl->hints.min_height == impl->hints.max_height)
    {
      *decoration = GDK_DECOR_ALL | GDK_DECOR_RESIZEH | GDK_DECOR_MAXIMIZE;

      *decoration |= GDK_DECOR_MINIMIZE;

      return TRUE;
    }
  else
    {
      *decoration = GDK_DECOR_ALL;
      return TRUE;
    }

  return FALSE;
}

static void
gdk_win32_surface_set_geometry_hints (GdkSurface         *surface,
                                      const GdkGeometry  *geometry,
                                      GdkSurfaceHints     geom_mask)
{
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);
  FullscreenInfo *fi;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  GDK_NOTE (MISC, g_print ("gdk_surface_set_geometry_hints: %p\n",
			   GDK_SURFACE_HWND (surface)));

  fi = g_object_get_data (G_OBJECT (surface), "fullscreen-info");
  if (fi)
    fi->hint_flags = geom_mask;
  else
    impl->hint_flags = geom_mask;
  impl->hints = *geometry;

  if (geom_mask & GDK_HINT_MIN_SIZE)
    {
      GDK_NOTE (MISC, g_print ("... MIN_SIZE: %dx%d\n",
			       geometry->min_width, geometry->min_height));
    }

  if (geom_mask & GDK_HINT_MAX_SIZE)
    {
      GDK_NOTE (MISC, g_print ("... MAX_SIZE: %dx%d\n",
			       geometry->max_width, geometry->max_height));
    }

  _gdk_win32_surface_update_style_bits (surface);
}

static void
gdk_win32_surface_set_title (GdkSurface *surface,
                             const char *title)
{
  wchar_t *wtitle;

  g_return_if_fail (GDK_IS_SURFACE (surface));
  g_return_if_fail (title != NULL);

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  /* Empty window titles not allowed, so set it to just a period. */
  if (!title[0])
    title = ".";

  GDK_NOTE (MISC, g_print ("gdk_surface_set_title: %p: %s\n",
			   GDK_SURFACE_HWND (surface), title));

  GDK_NOTE (MISC_OR_EVENTS, title = g_strdup_printf ("%p %s", GDK_SURFACE_HWND (surface), title));

  wtitle = g_utf8_to_utf16 (title, -1, NULL, NULL, NULL);
  API_CALL (SetWindowTextW, (GDK_SURFACE_HWND (surface), wtitle));
  g_free (wtitle);

  GDK_NOTE (MISC_OR_EVENTS, g_free ((char *) title));
}

static void
gdk_win32_surface_set_transient_for (GdkSurface *surface,
                                     GdkSurface *parent)
{
  HWND window_id, parent_id;
  LONG_PTR old_ptr;
  DWORD w32_error;
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);
  GdkWin32Surface *parent_impl = NULL;
  GSList *item;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  window_id = GDK_SURFACE_HWND (surface);
  parent_id = parent != NULL ? GDK_SURFACE_HWND (parent) : NULL;

  GDK_NOTE (MISC, g_print ("gdk_surface_set_transient_for: %p: %p\n", window_id, parent_id));

  if (GDK_SURFACE_DESTROYED (surface) || (parent && GDK_SURFACE_DESTROYED (parent)))
    {
      if (GDK_SURFACE_DESTROYED (surface))
	GDK_NOTE (MISC, g_print ("... destroyed!\n"));
      else
	GDK_NOTE (MISC, g_print ("... owner destroyed!\n"));

      return;
    }

  if (impl->transient_owner == parent)
    return;

  if (GDK_IS_SURFACE (impl->transient_owner))
    {
      GdkWin32Surface *trans_impl = GDK_WIN32_SURFACE (impl->transient_owner);
      item = g_slist_find (trans_impl->transient_children, surface);
      item->data = NULL;
      trans_impl->transient_children = g_slist_delete_link (trans_impl->transient_children, item);
      trans_impl->num_transients--;

      if (!trans_impl->num_transients)
        {
          trans_impl->transient_children = NULL;
        }

      g_object_unref (G_OBJECT (impl->transient_owner));
      g_object_unref (G_OBJECT (surface));

      impl->transient_owner = NULL;
    }

  if (parent)
    {
      parent_impl = GDK_WIN32_SURFACE (parent);

      parent_impl->transient_children = g_slist_append (parent_impl->transient_children, surface);
      g_object_ref (G_OBJECT (surface));
      parent_impl->num_transients++;
      impl->transient_owner = parent;
      g_object_ref (G_OBJECT (parent));
    }

  SetLastError (0);
  old_ptr = GetWindowLongPtr (window_id, GWLP_HWNDPARENT);
  w32_error = GetLastError ();

  /* Don't re-set GWLP_HWNDPARENT to the same value */
  if ((HWND) old_ptr == parent_id && w32_error == NO_ERROR)
    return;

  /* Don't return if it failed, try SetWindowLongPtr() anyway */
  if (old_ptr == 0 && w32_error != NO_ERROR)
    WIN32_API_FAILED ("GetWindowLongPtr");

  /* This changes the *owner* of the surface, despite the misleading
   * name. (Owner and parent are unrelated concepts.) At least that's
   * what people who seem to know what they talk about say on
   * USENET. Search on Google.
   */
  SetLastError (0);
  old_ptr = SetWindowLongPtr (window_id, GWLP_HWNDPARENT, (LONG_PTR) parent_id);
  w32_error = GetLastError ();

  if (old_ptr == 0 && w32_error != NO_ERROR)
    WIN32_API_FAILED ("SetWindowLongPtr");
}

#define MODAL_SURFACE_STACK(s) GDK_WIN32_DISPLAY (gdk_surface_get_display (s))->display_surface_record->modal_surface_stack

static void
gdk_win32_push_modal_surface (GdkSurface *surface)
{
  MODAL_SURFACE_STACK (surface) = g_slist_prepend (MODAL_SURFACE_STACK (surface), surface);
}

static void
gdk_win32_remove_modal_surface (GdkSurface *surface)
{
  GSList *tmp;

  g_return_if_fail (surface != NULL);

  /* It's possible to be NULL here if someone sets the modal hint of the surface
   * to FALSE before a modal surface stack has ever been created. */
  if (MODAL_SURFACE_STACK (surface) == NULL)
    return;

  /* Find the requested surface in the stack and remove it.  Yeah, I realize this
   * means we're not a 'real stack', strictly speaking.  Sue me. :) */
  tmp = g_slist_find (MODAL_SURFACE_STACK (surface), surface);
  if (tmp != NULL)
    {
      MODAL_SURFACE_STACK (surface) = g_slist_delete_link (MODAL_SURFACE_STACK (surface), tmp);
    }
}

gboolean
_gdk_modal_blocked (GdkSurface *surface)
{
  GSList *l;
  gboolean found_any = FALSE;

  for (l = MODAL_SURFACE_STACK (surface); l != NULL; l = l->next)
    {
      GdkSurface *modal = l->data;

      if (modal == surface)
	return FALSE;

      if (GDK_SURFACE_IS_MAPPED (modal))
	found_any = TRUE;
    }

  return found_any;
}

GdkSurface *
_gdk_modal_current (void)
{
  GSList *l;
  GdkDisplay *display = gdk_display_get_default ();

  for (l = GDK_WIN32_DISPLAY (display)->display_surface_record->modal_surface_stack;
       l != NULL;
       l = l->next)
    {
      GdkSurface *modal = l->data;

      if (GDK_SURFACE_IS_MAPPED (modal))
	return modal;
    }

  return NULL;
}

static void
gdk_win32_surface_get_geometry (GdkSurface *surface,
                                int        *x,
                                int        *y,
                                int        *width,
                                int        *height)
{
  if (!GDK_SURFACE_DESTROYED (surface))
    {
      RECT rect;
      GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

      if (GDK_IS_TOPLEVEL (surface) && impl->drag_move_resize_context.native_move_resize_pending)
         rect = impl->next_layout.configured_rect;
      else
        {
          POINT pt;
          GdkSurface *parent;

          if (GDK_IS_TOPLEVEL (surface))
            parent = NULL;
          else if (GDK_IS_POPUP (surface))
            parent = gdk_popup_get_parent (GDK_POPUP (surface));
          else
            parent = NULL;

          API_CALL (GetClientRect, (GDK_SURFACE_HWND (surface), &rect));

          pt.x = rect.left;
          pt.y = rect.top;
	      ClientToScreen (GDK_SURFACE_HWND (surface), &pt);
          if (parent)
            ScreenToClient (GDK_SURFACE_HWND (parent), &pt);

          rect.left = pt.x;
          rect.top = pt.y;

          pt.x = rect.right;
          pt.y = rect.bottom;
          ClientToScreen (GDK_SURFACE_HWND (surface), &pt);
          if (parent)
            ScreenToClient (GDK_SURFACE_HWND (parent), &pt);

          rect.right = pt.x;
          rect.bottom = pt.y;
        }

      if (x)
	*x = rect.left / impl->surface_scale;
      if (y)
	*y = rect.top / impl->surface_scale;
      if (width)
	*width = (rect.right - rect.left) / impl->surface_scale;
      if (height)
	*height = (rect.bottom - rect.top) / impl->surface_scale;

      GDK_NOTE (MISC, g_print ("gdk_win32_surface_get_geometry: %p: %ldx%ld@%+ld%+ld\n",
			       GDK_SURFACE_HWND (surface),
			       (rect.right - rect.left) / impl->surface_scale,
			       (rect.bottom - rect.top) / impl->surface_scale,
			       rect.left, rect.top));
    }
}

static void
gdk_win32_surface_get_root_coords (GdkSurface *surface,
                                   int         x,
                                   int         y,
                                   int        *root_x,
                                   int        *root_y)
{
  int tx;
  int ty;
  POINT pt;
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

  pt.x = x * impl->surface_scale;
  pt.y = y * impl->surface_scale;
  ClientToScreen (GDK_SURFACE_HWND (surface), &pt);
  tx = pt.x;
  ty = pt.y;

  if (root_x)
    *root_x = tx / impl->surface_scale;
  if (root_y)
    *root_y = ty / impl->surface_scale;

  GDK_NOTE (MISC, g_print ("gdk_win32_surface_get_root_coords: %p: %+d%+d %+d%+d\n",
			   GDK_SURFACE_HWND (surface),
			   x * impl->surface_scale,
			   y * impl->surface_scale,
			   tx / impl->surface_scale,
			   ty / impl->surface_scale));
}

static gboolean
gdk_surface_win32_get_device_state (GdkSurface      *surface,
                                    GdkDevice       *device,
                                    double          *x,
                                    double          *y,
                                    GdkModifierType *mask)
{
  _gdk_device_win32_query_state (device, surface, NULL, x, y, mask);

  return *x >= 0 && *y >= 0 && *x < surface->width && *y < surface->height;

}

static void
update_single_bit (LONG    *style,
                   gboolean all,
		   int      gdk_bit,
		   int      style_bit)
{
  /* all controls the interpretation of gdk_bit -- if all is TRUE,
   * gdk_bit indicates whether style_bit is off; if all is FALSE, gdk
   * bit indicate whether style_bit is on
   */
  if ((!all && gdk_bit) || (all && !gdk_bit))
    *style |= style_bit;
  else
    *style &= ~style_bit;
}

/*
 * Returns TRUE if surface has no decorations.
 * Usually it means CSD windows, because GTK
 * calls gdk_surface_set_decorations (surface, 0);
 */
gboolean
_gdk_win32_surface_lacks_wm_decorations (GdkSurface *surface)
{
  GdkWin32Surface *impl;
  LONG style;
  gboolean has_any_decorations;

  if (GDK_SURFACE_DESTROYED (surface))
    return FALSE;

  impl = GDK_WIN32_SURFACE (surface);

  /* This is because GTK calls gdk_surface_set_decorations (surface, 0),
   * even though GdkWMDecoration docs indicate that 0 does NOT mean
   * "no decorations".
   */
  if (!impl->decorate_all)
    return TRUE;

  if (GDK_SURFACE_HWND (surface) == 0)
    return FALSE;

  style = GetWindowLong (GDK_SURFACE_HWND (surface), GWL_STYLE);

  if (style == 0)
    {
      DWORD w32_error = GetLastError ();

      GDK_NOTE (MISC, g_print ("Failed to get style of surface %p (handle %p): %lu\n",
                               surface, GDK_SURFACE_HWND (surface), w32_error));
      return FALSE;
    }

  /* Keep this in sync with _gdk_win32_surface_update_style_bits() */
  /* We don't check what get_effective_surface_decorations()
   * has to say, because it gives suggestions based on
   * various hints, while we want *actual* decorations,
   * or their absence.
   */
  has_any_decorations = FALSE;

  if (style & (WS_BORDER | WS_THICKFRAME | WS_CAPTION |
               WS_SYSMENU | WS_MAXIMIZEBOX))
    has_any_decorations = TRUE;
  else
    GDK_NOTE (MISC, g_print ("Surface %p (handle %p): has no decorations (style %lx)\n",
                             surface, GDK_SURFACE_HWND (surface), style));

  return !has_any_decorations;
}

void
_gdk_win32_surface_update_style_bits (GdkSurface *surface)
{
  GdkWMDecoration decorations;
  LONG old_style, new_style, old_exstyle, new_exstyle;
  gboolean all;
  RECT rect, before, after;
  gboolean was_topmost;
  gboolean will_be_topmost;
  gboolean was_layered;
  gboolean will_be_layered;
  HWND insert_after;
  UINT flags;

  if (surface->state & GDK_TOPLEVEL_STATE_FULLSCREEN)
    return;

  old_style = GetWindowLong (GDK_SURFACE_HWND (surface), GWL_STYLE);
  old_exstyle = GetWindowLong (GDK_SURFACE_HWND (surface), GWL_EXSTYLE);

  GetClientRect (GDK_SURFACE_HWND (surface), &before);
  after = before;
  AdjustWindowRectEx (&before, old_style, FALSE, old_exstyle);

  was_topmost = (old_exstyle & WS_EX_TOPMOST) ? TRUE : FALSE;
  was_layered = (old_exstyle & WS_EX_LAYERED) ? TRUE : FALSE;
  will_be_topmost = was_topmost;
  will_be_layered = was_layered;

  old_exstyle &= ~WS_EX_TOPMOST;

  new_style = old_style;
  new_exstyle = old_exstyle;

  if (GDK_IS_DRAG_SURFACE (surface))
    {
      new_exstyle |= WS_EX_TOOLWINDOW;

      /* WS_EX_LAYERED | WS_EX_TRANSPARENT makes the drag surface behave
       * in pointer input passthrough mode, so it doesn't interfere with
       * the drag and drop operation.
       */
      new_exstyle |= WS_EX_LAYERED | WS_EX_TRANSPARENT;
      will_be_topmost = TRUE;
      will_be_layered = TRUE;
    }
  else
    {
      new_exstyle &= ~WS_EX_TOOLWINDOW;
    }

  if (get_effective_surface_decorations (surface, &decorations))
    {
      all = (decorations & GDK_DECOR_ALL);

      /* Keep this in sync with the test in _gdk_win32_surface_lacks_wm_decorations() */
      update_single_bit (&new_style, all, decorations & GDK_DECOR_BORDER, WS_BORDER);
      update_single_bit (&new_style, all, decorations & GDK_DECOR_RESIZEH, WS_THICKFRAME);
      update_single_bit (&new_style, all, decorations & GDK_DECOR_TITLE, WS_CAPTION);
      update_single_bit (&new_style, all, decorations & GDK_DECOR_MENU, WS_SYSMENU);
      update_single_bit (&new_style, all, decorations & GDK_DECOR_MAXIMIZE, WS_MAXIMIZEBOX);
    }

  if (old_style == new_style && old_exstyle == new_exstyle )
    {
      GDK_NOTE (MISC, g_print ("_gdk_win32_surface_update_style_bits: %p: no change\n",
			       GDK_SURFACE_HWND (surface)));
      return;
    }

  if (old_style != new_style)
    {
      GDK_NOTE (MISC, g_print ("_gdk_win32_surface_update_style_bits: %p: STYLE: %s => %s\n",
			       GDK_SURFACE_HWND (surface),
			       _gdk_win32_surface_style_to_string (old_style),
			       _gdk_win32_surface_style_to_string (new_style)));

      SetWindowLong (GDK_SURFACE_HWND (surface), GWL_STYLE, new_style);
    }

  if (old_exstyle != new_exstyle)
    {
      GDK_NOTE (MISC, g_print ("_gdk_win32_surface_update_style_bits: %p: EXSTYLE: %s => %s\n",
			       GDK_SURFACE_HWND (surface),
			       _gdk_win32_surface_exstyle_to_string (old_exstyle),
			       _gdk_win32_surface_exstyle_to_string (new_exstyle)));

      SetWindowLong (GDK_SURFACE_HWND (surface), GWL_EXSTYLE, new_exstyle);

      if (!was_layered && will_be_layered)
        {
          /* We have to call SetLayeredWindowAttributes when setting the
           * WS_EX_LAYERED style anew, otherwise the surface won't show up
           */
          API_CALL (SetLayeredWindowAttributes, (GDK_SURFACE_HWND (surface), 0, 255, LWA_ALPHA));
        }
    }

  AdjustWindowRectEx (&after, new_style, FALSE, new_exstyle);

  GetWindowRect (GDK_SURFACE_HWND (surface), &rect);
  rect.left += after.left - before.left;
  rect.top += after.top - before.top;
  rect.right += after.right - before.right;
  rect.bottom += after.bottom - before.bottom;

  flags = SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOREPOSITION;

  if (will_be_topmost && !was_topmost)
    {
      insert_after = HWND_TOPMOST;
    }
  else if (was_topmost && !will_be_topmost)
    {
      insert_after = HWND_NOTOPMOST;
    }
  else
    {
      flags |= SWP_NOZORDER;
      insert_after = SWP_NOZORDER_SPECIFIED;
    }

  SetWindowPos (GDK_SURFACE_HWND (surface), insert_after,
		rect.left, rect.top,
		rect.right - rect.left, rect.bottom - rect.top,
		flags);
}

/* Registers a dumb window class. This window
 * has DefWindowProc() for a window procedure and
 * does not do anything that GdkSurface-bound HWNDs do.
 */
static ATOM
RegisterGdkDumbClass ()
{
  static ATOM klassDUMB = 0;
  static WNDCLASSEXW wcl;
  ATOM klass = 0;

  wcl.cbSize = sizeof (WNDCLASSEX);
  wcl.style = 0; /* DON'T set CS_<H,V>REDRAW. It causes total redraw
                  * on WM_SIZE and WM_MOVE. Flicker, Performance!
                  */
  wcl.lpfnWndProc = DefWindowProcW;
  wcl.cbClsExtra = 0;
  wcl.cbWndExtra = 0;
  wcl.hInstance = this_module ();
  wcl.hIcon = 0;
  wcl.hIconSm = 0;
  wcl.lpszMenuName = NULL;
  wcl.hbrBackground = NULL;
  wcl.hCursor = LoadCursor (NULL, IDC_ARROW);
  wcl.style |= CS_OWNDC;
  wcl.lpszClassName = L"gdkSurfaceDumb";

  if (klassDUMB == 0)
    klassDUMB = RegisterClassExW (&wcl);

  klass = klassDUMB;

  if (klass == 0)
    {
      WIN32_API_FAILED ("RegisterClassExW");
      g_error ("That is a fatal error");
    }

  return klass;
}

static const char *
get_cursor_name_from_op (GdkW32WindowDragOp op,
                         GdkSurfaceEdge      edge)
{
  switch (op)
    {
    case GDK_WIN32_DRAGOP_MOVE:
      return "move";
    case GDK_WIN32_DRAGOP_RESIZE:
      switch (edge)
        {
        case GDK_SURFACE_EDGE_NORTH_WEST:
          return "nw-resize";
        case GDK_SURFACE_EDGE_NORTH:
          return "n-resize";
        case GDK_SURFACE_EDGE_NORTH_EAST:
          return "ne-resize";
        case GDK_SURFACE_EDGE_WEST:
          return "w-resize";
        case GDK_SURFACE_EDGE_EAST:
          return "e-resize";
        case GDK_SURFACE_EDGE_SOUTH_WEST:
          return "sw-resize";
        case GDK_SURFACE_EDGE_SOUTH:
          return "s-resize";
        case GDK_SURFACE_EDGE_SOUTH_EAST:
          return "se-resize";
        }
      /* default: warn about unhandled enum values,
       * fallthrough to GDK_WIN32_DRAGOP_NONE case
       */
    case GDK_WIN32_DRAGOP_COUNT:
      g_assert_not_reached ();
    case GDK_WIN32_DRAGOP_NONE:
      return "default";
    /* default: warn about unhandled enum values */
    }

  g_assert_not_reached ();

  return NULL;
}

static void
setup_drag_move_resize_context (GdkSurface                  *surface,
                                GdkW32DragMoveResizeContext *context,
                                GdkW32WindowDragOp           op,
                                GdkSurfaceEdge               edge,
                                GdkDevice                   *device,
                                int                          button,
                                double                       x,
                                double                       y,
                                guint32                      timestamp)
{
  RECT rect;
  const char *cursor_name;
  GdkSurface *pointer_surface;
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);
  gboolean maximized = gdk_toplevel_get_state (GDK_TOPLEVEL (surface)) & GDK_TOPLEVEL_STATE_MAXIMIZED;
  int root_x, root_y;
  gboolean restore_configure = FALSE;

  gdk_win32_surface_get_root_coords (surface, x, y, &root_x, &root_y);

  /* Before we drag, we need to undo any maximization */
  if (maximized)
    {
      GdkMonitor *monitor;
      int wx, wy, wwidth, wheight;
      int swx, swy, swwidth, swheight;
      gboolean pointer_outside_of_surface;
      int offsetx, offsety;
      gboolean left_half;
      GdkDisplay *display;

      restore_configure = TRUE;
      display = gdk_surface_get_display (surface);
      monitor = gdk_display_get_monitor_at_surface (display, surface);
      gdk_surface_get_geometry (surface, &wx, &wy, &wwidth, &wheight);

      swx = wx;
      swy = wy;
      swwidth = wwidth;
      swheight = wheight;

      /* Subtract surface shadow. We don't want pointer to go outside of
       * the visible surface during drag-move. For drag-resize it's OK.
       * Don't take shadow into account if the surface is maximized -
       * maximized surfaces don't have shadows.
       */
      if (op == GDK_WIN32_DRAGOP_MOVE && !maximized)
        {
          swx += impl->shadow.left / impl->surface_scale;
          swy += impl->shadow.top / impl->surface_scale;
          swwidth -= impl->shadow_x;
          swheight -= impl->shadow_y;
        }

      pointer_outside_of_surface = root_x < swx || root_x > swx + swwidth ||
                                  root_y < swy || root_y > swy + swheight;
      /* Calculate the offset of the pointer relative to the surface */
      offsetx = root_x - swx;
      offsety = root_y - swy;

      /* Figure out in which half of the surface the pointer is.
       * The code currently only concerns itself with horizontal
       * dimension (left/right halves).
       * There's no upper/lower half, because usually surface
       * is dragged by its upper half anyway. If that changes, adjust
       * accordingly.
       */
      left_half = (offsetx < swwidth / 2);

      /* Inverse the offset for it to be from the right edge */
      if (!left_half)
        offsetx = swwidth - offsetx;

      GDK_NOTE (MISC, g_print ("Pointer at %d : %d, this is %d : %d relative to the surface's %s\n",
                               root_x, root_y, offsetx, offsety,
                               left_half ? "left half" : "right half"));

      /* Move surface in such a way that on unmaximization the pointer
       * is still pointing at the appropriate half of the surface,
       * with the same offset from the left or right edge. If the new
       * surface size is too small, and adding that offset puts the pointer
       * into the other half or even beyond, move the pointer to the middle.
       */
      if (!pointer_outside_of_surface && maximized)
        {
          WINDOWPLACEMENT placement;
          int unmax_width, unmax_height;
          int shadow_unmax_width, shadow_unmax_height;

          placement.length = sizeof (placement);
          API_CALL (GetWindowPlacement, (GDK_SURFACE_HWND (surface), &placement));

          GDK_NOTE (MISC, g_print ("W32 WM unmaximized surface placement is %ld x %ld @ %ld : %ld\n",
                                   placement.rcNormalPosition.right - placement.rcNormalPosition.left,
                                   placement.rcNormalPosition.bottom - placement.rcNormalPosition.top,
                                   placement.rcNormalPosition.left,
                                   placement.rcNormalPosition.top));

          unmax_width = placement.rcNormalPosition.right - placement.rcNormalPosition.left;
          unmax_height = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top;

          shadow_unmax_width = unmax_width - impl->shadow_x * impl->surface_scale;
          shadow_unmax_height = unmax_height - impl->shadow_y * impl->surface_scale;

          if (offsetx * impl->surface_scale < (shadow_unmax_width / 2) &&
              offsety * impl->surface_scale < (shadow_unmax_height / 2))
            {
              placement.rcNormalPosition.top = (root_y - offsety + impl->shadow.top) * impl->surface_scale;
              placement.rcNormalPosition.bottom = placement.rcNormalPosition.top + unmax_height;

              if (left_half)
                {
                  placement.rcNormalPosition.left = (root_x - offsetx + impl->shadow.left) * impl->surface_scale;
                  placement.rcNormalPosition.right = placement.rcNormalPosition.left + unmax_width;
                }
              else
                {
                  placement.rcNormalPosition.right = (root_x + offsetx + impl->shadow.right) * impl->surface_scale;
                  placement.rcNormalPosition.left = placement.rcNormalPosition.right - unmax_width;
                }
            }
          else
            {
              placement.rcNormalPosition.left = root_x * impl->surface_scale - unmax_width / 2;

              if (offsety * impl->surface_scale < shadow_unmax_height / 2)
                placement.rcNormalPosition.top = (root_y - offsety + impl->shadow.top) * impl->surface_scale;
              else
                placement.rcNormalPosition.top = root_y * impl->surface_scale - unmax_height / 2;

              placement.rcNormalPosition.right = placement.rcNormalPosition.left + unmax_width;
              placement.rcNormalPosition.bottom = placement.rcNormalPosition.top + unmax_height;
            }

          GDK_NOTE (MISC, g_print ("Unmaximized HWND will be at %ld : %ld\n",
                                   placement.rcNormalPosition.left,
                                   placement.rcNormalPosition.top));

          API_CALL (SetWindowPlacement, (GDK_SURFACE_HWND (surface), &placement));
        }

      if (maximized)
        gdk_win32_surface_unmaximize (surface);

      if (pointer_outside_of_surface)
        {
          /* Pointer outside of the surface, move pointer into surface */
          GDK_NOTE (MISC, g_print ("Pointer at %d : %d is outside of %d x %d @ %d : %d, move it to %d : %d\n",
                                   root_x, root_y, wwidth, wheight, wx, wy, wx + wwidth / 2, wy + wheight / 2));
          root_x = wx + wwidth / 2;
          /* This is Gnome behaviour. Windows WM would put the pointer
           * in the middle of the titlebar, but GDK doesn't know where
           * the titlebar is, if any.
           */
          root_y = wy + wheight / 2;
          SetCursorPos (root_x, root_y);
        }
    }

  if (restore_configure)
    impl->inhibit_configure = FALSE;

  gdk_win32_get_surface_hwnd_rect (surface, &rect);

  cursor_name = get_cursor_name_from_op (op, edge);

  context->cursor = gdk_cursor_new_from_name (cursor_name, NULL);

  pointer_surface = surface;

  /* Note: This triggers a WM_CAPTURECHANGED, which will trigger
   * gdk_win32_surface_end_move_resize_drag(), which will end
   * our op before it even begins, but only if context->op is not NONE.
   * This is why we first do the grab, *then* set the op.
   */
  gdk_device_grab (device, pointer_surface,
                   FALSE,
                   GDK_ALL_EVENTS_MASK,
                   context->cursor,
                   timestamp);

  context->surface = g_object_ref (surface);
  context->op = op;
  context->edge = edge;
  context->device = device;
  context->button = button;
  context->start_root_x = root_x;
  context->start_root_y = root_y;
  context->current_root_x = root_x;
  context->current_root_y = root_y;
  context->timestamp = timestamp;
  context->start_rect = rect;

  GDK_NOTE (EVENTS,
            g_print ("begin drag moveresize: surface %p, toplevel %p, "
                     "op %u, edge %d, device %p, "
                     "button %d, coord %d:%d, time %u\n",
                     pointer_surface, surface,
                     context->op, context->edge, context->device,
                     context->button, context->start_root_x,
                     context->start_root_y, context->timestamp));
}

void
gdk_win32_surface_end_move_resize_drag (GdkSurface *surface)
{
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);
  GdkW32DragMoveResizeContext *context = &impl->drag_move_resize_context;
  
  if (context->op == GDK_WIN32_DRAGOP_RESIZE)
    _gdk_win32_surface_invalidate_egl_framebuffer (surface);

  context->op = GDK_WIN32_DRAGOP_NONE;

  gdk_device_ungrab (context->device, GDK_CURRENT_TIME);

  g_clear_object (&context->cursor);

  context->revealed = FALSE;

  if (context->timer)
    {
      g_source_remove (context->timer);
      context->timer = 0;
    }

  g_clear_object (&context->surface);

  GDK_NOTE (EVENTS,
            g_print ("end drag moveresize: surface %p, toplevel %p,"
                     "op %u, edge %d, device %p, "
                     "button %d, coord %d:%d, time %u\n",
                     surface, surface,
                     context->op, context->edge, context->device,
                     context->button, context->start_root_x,
                     context->start_root_y, context->timestamp));
}

static void
gdk_win32_get_window_size_and_position_from_client_rect (GdkSurface *surface,
                                                         RECT       *hwnd_rect,
                                                         SIZE       *hwnd_size,
                                                         POINT      *hwnd_position)
{
  /* Turn client area into HWND area */
  _gdk_win32_adjust_client_rect (surface, hwnd_rect);

  hwnd_position->x = hwnd_rect->left;
  hwnd_position->y = hwnd_rect->top;
  hwnd_size->cx = hwnd_rect->right - hwnd_rect->left;
  hwnd_size->cy = hwnd_rect->bottom - hwnd_rect->top;
}

void
gdk_win32_surface_do_move_resize_drag (GdkSurface *surface,
                                       int         x,
                                       int         y)
{
  RECT rect;
  RECT new_rect;
  int diffy, diffx;
  MINMAXINFO mmi;
  GdkWin32Surface *impl;
  GdkW32DragMoveResizeContext *context;
  int width;
  int height;

  impl = GDK_WIN32_SURFACE (surface);
  context = &impl->drag_move_resize_context;

  if (!gdk_win32_get_surface_hwnd_rect (surface, &rect))
    return;

  x /= impl->surface_scale;
  y /= impl->surface_scale;

  if (context->current_root_x == x &&
      context->current_root_y == y)
    return;

  context->current_root_x = x;
  context->current_root_y = y;

  new_rect = context->start_rect;
  diffx = (x - context->start_root_x) * impl->surface_scale;
  diffy = (y - context->start_root_y) * impl->surface_scale;

  switch (context->op)
    {
    case GDK_WIN32_DRAGOP_RESIZE:

      switch (context->edge)
        {
        case GDK_SURFACE_EDGE_NORTH_WEST:
          new_rect.left += diffx;
          new_rect.top += diffy;
          break;

        case GDK_SURFACE_EDGE_NORTH:
          new_rect.top += diffy;
          break;

        case GDK_SURFACE_EDGE_NORTH_EAST:
          new_rect.right += diffx;
          new_rect.top += diffy;
          break;

        case GDK_SURFACE_EDGE_WEST:
          new_rect.left += diffx;
          break;

        case GDK_SURFACE_EDGE_EAST:
          new_rect.right += diffx;
          break;

        case GDK_SURFACE_EDGE_SOUTH_WEST:
          new_rect.left += diffx;
          new_rect.bottom += diffy;
          break;

        case GDK_SURFACE_EDGE_SOUTH:
          new_rect.bottom += diffy;
          break;

        case GDK_SURFACE_EDGE_SOUTH_EAST:
        default:
          new_rect.right += diffx;
          new_rect.bottom += diffy;
          break;
        }

      /* When handling WM_GETMINMAXINFO, mmi is already populated
       * by W32 WM and we apply our stuff on top of that.
       * Here it isn't, so we should at least clear it.
       */
      memset (&mmi, 0, sizeof (mmi));

      if (!_gdk_win32_surface_fill_min_max_info (surface, &mmi))
        break;

      width = new_rect.right - new_rect.left;
      height = new_rect.bottom - new_rect.top;

      if (width > mmi.ptMaxTrackSize.x)
        {
          switch (context->edge)
            {
            case GDK_SURFACE_EDGE_NORTH_WEST:
            case GDK_SURFACE_EDGE_WEST:
            case GDK_SURFACE_EDGE_SOUTH_WEST:
              new_rect.left = new_rect.right - mmi.ptMaxTrackSize.x;
              break;

            case GDK_SURFACE_EDGE_NORTH_EAST:
            case GDK_SURFACE_EDGE_EAST:
            case GDK_SURFACE_EDGE_SOUTH_EAST:
            default:
              new_rect.right = new_rect.left + mmi.ptMaxTrackSize.x;
              break;
            }
        }
      else if (width < mmi.ptMinTrackSize.x)
        {
          switch (context->edge)
            {
            case GDK_SURFACE_EDGE_NORTH_WEST:
            case GDK_SURFACE_EDGE_WEST:
            case GDK_SURFACE_EDGE_SOUTH_WEST:
              new_rect.left = new_rect.right - mmi.ptMinTrackSize.x;
              break;

            case GDK_SURFACE_EDGE_NORTH_EAST:
            case GDK_SURFACE_EDGE_EAST:
            case GDK_SURFACE_EDGE_SOUTH_EAST:
            default:
              new_rect.right = new_rect.left + mmi.ptMinTrackSize.x;
              break;
            }
        }

      if (height > mmi.ptMaxTrackSize.y)
        {
          switch (context->edge)
            {
            case GDK_SURFACE_EDGE_NORTH_WEST:
            case GDK_SURFACE_EDGE_NORTH:
            case GDK_SURFACE_EDGE_NORTH_EAST:
              new_rect.top = new_rect.bottom - mmi.ptMaxTrackSize.y;

            case GDK_SURFACE_EDGE_SOUTH_WEST:
            case GDK_SURFACE_EDGE_SOUTH:
            case GDK_SURFACE_EDGE_SOUTH_EAST:
            default:
              new_rect.bottom = new_rect.top + mmi.ptMaxTrackSize.y;
              break;
            }
        }
      else if (height < mmi.ptMinTrackSize.y)
        {
          switch (context->edge)
            {
            case GDK_SURFACE_EDGE_NORTH_WEST:
            case GDK_SURFACE_EDGE_NORTH:
            case GDK_SURFACE_EDGE_NORTH_EAST:
              new_rect.top = new_rect.bottom - mmi.ptMinTrackSize.y;

            case GDK_SURFACE_EDGE_SOUTH_WEST:
            case GDK_SURFACE_EDGE_SOUTH:
            case GDK_SURFACE_EDGE_SOUTH_EAST:
            default:
              new_rect.bottom = new_rect.top + mmi.ptMinTrackSize.y;
              break;
            }
        }

      break;
    case GDK_WIN32_DRAGOP_MOVE:
      new_rect.left += diffx;
      new_rect.top += diffy;
      new_rect.right += diffx;
      new_rect.bottom += diffy;
      break;
    default:
      break;
    }

  if (context->op == GDK_WIN32_DRAGOP_RESIZE &&
      (rect.left != new_rect.left ||
       rect.right != new_rect.right ||
       rect.top != new_rect.top ||
       rect.bottom != new_rect.bottom))
    {
      if (GDK_IS_TOPLEVEL (surface))
        {
          int scale = impl->surface_scale;

          impl->next_layout.configured_rect = new_rect;
          impl->next_layout.configured_width = (new_rect.right - new_rect.left + scale - 1) / scale;
          impl->next_layout.configured_height = (new_rect.bottom - new_rect.top + scale - 1) / scale;
        }

      context->native_move_resize_pending = TRUE;
    }
  else if (context->op == GDK_WIN32_DRAGOP_MOVE &&
           (rect.left != new_rect.left ||
            rect.top != new_rect.top))
    {
      SIZE hwnd_size;
      POINT hwnd_position;

      context->native_move_resize_pending = FALSE;

      gdk_surface_request_layout (surface);

      gdk_win32_get_window_size_and_position_from_client_rect (surface,
                                                              &new_rect,
                                                              &hwnd_size,
                                                              &hwnd_position);

      API_CALL (SetWindowPos, (GDK_SURFACE_HWND (surface),
                               SWP_NOZORDER_SPECIFIED,
                               hwnd_position.x, hwnd_position.y,
                               0, 0,
                               SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOSIZE));
    }

  gdk_surface_request_layout (surface);
}

static void
gdk_win32_toplevel_begin_resize (GdkToplevel    *toplevel,
                                 GdkSurfaceEdge  edge,
                                 GdkDevice      *device,
                                 int             button,
                                 double          x,
                                 double          y,
                                 guint32         timestamp)
{
  GdkSurface *surface = GDK_SURFACE (toplevel);
  GdkWin32Surface *impl;

  if (GDK_SURFACE_DESTROYED (surface) ||
      IsIconic (GDK_SURFACE_HWND (surface)))
    return;

  /* Tell Windows to start interactively resizing the surface by pretending that
   * the left pointer button was clicked in the suitable edge or corner. This
   * will only work if the button is down when this function is called, and
   * will only work with button 1 (left), since Windows only allows surface
   * dragging using the left mouse button.
   */

  if (button != 1)
    return;

  impl = GDK_WIN32_SURFACE (surface);

  if (impl->drag_move_resize_context.op != GDK_WIN32_DRAGOP_NONE)
    gdk_win32_surface_end_move_resize_drag (surface);

  setup_drag_move_resize_context (surface, &impl->drag_move_resize_context,
                                  GDK_WIN32_DRAGOP_RESIZE, edge, device,
                                  button, x, y, timestamp);
}

static void
gdk_win32_toplevel_begin_move (GdkToplevel *toplevel,
                               GdkDevice   *device,
                               int          button,
                               double       x,
                               double       y,
                               guint32      timestamp)
{
  GdkSurface *surface = GDK_SURFACE (toplevel);
  GdkWin32Surface *impl;

  if (GDK_SURFACE_DESTROYED (surface) ||
      IsIconic (GDK_SURFACE_HWND (surface)))
    return;

  /* Tell Windows to start interactively moving the surface by pretending that
   * the left pointer button was clicked in the titlebar. This will only work
   * if the button is down when this function is called, and will only work
   * with button 1 (left), since Windows only allows surface dragging using the
   * left mouse button.
   */
  if (button != 1)
    return;

  impl = GDK_WIN32_SURFACE (surface);

  if (impl->drag_move_resize_context.op != GDK_WIN32_DRAGOP_NONE)
    gdk_win32_surface_end_move_resize_drag (surface);

  setup_drag_move_resize_context (surface, &impl->drag_move_resize_context,
                                  GDK_WIN32_DRAGOP_MOVE, GDK_SURFACE_EDGE_NORTH_WEST,
                                  device, button, x, y, timestamp);
}


/*
 * Setting surface states
 */
static void
gdk_win32_surface_minimize (GdkSurface *surface)
{
  HWND old_active_hwnd;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  GDK_NOTE (MISC, g_print ("gdk_surface_minimize: %p: %s\n",
			   GDK_SURFACE_HWND (surface),
			   _gdk_win32_surface_state_to_string (surface->state)));

  if (GDK_SURFACE_IS_MAPPED (surface))
    {
      old_active_hwnd = GetActiveWindow ();
      GtkShowSurfaceHWND (surface, SW_MINIMIZE);
      if (old_active_hwnd != GDK_SURFACE_HWND (surface))
	SetActiveWindow (old_active_hwnd);
    }
  else
    {
      gdk_synthesize_surface_state (surface,
                                    0,
                                    GDK_TOPLEVEL_STATE_MINIMIZED);
    }
}

static void
gdk_win32_surface_maximize (GdkSurface *surface)
{
  GdkWin32Surface *impl;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  GDK_NOTE (MISC, g_print ("gdk_surface_maximize: %p: %s\n",
			   GDK_SURFACE_HWND (surface),
			   _gdk_win32_surface_state_to_string (surface->state)));

  impl = GDK_WIN32_SURFACE (surface);
  impl->inhibit_configure = TRUE;
  impl->force_recompute_size = FALSE;

  if (GDK_SURFACE_IS_MAPPED (surface))
    GtkShowSurfaceHWND (surface, SW_MAXIMIZE);
  else
    gdk_synthesize_surface_state (surface,
				 0,
				 GDK_TOPLEVEL_STATE_MAXIMIZED);
}

static void
gdk_win32_surface_unmaximize (GdkSurface *surface)
{
  GdkWin32Surface *impl;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  GDK_NOTE (MISC, g_print ("gdk_surface_unmaximize: %p: %s\n",
			   GDK_SURFACE_HWND (surface),
			   _gdk_win32_surface_state_to_string (surface->state)));

  _gdk_win32_surface_invalidate_egl_framebuffer (surface);

  if (GDK_SURFACE_IS_MAPPED (surface))
    GtkShowSurfaceHWND (surface, SW_RESTORE);
  else
    gdk_synthesize_surface_state (surface,
				 GDK_TOPLEVEL_STATE_MAXIMIZED,
				 0);

  impl = GDK_WIN32_SURFACE (surface);

  if (impl->inhibit_configure)
    {
      impl->inhibit_configure = FALSE;
      impl->force_recompute_size = TRUE;
    }
}

static void
gdk_win32_surface_fullscreen (GdkSurface *surface,
                              GdkMonitor *monitor)
{
  int x, y, width, height;
  FullscreenInfo *fi;
  HMONITOR hmonitor =  NULL;
  MONITORINFO mi;
  HWND surface_hwnd;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  surface_hwnd = GDK_SURFACE_HWND (surface);
  fi = g_new (FullscreenInfo, 1);

  if (!GetWindowRect (surface_hwnd, &(fi->r)))
    g_free (fi);
  else
    {
      GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

      if (monitor && GDK_IS_WIN32_MONITOR (monitor))
        hmonitor = GDK_WIN32_MONITOR (monitor)->hmonitor;

      if (!hmonitor)
        hmonitor = MonitorFromWindow (surface_hwnd, MONITOR_DEFAULTTONEAREST);

      mi.cbSize = sizeof (mi);
      if (hmonitor && GetMonitorInfo (hmonitor, &mi))
	{
	  x = mi.rcMonitor.left;
	  y = mi.rcMonitor.top;
	  width = mi.rcMonitor.right - x;
	  height = mi.rcMonitor.bottom - y;
	}
      else
	{
	  x = y = 0;
	  width = GetSystemMetrics (SM_CXSCREEN);
	  height = GetSystemMetrics (SM_CYSCREEN);
	}

      /* remember for restoring */
      fi->hint_flags = impl->hint_flags;
      impl->hint_flags &= ~GDK_HINT_MAX_SIZE;
      g_object_set_data (G_OBJECT (surface), "fullscreen-info", fi);
      fi->style = GetWindowLong (surface_hwnd, GWL_STYLE);

      impl->inhibit_configure = TRUE;
      impl->force_recompute_size = FALSE;

      /* Send state change before configure event */
      gdk_synthesize_surface_state (surface, 0, GDK_TOPLEVEL_STATE_FULLSCREEN);

      SetWindowLong (surface_hwnd, GWL_STYLE,
                     (fi->style & ~WS_OVERLAPPEDWINDOW) | WS_POPUP);

      API_CALL (SetWindowPos, (surface_hwnd, HWND_TOP,
                x, y, width, height,
                SWP_NOCOPYBITS | SWP_SHOWWINDOW | SWP_FRAMECHANGED));
    }
}

static void
gdk_win32_surface_unfullscreen (GdkSurface *surface)
{
  FullscreenInfo *fi;

  g_return_if_fail (GDK_IS_SURFACE (surface));

  fi = g_object_get_data (G_OBJECT (surface), "fullscreen-info");
  if (fi)
    {
      GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

      gdk_synthesize_surface_state (surface, GDK_TOPLEVEL_STATE_FULLSCREEN, 0);

      impl->hint_flags = fi->hint_flags;
      SetWindowLong (GDK_SURFACE_HWND (surface), GWL_STYLE, fi->style);
      _gdk_win32_surface_invalidate_egl_framebuffer (surface);
      API_CALL (SetWindowPos, (GDK_SURFACE_HWND (surface), HWND_NOTOPMOST,
			       fi->r.left, fi->r.top,
			       fi->r.right - fi->r.left, fi->r.bottom - fi->r.top,
			       SWP_NOCOPYBITS | SWP_SHOWWINDOW | SWP_FRAMECHANGED));

      g_object_set_data (G_OBJECT (surface), "fullscreen-info", NULL);
      g_free (fi);
      _gdk_win32_surface_update_style_bits (surface);

      if (impl->inhibit_configure)
        {
          impl->inhibit_configure = FALSE;
          impl->force_recompute_size = TRUE;
        }
    }
}

static void
gdk_win32_surface_focus (GdkSurface *surface,
                         guint32     timestamp)
{
  g_return_if_fail (GDK_IS_SURFACE (surface));

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  GDK_NOTE (MISC, g_print ("gdk_surface_focus: %p: %s\n",
			   GDK_SURFACE_HWND (surface),
			   _gdk_win32_surface_state_to_string (surface->state)));

  if (surface->state & GDK_TOPLEVEL_STATE_MAXIMIZED)
    GtkShowSurfaceHWND (surface, SW_SHOWMAXIMIZED);
  else if (surface->state & GDK_TOPLEVEL_STATE_MINIMIZED)
    GtkShowSurfaceHWND (surface, SW_RESTORE);
  else if (!IsWindowVisible (GDK_SURFACE_HWND (surface)))
    GtkShowSurfaceHWND (surface, SW_SHOWNORMAL);
  else
    GtkShowSurfaceHWND (surface, SW_SHOW);

  SetFocus (GDK_SURFACE_HWND (surface));
}

/**
 * gdk_win32_surface_lookup_for_display:
 * @display: a %GdkDisplay
 * @anid: a HWND window handle
 *
 * Returns: (nullable): the %GdkSurface associated with the given @anid, or %NULL.
 */
GdkSurface *
gdk_win32_surface_lookup_for_display (GdkDisplay *display,
                                      HWND        anid)
{
  g_return_val_if_fail (display == gdk_display_get_default (), NULL);

  return (GdkSurface*) gdk_win32_display_handle_table_lookup_ (display, anid);
}

/**
 * gdk_win32_surface_is_win32:
 * @surface: a `GdkSurface`
 *
 * Returns: %TRUE if the @surface is a win32 implemented surface.
 *
 * Deprecated: 4.8: Use `GDK_IS_WIN32_SURFACE` instead.
 */
gboolean
gdk_win32_surface_is_win32 (GdkSurface *surface)
{
  return GDK_IS_WIN32_SURFACE (surface);
}

static gboolean
gdk_win32_surface_show_window_menu (GdkSurface *surface,
                                    GdkEvent   *event)
{
  double event_x, event_y;
  int x, y;
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);
  GdkEventType event_type = gdk_event_get_event_type (event);

  switch ((int) event_type)
    {
    case GDK_BUTTON_PRESS:
    case GDK_BUTTON_RELEASE:
    case GDK_TOUCH_BEGIN:
    case GDK_TOUCH_END:
      break;
    default:
      return FALSE;
    }

  gdk_event_get_position (event, &event_x, &event_y);
  gdk_win32_surface_get_root_coords (surface, event_x, event_y, &x, &y);

  SendMessage (GDK_SURFACE_HWND (surface),
               WM_SYSMENU,
               0,
               MAKELPARAM (x * impl->surface_scale, y * impl->surface_scale));

  return TRUE;
}

/**
 * gdk_win32_surface_get_impl_hwnd:
 * @surface: a `GdkSurface`
 *
 * Returns: the associated @surface HWND handle.
 *
 * Deprecated: 4.8: Use gdk_win32_surface_get_handle() instead.
 */
HWND
gdk_win32_surface_get_impl_hwnd (GdkSurface *surface)
{
  if (GDK_IS_WIN32_SURFACE (surface))
    return GDK_SURFACE_HWND (surface);
  return NULL;
}

BOOL WINAPI
GtkShowSurfaceHWND (GdkSurface *surface,
                    int         cmd_show)
{
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

  /* Ensure that maximized surface HWND size is corrected later on */
  if (cmd_show == SW_MAXIMIZE)
    impl->maximizing = TRUE;

  return ShowWindow (GDK_SURFACE_HWND (surface), cmd_show);
}

static void
gdk_win32_surface_set_shadow_width (GdkSurface *surface,
                                    int         left,
                                    int         right,
                                    int         top,
                                    int         bottom)
{
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  GDK_NOTE (MISC, g_print ("gdk_win32_surface_set_shadow_width: surface %p, "
                           "left %d, top %d, right %d, bottom %d\n",
                           surface, left, top, right, bottom));

  impl->zero_shadow = left == 0 && right == 0 && top == 0 && bottom == 0;

  if (impl->zero_shadow)
    return;

  impl->shadow.left = left * impl->surface_scale;;
  impl->shadow.right = right * impl->surface_scale;
  impl->shadow.top = top * impl->surface_scale;;
  impl->shadow.bottom = bottom * impl->surface_scale;
  impl->shadow_x = left + right;
  impl->shadow_y = top + bottom;
}

static void
gdk_win32_surface_set_icon_list (GdkSurface *surface,
                                 GList      *textures)
{
  GdkTexture *texture, *big_texture, *small_texture;
  gint big_diff, small_diff;
  gint big_w, big_h, small_w, small_h;
  gint w, h;
  gint dw, dh, diff;
  HICON small_hicon, big_hicon;
  GdkWin32Surface *impl;

  if (GDK_SURFACE_DESTROYED (surface))
    return;

  impl = GDK_WIN32_SURFACE (surface);

  /* ideal sizes for small and large icons */
  big_w = GetSystemMetrics (SM_CXICON);
  big_h = GetSystemMetrics (SM_CYICON);
  small_w = GetSystemMetrics (SM_CXSMICON);
  small_h = GetSystemMetrics (SM_CYSMICON);

  /* find closest sized icons in the list */
  big_texture = NULL;
  small_texture = NULL;
  big_diff = 0;
  small_diff = 0;
  while (textures)
    {
      texture = (GdkTexture*) textures->data;
      w = gdk_texture_get_width (texture);
      h = gdk_texture_get_height (texture);

      dw = ABS (w - big_w);
      dh = ABS (h - big_h);
      diff = dw*dw + dh*dh;
      if (big_texture == NULL || diff < big_diff)
        {
          big_texture = texture;
          big_diff = diff;
        }

      dw = ABS (w - small_w);
      dh = ABS (h - small_h);
      diff = dw*dw + dh*dh;
      if (small_texture == NULL || diff < small_diff)
        {
          small_texture = texture;
          small_diff = diff;
        }

      textures = textures->next;
    }

  if (big_texture == NULL || small_texture == NULL)
    return;

  /* Create the icons */
  big_hicon = big_texture ? _gdk_win32_create_hicon_for_texture (big_texture, TRUE, 0, 0) : NULL;
  small_hicon = small_texture ? _gdk_win32_create_hicon_for_texture (small_texture, TRUE, 0, 0) : NULL;

  /* Set the icons */
  SendMessage (GDK_SURFACE_HWND (surface), WM_SETICON, ICON_BIG,
               (LPARAM)big_hicon);
  SendMessage (GDK_SURFACE_HWND (surface), WM_SETICON, ICON_SMALL,
               (LPARAM)small_hicon);

  /* Store the icons, destroying any previous icons */
  if (impl->hicon_big)
    GDI_CALL (DestroyIcon, (impl->hicon_big));
  impl->hicon_big = big_hicon;
  if (impl->hicon_small)
    GDI_CALL (DestroyIcon, (impl->hicon_small));
  impl->hicon_small = small_hicon;
}

double
_gdk_win32_surface_get_scale (GdkSurface *surface)
{
  GdkDisplay *display;
  GdkWin32Surface *impl;
  GdkWin32Display *win32_display;

  g_return_val_if_fail (surface != NULL, 1);

  display = gdk_surface_get_display (surface);
  impl = GDK_WIN32_SURFACE (surface);

  win32_display = GDK_WIN32_DISPLAY (display);

  if (win32_display->dpi_aware_type != PROCESS_DPI_UNAWARE)
    {
      if (win32_display->has_fixed_scale)
        impl->surface_scale = win32_display->surface_scale;
      else
        impl->surface_scale = gdk_win32_display_get_monitor_scale_factor (win32_display,
                                                                          surface,
                                                                          NULL);

      return impl->surface_scale;
    }
  else
    {
      if (win32_display->has_fixed_scale)
        {
          static gsize hidpi_msg_displayed = 0;

          if (g_once_init_enter (&hidpi_msg_displayed))
            {
              g_message ("Note: GDK_SCALE is ignored as HiDPI awareness is disabled.");
              g_once_init_leave (&hidpi_msg_displayed, 1);
            }
        }

      /* Application is not DPI aware, don't bother */
      return 1;
    }
}

static void
gdk_win32_surface_set_input_region (GdkSurface     *surface,
                                    cairo_region_t *input_region)
{
  /* Input region support is implemented by handling the
   * WM_NCHITTEST message. */
}

static void
compute_toplevel_size (GdkSurface *surface,
                       gboolean    update_geometry,
                       int        *width,
                       int        *height)
{
  GdkDisplay *display = gdk_surface_get_display (surface);
  GdkMonitor *monitor;
  GdkToplevelSize size;
  int bounds_width, bounds_height;
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

  monitor = gdk_display_get_monitor_at_surface (display, surface);
  if (monitor)
    {
      GdkRectangle workarea;

      gdk_win32_monitor_get_workarea (monitor, &workarea);
      bounds_width = workarea.width;
      bounds_height = workarea.height;
    }
  else
    {
      bounds_width = G_MAXINT;
      bounds_height = G_MAXINT;
    }

  gdk_toplevel_size_init (&size, bounds_width, bounds_height);
  gdk_toplevel_notify_compute_size (GDK_TOPLEVEL (surface), &size);
  g_warn_if_fail (size.width > 0);
  g_warn_if_fail (size.height > 0);
  *width = size.width;
  *height = size.height;

  if (size.shadow.is_valid)
    {
      gdk_win32_surface_set_shadow_width (surface,
                                          size.shadow.left,
                                          size.shadow.right,
                                          size.shadow.top,
                                          size.shadow.bottom);
    }

  if (update_geometry)
    {
      GdkGeometry geometry;
      GdkSurfaceHints mask;
      GdkToplevelLayout *layout = impl->toplevel_layout;

      if (gdk_toplevel_layout_get_resizable (layout))
        {
          geometry.min_width = size.min_width;
          geometry.min_height = size.min_height;
          mask = GDK_HINT_MIN_SIZE;
        }
      else
        {
          geometry.max_width = geometry.min_width = *width;
          geometry.max_height = geometry.min_height = *height;
          mask = GDK_HINT_MIN_SIZE | GDK_HINT_MAX_SIZE;
        }
      gdk_win32_surface_set_geometry_hints (surface, &geometry, mask);
      gdk_surface_constrain_size (&geometry, mask, *width, *height, width, height);
    }
}

static void
_gdk_win32_surface_request_layout (GdkSurface *surface)
{
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);
  int scale = impl->surface_scale;
  RECT rect;

  if (impl->drag_move_resize_context.native_move_resize_pending)
    {
      surface->width = impl->next_layout.configured_width;
      surface->height = impl->next_layout.configured_height;
    }
  else
    {
      gdk_win32_get_surface_hwnd_rect (surface, &rect);

      /* Keep current position if rect is invalid (i.e. queried in bad context) */
      if (rect.right == rect.left || rect.bottom == rect.top)
        return;

      impl->next_layout.configured_width = (rect.right - rect.left + scale - 1) / scale;
      impl->next_layout.configured_height = (rect.bottom - rect.top + scale - 1) / scale;

      if (GDK_IS_TOPLEVEL (surface))
        {
          surface->x = rect.left / scale;
          surface->y = rect.top / scale;
        }
      else if (GDK_IS_POPUP (surface))
        {
          gdk_win32_surface_get_geometry (surface,
                                         &surface->x, &surface->y,
                                          NULL, NULL);
        }

      if (!impl->inhibit_configure)
        impl->force_recompute_size = TRUE;
    }
}

static gboolean
_gdk_win32_surface_compute_size (GdkSurface *surface)
{
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);
  int width, height;

  if (GDK_IS_TOPLEVEL (surface))
    compute_toplevel_size (surface, TRUE, &width, &height);

  if (!impl->drag_move_resize_context.native_move_resize_pending)
    {
      bool size_changed;

      if (GDK_IS_TOPLEVEL (surface) && impl->force_recompute_size)
        {
          size_changed = width != surface->width ||
                         height != surface->height;

          surface->width = width;
          surface->height = height;
          gdk_win32_surface_resize (surface, width, height);
          impl->force_recompute_size = FALSE;
        }
      else
        {
          size_changed = width != impl->next_layout.configured_width ||
                         height != impl->next_layout.configured_height;

          surface->width = impl->next_layout.configured_width;
          surface->height = impl->next_layout.configured_height;
        }

      if (size_changed)
        _gdk_surface_update_size (surface);
    }

  return FALSE;
}

static void
gdk_win32_surface_class_init (GdkWin32SurfaceClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);
  GdkSurfaceClass *impl_class = GDK_SURFACE_CLASS (klass);

  object_class->constructed = gdk_win32_surface_constructed;
  object_class->dispose = gdk_surface_win32_dispose;
  object_class->finalize = gdk_surface_win32_finalize;

  impl_class->hide = gdk_win32_surface_hide;
  impl_class->get_geometry = gdk_win32_surface_get_geometry;
  impl_class->get_device_state = gdk_surface_win32_get_device_state;
  impl_class->get_root_coords = gdk_win32_surface_get_root_coords;

  impl_class->set_input_region = gdk_win32_surface_set_input_region;
  impl_class->destroy = gdk_win32_surface_destroy;

  //impl_class->beep = gdk_x11_surface_beep;

  impl_class->destroy_notify = gdk_win32_surface_destroy_notify;
  impl_class->drag_begin = _gdk_win32_surface_drag_begin;
  impl_class->get_scale = _gdk_win32_surface_get_scale;
  impl_class->request_layout = _gdk_win32_surface_request_layout;
  impl_class->compute_size = _gdk_win32_surface_compute_size;
}

/**
 * gdk_win32_surface_get_handle:
 * @surface: (type GdkWin32Surface): a native `GdkSurface`.
 *
 * Returns the HWND handle belonging to @surface.
 *
 * Returns: the associated HWND handle.
 */
HWND
gdk_win32_surface_get_handle (GdkSurface *surface)
{
  g_return_val_if_fail (GDK_IS_WIN32_SURFACE (surface), NULL);

  return GDK_SURFACE_HWND (surface);
}

#define LAST_PROP 1

typedef struct
{
  GdkWin32Surface parent_instance;
} GdkWin32Popup;

typedef struct
{
  GdkWin32SurfaceClass parent_class;
} GdkWin32PopupClass;

static void gdk_win32_popup_iface_init (GdkPopupInterface *iface);

G_DEFINE_TYPE_WITH_CODE (GdkWin32Popup, gdk_win32_popup, GDK_TYPE_WIN32_SURFACE,
                         G_IMPLEMENT_INTERFACE (GDK_TYPE_POPUP,
                                                gdk_win32_popup_iface_init))

static void
gdk_win32_popup_init (GdkWin32Popup *popup)
{
}

static void
gdk_win32_popup_get_property (GObject    *object,
                              guint       prop_id,
                              GValue     *value,
                              GParamSpec *pspec)
{
  GdkSurface *surface = GDK_SURFACE (object);

  switch (prop_id)
    {
    case LAST_PROP + GDK_POPUP_PROP_PARENT:
      g_value_set_object (value, surface->parent);
      break;

    case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
      g_value_set_boolean (value, surface->autohide);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
      g_value_set_boolean (value, surface->shortcuts_inhibited);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gdk_win32_popup_set_property (GObject      *object,
                              guint         prop_id,
                              const GValue *value,
                              GParamSpec   *pspec)
{
  GdkSurface *surface = GDK_SURFACE (object);

  switch (prop_id)
    {
    case LAST_PROP + GDK_POPUP_PROP_PARENT:
      surface->parent = g_value_dup_object (value);
      if (surface->parent != NULL)
        surface->parent->children = g_list_prepend (surface->parent->children, surface);
      break;

    case LAST_PROP + GDK_POPUP_PROP_AUTOHIDE:
      surface->autohide = g_value_get_boolean (value);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gdk_win32_popup_class_init (GdkWin32PopupClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->get_property = gdk_win32_popup_get_property;
  object_class->set_property = gdk_win32_popup_set_property;

  gdk_popup_install_properties (object_class, 1);
}

static gboolean
gdk_win32_popup_present (GdkPopup       *popup,
                         int             width,
                         int             height,
                         GdkPopupLayout *layout)
{
  return gdk_win32_surface_present_popup (GDK_SURFACE (popup), width, height, layout);
}

static GdkGravity
gdk_win32_popup_get_surface_anchor (GdkPopup *popup)
{
  return GDK_SURFACE (popup)->popup.surface_anchor;
}

static GdkGravity
gdk_win32_popup_get_rect_anchor (GdkPopup *popup)
{
  return GDK_SURFACE (popup)->popup.rect_anchor;
}

static int
gdk_win32_popup_get_position_x (GdkPopup *popup)
{
  return GDK_SURFACE (popup)->x;
}

static int
gdk_win32_popup_get_position_y (GdkPopup *popup)
{
  return GDK_SURFACE (popup)->y;
}

static void
gdk_win32_popup_iface_init (GdkPopupInterface *iface)
{
  iface->present = gdk_win32_popup_present;
  iface->get_surface_anchor = gdk_win32_popup_get_surface_anchor;
  iface->get_rect_anchor = gdk_win32_popup_get_rect_anchor;
  iface->get_position_x = gdk_win32_popup_get_position_x;
  iface->get_position_y = gdk_win32_popup_get_position_y;
}


typedef struct
{
  GdkWin32Surface parent_instance;
} GdkWin32Toplevel;

typedef struct
{
  GdkWin32SurfaceClass parent_class;
} GdkWin32ToplevelClass;

static void gdk_win32_toplevel_iface_init (GdkToplevelInterface *iface);

G_DEFINE_TYPE_WITH_CODE (GdkWin32Toplevel, gdk_win32_toplevel, GDK_TYPE_WIN32_SURFACE,
                         G_IMPLEMENT_INTERFACE (GDK_TYPE_TOPLEVEL,
                                                gdk_win32_toplevel_iface_init))

static void
gdk_win32_toplevel_constructed (GObject *object)
{
  g_signal_connect (object, "notify::state",
                    G_CALLBACK (gdk_win32_toplevel_state_callback),
                    NULL);

  G_OBJECT_CLASS (gdk_win32_toplevel_parent_class)->constructed (object);
}

static void
gdk_win32_toplevel_set_property (GObject      *object,
                                 guint         prop_id,
                                 const GValue *value,
                                 GParamSpec   *pspec)
{
  GdkSurface *surface = GDK_SURFACE (object);

  switch (prop_id)
    {
    case LAST_PROP + GDK_TOPLEVEL_PROP_TITLE:
      gdk_win32_surface_set_title (surface, g_value_get_string (value));
      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_STARTUP_ID:
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_TRANSIENT_FOR:
      gdk_win32_surface_set_transient_for (surface, g_value_get_object (value));
      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_MODAL:
      GDK_SURFACE (surface)->modal_hint = g_value_get_boolean (value);

      if (GDK_SURFACE (surface)->modal_hint)
        gdk_win32_push_modal_surface (surface);

      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_ICON_LIST:
      gdk_win32_surface_set_icon_list (surface, g_value_get_pointer (value));
      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_DECORATED:
      GDK_WIN32_SURFACE (surface)->decorate_all = g_value_get_boolean (value);
      _gdk_win32_surface_update_style_bits (surface);
      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_DELETABLE:
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_FULLSCREEN_MODE:
      surface->fullscreen_mode = g_value_get_enum (value);
      g_object_notify_by_pspec (G_OBJECT (surface), pspec);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gdk_win32_toplevel_get_property (GObject    *object,
                                 guint       prop_id,
                                 GValue     *value,
                                 GParamSpec *pspec)
{
  GdkSurface *surface = GDK_SURFACE (object);

  switch (prop_id)
    {
    case LAST_PROP + GDK_TOPLEVEL_PROP_STATE:
      g_value_set_flags (value, surface->state);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_TITLE:
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_STARTUP_ID:
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_TRANSIENT_FOR:
      g_value_set_object (value, surface->transient_for);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_MODAL:
      g_value_set_boolean (value, GDK_SURFACE (surface)->modal_hint);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_ICON_LIST:
      g_value_set_pointer (value, NULL);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_DECORATED:
      g_value_set_boolean (value, GDK_WIN32_SURFACE (surface)->decorate_all);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_DELETABLE:
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_FULLSCREEN_MODE:
      g_value_set_enum (value, surface->fullscreen_mode);
      break;

    case LAST_PROP + GDK_TOPLEVEL_PROP_SHORTCUTS_INHIBITED:
      g_value_set_boolean (value, surface->shortcuts_inhibited);
      break;

    default:
      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
      break;
    }
}

static void
gdk_win32_toplevel_finalize (GObject *object)
{
  GdkWin32Surface *self = GDK_WIN32_SURFACE (object);

  g_signal_handlers_disconnect_by_func (self,
                                        gdk_win32_toplevel_state_callback,
                                        NULL);

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

static void
gdk_win32_toplevel_class_init (GdkWin32ToplevelClass *class)
{
  GObjectClass *object_class = G_OBJECT_CLASS (class);

  object_class->constructed = gdk_win32_toplevel_constructed;
  object_class->finalize = gdk_win32_toplevel_finalize;
  object_class->get_property = gdk_win32_toplevel_get_property;
  object_class->set_property = gdk_win32_toplevel_set_property;

  gdk_toplevel_install_properties (object_class, 1);
}

static void
gdk_win32_toplevel_init (GdkWin32Toplevel *toplevel)
{
}

static void
gdk_win32_toplevel_present (GdkToplevel       *toplevel,
                            GdkToplevelLayout *layout)
{
  GdkSurface *surface = GDK_SURFACE (toplevel);
  GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);
  int width, height;
  gboolean maximize;
  gboolean fullscreen;

  g_clear_pointer (&impl->toplevel_layout, gdk_toplevel_layout_unref);
  impl->toplevel_layout = gdk_toplevel_layout_copy (layout);
  compute_toplevel_size (surface, FALSE, &width, &height);
  gdk_win32_surface_resize (surface, width, height);

  if (gdk_toplevel_layout_get_maximized (layout, &maximize))
    {
      if (maximize)
        gdk_win32_surface_maximize (surface);
      else
        gdk_win32_surface_unmaximize (surface);
    }

  if (gdk_toplevel_layout_get_fullscreen (layout, &fullscreen))
    {
      if (fullscreen)
        {
          GdkMonitor *monitor;

          monitor = gdk_toplevel_layout_get_fullscreen_monitor (layout);
          gdk_win32_surface_fullscreen (surface, monitor);
        }
      else
        {
          gdk_win32_surface_unfullscreen (surface);
        }
    }

  gdk_win32_surface_show (surface, FALSE);
  maybe_notify_mapped (surface);
}

static gboolean
gdk_win32_toplevel_minimize (GdkToplevel *toplevel)
{
  gdk_win32_surface_minimize (GDK_SURFACE (toplevel));

  return TRUE;
}

static gboolean
gdk_win32_toplevel_lower (GdkToplevel *toplevel)
{
  return FALSE;
}

static void
gdk_win32_toplevel_focus (GdkToplevel *toplevel,
                            guint32      timestamp)
{
  gdk_win32_surface_focus (GDK_SURFACE (toplevel), timestamp);
}

static gboolean
gdk_win32_toplevel_show_window_menu (GdkToplevel *toplevel,
                                     GdkEvent    *event)
{
  return gdk_win32_surface_show_window_menu (GDK_SURFACE (toplevel), event);
}

static gboolean
gdk_win32_toplevel_supports_edge_constraints (GdkToplevel *toplevel)
{
  return FALSE;
}

static void
gdk_win32_toplevel_inhibit_system_shortcuts (GdkToplevel *toplevel,
                                             GdkEvent    *gdk_event)
{
  GdkSurface *surface = GDK_SURFACE (toplevel);
  GdkSeat *gdk_seat;
  GdkGrabStatus status;

  if (surface->shortcuts_inhibited)
    return; /* Already inhibited */

  if (!(surface->state & GDK_TOPLEVEL_STATE_FOCUSED))
    return;

  gdk_seat = gdk_surface_get_seat_from_event (surface, gdk_event);

  if (!(gdk_seat_get_capabilities (gdk_seat) & GDK_SEAT_CAPABILITY_KEYBOARD))
    return;

  status = gdk_seat_grab (gdk_seat, surface, GDK_SEAT_CAPABILITY_KEYBOARD,
                          TRUE, NULL, gdk_event, NULL, NULL);

  if (status != GDK_GRAB_SUCCESS)
    return;

  // TODO: install a WH_KEYBOARD_LL hook to take alt-tab/win etc.

  surface->shortcuts_inhibited = TRUE;
  surface->current_shortcuts_inhibited_seat = gdk_seat;
  g_object_notify (G_OBJECT (toplevel), "shortcuts-inhibited");
}

static void
gdk_win32_toplevel_restore_system_shortcuts (GdkToplevel *toplevel)
{
  GdkSurface *surface = GDK_SURFACE (toplevel);
  GdkSeat *gdk_seat;

  if (!surface->shortcuts_inhibited)
    return; /* Not inhibited */

  gdk_seat = surface->current_shortcuts_inhibited_seat;
  gdk_seat_ungrab (gdk_seat);
  surface->current_shortcuts_inhibited_seat = NULL;

  surface->shortcuts_inhibited = FALSE;
  g_object_notify (G_OBJECT (toplevel), "shortcuts-inhibited");
}

static void
gdk_win32_toplevel_state_callback (GdkSurface *surface)
{
  if (surface->state & GDK_TOPLEVEL_STATE_FOCUSED)
    return;

  if (surface->shortcuts_inhibited)
    gdk_win32_toplevel_restore_system_shortcuts (GDK_TOPLEVEL (surface));
}

static void
gdk_win32_toplevel_iface_init (GdkToplevelInterface *iface)
{
  iface->present = gdk_win32_toplevel_present;
  iface->minimize = gdk_win32_toplevel_minimize;
  iface->lower = gdk_win32_toplevel_lower;
  iface->focus = gdk_win32_toplevel_focus;
  iface->show_window_menu = gdk_win32_toplevel_show_window_menu;
  iface->supports_edge_constraints = gdk_win32_toplevel_supports_edge_constraints;
  iface->inhibit_system_shortcuts = gdk_win32_toplevel_inhibit_system_shortcuts;
  iface->restore_system_shortcuts = gdk_win32_toplevel_restore_system_shortcuts;
  iface->begin_resize = gdk_win32_toplevel_begin_resize;
  iface->begin_move = gdk_win32_toplevel_begin_move;
}

typedef struct
{
  GdkWin32Surface parent_instance;
} GdkWin32DragSurface;

typedef struct
{
  GdkWin32SurfaceClass parent_class;
} GdkWin32DragSurfaceClass;

static void gdk_win32_drag_surface_iface_init (GdkDragSurfaceInterface *iface);

G_DEFINE_TYPE_WITH_CODE (GdkWin32DragSurface, gdk_win32_drag_surface, GDK_TYPE_WIN32_SURFACE,
                         G_IMPLEMENT_INTERFACE (GDK_TYPE_DRAG_SURFACE,
                                                gdk_win32_drag_surface_iface_init))

static void
gdk_win32_drag_surface_init (GdkWin32DragSurface *surface)
{
}

static void
gdk_win32_drag_surface_class_init (GdkWin32DragSurfaceClass *class)
{
}

static gboolean
gdk_win32_drag_surface_present (GdkDragSurface *drag_surface,
                                  int             width,
                                  int             height)
{
  GdkSurface *surface = GDK_SURFACE (drag_surface);

  gdk_win32_surface_resize (surface, width, height);  
  gdk_win32_surface_show (surface, FALSE);
  maybe_notify_mapped (surface);

  return TRUE;
}

static void
gdk_win32_drag_surface_iface_init (GdkDragSurfaceInterface *iface)
{
  iface->present = gdk_win32_drag_surface_present;
}


static void
gdk_win32_surface_get_queued_hwnd_rect (GdkSurface *surface,
                                        int         scale,
                                        RECT       *return_hwnd_rect)
{
  RECT hwnd_rect;

  _gdk_win32_get_window_client_area_rect (surface, scale, &hwnd_rect);

  /* Turn client area into window area */
  _gdk_win32_adjust_client_rect (surface, &hwnd_rect);

  *return_hwnd_rect = hwnd_rect;
}

static void
gdk_win32_surface_apply_queued_move_resize (GdkSurface *surface,
                                            RECT        hwnd_rect)
{
  if (!IsIconic (GDK_SURFACE_HWND (surface)))
    {
      GDK_NOTE (EVENTS, g_print ("Setting HWND position (SetWindowPos) ... "));

      API_CALL (SetWindowPos, (GDK_SURFACE_HWND (surface),
                               SWP_NOZORDER_SPECIFIED,
                               hwnd_rect.left, hwnd_rect.top,
                               hwnd_rect.right - hwnd_rect.left,
                               hwnd_rect.bottom - hwnd_rect.top,
                               SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOREDRAW));

      GDK_NOTE (EVENTS, g_print (" ... set HWND position (SetWindowPos)\n"));

      /*
       * Workaround situations in the current Win32 surface resize code that may have notified GDK
       * too late for resizes, which manifests on nVidia drivers (and AMD drivers in mailbox
       * presentation mode) running under Vulkan when one interactively enlarges the surface (HWND).
       *
       * See MR !7562 for more details
       */
      _gdk_surface_update_size (surface);

      return;
    }

  /* Don't move iconic windows */
  /* TODO: use SetWindowPlacement() to change non-minimized window position */
}

RECT
gdk_win32_surface_handle_queued_move_resize (GdkDrawContext *draw_context)
{
  GdkSurface *surface;
  GdkWin32Surface *impl;
  int scale;
  RECT queued_hwnd_rect;

  surface = gdk_draw_context_get_surface (draw_context);
  impl = GDK_WIN32_SURFACE (surface);
  scale = gdk_surface_get_scale_factor (surface);

  gdk_win32_surface_get_queued_hwnd_rect (surface, scale, &queued_hwnd_rect);

  /* Apply queued resizes for non-double-buffered HWNDs
   * before painting them (we paint on the surface DC directly,
   * it must have the right size).
   * Due to some poorly-undetstood issue delayed
   * resizing of double-buffered HWNDs can produce weird
   * artefacts, so these are also resized before we paint.
   */
  if (impl->drag_move_resize_context.native_move_resize_pending)
    {
      impl->drag_move_resize_context.native_move_resize_pending = FALSE;
      gdk_win32_surface_apply_queued_move_resize (surface, queued_hwnd_rect);
    }

  return queued_hwnd_rect;
}

void
_gdk_win32_surface_invalidate_egl_framebuffer (GdkSurface *surface)
{
/* If we are using ANGLE, we need to force redraw of the whole surface and its child surfaces
 *  as we need to re-acquire the EGL surfaces that we rendered to upload to Cairo explicitly,
 *  using gdk_window_invalidate_rect (), when we maximize or restore
 */
#ifdef HAVE_EGL
  if (surface->gl_paint_context != NULL && gdk_gl_context_get_use_es (surface->gl_paint_context))
    {
      GdkWin32Surface *impl = GDK_WIN32_SURFACE (surface);

      impl->egl_force_redraw_all = TRUE;
    }
#endif
}

GdkSurface *
gdk_win32_drag_surface_new (GdkDisplay *display)
{
  return g_object_new (GDK_TYPE_WIN32_DRAG_SURFACE,
                       "display", display,
                       NULL);
}
