[gtk+] GDK W32: Re-implement AeroSnap for CSD windows



commit 0ce217cf94c2a0c1c9936daded9dd5b6183cea32
Author: Руслан Ижбулатов <lrn1986 gmail com>
Date:   Tue Mar 8 02:33:47 2016 +0000

    GDK W32: Re-implement AeroSnap for CSD windows
    
    It works exactly like AeroSnap.
    Except for shift+win+left/right, which is left for AeroSnap
    to handle (AeroSnap takes action before we get the message,
    so there's no way for us to override it).
    The only thing that doesn't work is shift+win+left/right on
    a maximized window, for reasons unknown at the moment.
    
    This only implements winkey+stuff behaviour of AeroSnap,
    not the drag-to-the-edge-and-something-funny-happens one.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=763013

 gdk/win32/gdkevents-win32.c  |   42 +++++++
 gdk/win32/gdkprivate-win32.h |    3 +
 gdk/win32/gdkwindow-win32.c  |  273 ++++++++++++++++++++++++++++++++++++++++++
 gdk/win32/gdkwindow-win32.h  |   51 ++++++++
 4 files changed, 369 insertions(+), 0 deletions(-)
---
diff --git a/gdk/win32/gdkevents-win32.c b/gdk/win32/gdkevents-win32.c
index 70d1335..0ee314e 100644
--- a/gdk/win32/gdkevents-win32.c
+++ b/gdk/win32/gdkevents-win32.c
@@ -2288,6 +2288,48 @@ gdk_event_translate (MSG  *msg,
       if (GDK_WINDOW_DESTROYED (window))
        break;
 
+      if (msg->message == WM_KEYUP &&
+          !GDK_WINDOW_DESTROYED (gdk_window_get_toplevel (window)) &&
+          _gdk_win32_window_lacks_wm_decorations (gdk_window_get_toplevel (window)) && /* For CSD only */
+          ((GetKeyState (VK_LWIN) & 0x8000) ||
+           (GetKeyState (VK_RWIN) & 0x8000)))
+       {
+         GdkWin32AeroSnapCombo combo = GDK_WIN32_AEROSNAP_COMBO_NOTHING;
+         gboolean lshiftdown = GetKeyState (VK_LSHIFT) & 0x8000;
+          gboolean rshiftdown = GetKeyState (VK_RSHIFT) & 0x8000;
+          gboolean oneshiftdown = (lshiftdown || rshiftdown) && !(lshiftdown && rshiftdown);
+
+         switch (msg->wParam)
+           {
+           case VK_UP:
+             combo = GDK_WIN32_AEROSNAP_COMBO_UP;
+             break;
+           case VK_DOWN:
+             combo = GDK_WIN32_AEROSNAP_COMBO_DOWN;
+             break;
+           case VK_LEFT:
+             combo = GDK_WIN32_AEROSNAP_COMBO_LEFT;
+             break;
+           case VK_RIGHT:
+             combo = GDK_WIN32_AEROSNAP_COMBO_RIGHT;
+             break;
+           }
+
+         if (oneshiftdown && combo != GDK_WIN32_AEROSNAP_COMBO_NOTHING)
+           combo += 4;
+
+         /* These are the only combos that Windows WM does handle for us */
+         if (combo == GDK_WIN32_AEROSNAP_COMBO_SHIFTLEFT ||
+              combo == GDK_WIN32_AEROSNAP_COMBO_SHIFTRIGHT)
+            combo = GDK_WIN32_AEROSNAP_COMBO_NOTHING;
+
+         if (combo != GDK_WIN32_AEROSNAP_COMBO_NOTHING)
+           {
+             _gdk_win32_window_handle_aerosnap (gdk_window_get_toplevel (window), combo);
+             break;
+           }
+       }
+
       event = gdk_event_new ((msg->message == WM_KEYDOWN ||
                              msg->message == WM_SYSKEYDOWN) ?
                             GDK_KEY_PRESS : GDK_KEY_RELEASE);
diff --git a/gdk/win32/gdkprivate-win32.h b/gdk/win32/gdkprivate-win32.h
index 86abd99..dac65f4 100644
--- a/gdk/win32/gdkprivate-win32.h
+++ b/gdk/win32/gdkprivate-win32.h
@@ -536,6 +536,9 @@ void _gdk_win32_emit_configure_event (GdkWindow *window);
 
 guint32 _gdk_win32_keymap_get_decimal_mark (void);
 
+void     _gdk_win32_window_handle_aerosnap      (GdkWindow            *window,
+                                                 GdkWin32AeroSnapCombo combo);
+
 gboolean _gdk_win32_get_window_rect             (GdkWindow  *window,
                                                  RECT       *rect);
 void     _gdk_win32_do_emit_configure_event     (GdkWindow  *window,
diff --git a/gdk/win32/gdkwindow-win32.c b/gdk/win32/gdkwindow-win32.c
index afba8c1..2289927 100644
--- a/gdk/win32/gdkwindow-win32.c
+++ b/gdk/win32/gdkwindow-win32.c
@@ -42,6 +42,8 @@
 
 #include <cairo-win32.h>
 #include <dwmapi.h>
+#include <math.h>
+#include "fallback-c89.c"
 
 static void gdk_window_impl_win32_init       (GdkWindowImplWin32      *window);
 static void gdk_window_impl_win32_class_init (GdkWindowImplWin32Class *klass);
@@ -2919,6 +2921,277 @@ _gdk_window_get_functions (GdkWindow     *window,
   return (functions_set != NULL);
 }
 
+static void
+unsnap (GdkWindow  *window,
+        GdkScreen  *screen,
+        gint        monitor)
+{
+  GdkWindowImplWin32 *impl;
+  GdkRectangle rect;
+
+  impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
+
+  impl->snap_state = GDK_WIN32_AEROSNAP_STATE_UNDETERMINED;
+
+  if (impl->snap_stash == NULL)
+    return;
+
+  gdk_screen_get_monitor_workarea (screen, monitor, &rect);
+
+  GDK_NOTE (MISC, g_print ("Monitor work area %d x %d @ %d : %d\n", rect.width, rect.height, rect.x, 
rect.y));
+
+  /* Calculate actual unsnapped window size based on its
+   * old relative size. Same for position.
+   */
+  rect.x += round (rect.width * impl->snap_stash->x);
+  rect.y += round (rect.height * impl->snap_stash->y);
+  rect.width = round (rect.width * impl->snap_stash->width);
+  rect.height = round (rect.height * impl->snap_stash->height);
+
+  GDK_NOTE (MISC, g_print ("Unsnapped window size %d x %d @ %d : %d\n", rect.width, rect.height, rect.x, 
rect.y));
+
+  gdk_window_move_resize (window, rect.x, rect.y,
+                          rect.width, rect.height);
+
+  g_clear_pointer (&impl->snap_stash, g_free);
+}
+
+static void
+stash_window (GdkWindow          *window,
+              GdkWindowImplWin32 *impl,
+              GdkScreen          *screen,
+              gint                monitor)
+{
+  gint x, y;
+  gint width;
+  gint height;
+  WINDOWPLACEMENT placement;
+  HMONITOR hmonitor;
+  MONITORINFO hmonitor_info;
+
+  placement.length = sizeof(WINDOWPLACEMENT);
+
+  /* Use W32 API to get unmaximized window size, which GDK doesn't remember */
+  if (!GetWindowPlacement (GDK_WINDOW_HWND (window), &placement))
+    return;
+
+  /* MSDN is very vague, but in practice rcNormalPosition is the same as GetWindowRect(),
+   * only with adjustments for toolbars (which creates rather weird coodinate space issues).
+   * We need to get monitor info and apply workarea vs monitorarea diff to turn
+   * these into screen coordinates proper.
+   */
+  hmonitor = MonitorFromWindow (GDK_WINDOW_HWND (window), MONITOR_DEFAULTTONEAREST);
+  hmonitor_info.cbSize = sizeof (hmonitor_info);
+
+  if (!GetMonitorInfoA (hmonitor, &hmonitor_info))
+    return;
+
+  if (impl->snap_stash == NULL)
+    impl->snap_stash = g_new0 (GdkRectangleDouble, 1);
+
+  GDK_NOTE (MISC, g_print ("monitor work area  %ld x %ld @ %ld : %ld\n",
+                           hmonitor_info.rcWork.right - hmonitor_info.rcWork.left,
+                           hmonitor_info.rcWork.bottom - hmonitor_info.rcWork.top,
+                           hmonitor_info.rcWork.left,
+                           hmonitor_info.rcWork.top));
+  GDK_NOTE (MISC, g_print ("monitor      area  %ld x %ld @ %ld : %ld\n",
+                           hmonitor_info.rcMonitor.right - hmonitor_info.rcMonitor.left,
+                           hmonitor_info.rcMonitor.bottom - hmonitor_info.rcMonitor.top,
+                           hmonitor_info.rcMonitor.left,
+                           hmonitor_info.rcMonitor.top));
+  GDK_NOTE (MISC, g_print ("window  work place %ld x %ld @ %ld : %ld\n",
+                           placement.rcNormalPosition.right - placement.rcNormalPosition.left,
+                           placement.rcNormalPosition.bottom - placement.rcNormalPosition.top,
+                           placement.rcNormalPosition.left,
+                           placement.rcNormalPosition.top));
+
+  width = placement.rcNormalPosition.right - placement.rcNormalPosition.left;
+  height = placement.rcNormalPosition.bottom - placement.rcNormalPosition.top;
+  x = placement.rcNormalPosition.left - hmonitor_info.rcMonitor.left;
+  y = placement.rcNormalPosition.top - hmonitor_info.rcMonitor.top;
+
+  impl->snap_stash->x = (gdouble) (x) / (gdouble) (hmonitor_info.rcWork.right - hmonitor_info.rcWork.left);
+  impl->snap_stash->y = (gdouble) (y) / (gdouble) (hmonitor_info.rcWork.bottom - hmonitor_info.rcWork.top);
+  impl->snap_stash->width = (gdouble) width / (gdouble) (hmonitor_info.rcWork.right - 
hmonitor_info.rcWork.left);
+  impl->snap_stash->height = (gdouble) height / (gdouble) (hmonitor_info.rcWork.bottom - 
hmonitor_info.rcWork.top);
+
+  GDK_NOTE (MISC, g_print ("Stashed window %d x %d @ %d : %d as %f x %f @ %f : %f\n",
+                           width, height, x, y,
+                           impl->snap_stash->width, impl->snap_stash->height, impl->snap_stash->x, 
impl->snap_stash->y));
+}
+
+static void
+snap_up (GdkWindow *window,
+         GdkScreen *screen,
+         gint       monitor)
+{
+  SHORT maxysize;
+  gint x, y;
+  gint width;
+  GdkWindowImplWin32 *impl;
+
+  impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
+
+  impl->snap_state = GDK_WIN32_AEROSNAP_STATE_FULLUP;
+
+  maxysize = GetSystemMetrics (SM_CYMAXTRACK);
+  gdk_window_get_position (window, &x, &y);
+  width = gdk_window_get_width (window);
+
+  stash_window (window, impl, screen, monitor);
+
+  gdk_window_move_resize (window, x, 0, width, maxysize);
+}
+
+static void
+snap_left (GdkWindow *window,
+           GdkScreen *screen,
+           gint       monitor,
+           gint       snap_monitor)
+{
+  GdkRectangle rect;
+  GdkWindowImplWin32 *impl;
+
+  impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
+
+  impl->snap_state = GDK_WIN32_AEROSNAP_STATE_HALFLEFT;
+
+  gdk_screen_get_monitor_workarea (screen, snap_monitor, &rect);
+
+  stash_window (window, impl, screen, monitor);
+
+  rect.width = rect.width / 2;
+
+  gdk_window_move_resize (window, rect.x, rect.y, rect.width, rect.height);
+}
+
+static void
+snap_right (GdkWindow *window,
+            GdkScreen *screen,
+            gint       monitor,
+            gint       snap_monitor)
+{
+  GdkRectangle rect;
+  GdkWindowImplWin32 *impl;
+
+  impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
+
+  impl->snap_state = GDK_WIN32_AEROSNAP_STATE_HALFRIGHT;
+
+  gdk_screen_get_monitor_workarea (screen, snap_monitor, &rect);
+
+  stash_window (window, impl, screen, monitor);
+
+  rect.width /= 2;
+  rect.x += rect.width;
+
+  gdk_window_move_resize (window, rect.x, rect.y, rect.width, rect.height);
+}
+
+void
+_gdk_win32_window_handle_aerosnap (GdkWindow            *window,
+                                   GdkWin32AeroSnapCombo combo)
+{
+  GdkWindowImplWin32 *impl;
+  GdkDisplay *display;
+  GdkScreen *screen;
+  gint n_monitors, monitor;
+  GdkWindowState window_state = gdk_window_get_state (window);
+  gboolean minimized = window_state & GDK_WINDOW_STATE_ICONIFIED;
+  gboolean maximized = window_state & GDK_WINDOW_STATE_MAXIMIZED;
+  gboolean halfsnapped;
+
+  impl = GDK_WINDOW_IMPL_WIN32 (window->impl);
+  display = gdk_window_get_display (window);
+  screen = gdk_display_get_default_screen (display);
+  n_monitors = gdk_screen_get_n_monitors (screen);
+  monitor = gdk_screen_get_monitor_at_window (screen, window);
+
+  if (minimized && maximized)
+    minimized = FALSE;
+
+  halfsnapped = (impl->snap_state == GDK_WIN32_AEROSNAP_STATE_HALFRIGHT ||
+                 impl->snap_state == GDK_WIN32_AEROSNAP_STATE_HALFLEFT ||
+                 impl->snap_state == GDK_WIN32_AEROSNAP_STATE_FULLUP);
+
+  switch (combo)
+    {
+    case GDK_WIN32_AEROSNAP_COMBO_NOTHING:
+      /* Do nothing */
+      break;
+    case GDK_WIN32_AEROSNAP_COMBO_UP:
+      if (!maximized)
+        {
+         unsnap (window, screen, monitor);
+          gdk_window_maximize (window);
+        }
+      break;
+    case GDK_WIN32_AEROSNAP_COMBO_DOWN:
+    case GDK_WIN32_AEROSNAP_COMBO_SHIFTDOWN:
+      if (maximized)
+        {
+         gdk_window_unmaximize (window);
+         unsnap (window, screen, monitor);
+        }
+      else if (halfsnapped)
+       unsnap (window, screen, monitor);
+      else if (!minimized)
+       gdk_window_iconify (window);
+      break;
+    case GDK_WIN32_AEROSNAP_COMBO_LEFT:
+      if (maximized)
+        gdk_window_unmaximize (window);
+
+      if (impl->snap_state == GDK_WIN32_AEROSNAP_STATE_UNDETERMINED ||
+         impl->snap_state == GDK_WIN32_AEROSNAP_STATE_FULLUP)
+       {
+         unsnap (window, screen, monitor);
+         snap_left (window, screen, monitor, monitor);
+       }
+      else if (impl->snap_state == GDK_WIN32_AEROSNAP_STATE_HALFLEFT)
+       {
+         unsnap (window, screen, monitor);
+         snap_right (window, screen, monitor, monitor - 1 >= 0 ? monitor - 1 : n_monitors - 1);
+       }
+      else if (impl->snap_state == GDK_WIN32_AEROSNAP_STATE_HALFRIGHT)
+       {
+         unsnap (window, screen, monitor);
+       }
+      break;
+    case GDK_WIN32_AEROSNAP_COMBO_RIGHT:
+      if (maximized)
+        gdk_window_unmaximize (window);
+
+      if (impl->snap_state == GDK_WIN32_AEROSNAP_STATE_UNDETERMINED ||
+         impl->snap_state == GDK_WIN32_AEROSNAP_STATE_FULLUP)
+       {
+         unsnap (window, screen, monitor);
+         snap_right (window, screen, monitor, monitor);
+       }
+      else if (impl->snap_state == GDK_WIN32_AEROSNAP_STATE_HALFLEFT)
+       {
+         unsnap (window, screen, monitor);
+       }
+      else if (impl->snap_state == GDK_WIN32_AEROSNAP_STATE_HALFRIGHT)
+       {
+         unsnap (window, screen, monitor);
+         snap_left (window, screen, monitor, monitor + 1 < n_monitors ? monitor + 1 : 0);
+       }
+      break;
+    case GDK_WIN32_AEROSNAP_COMBO_SHIFTUP:
+      if (!maximized &&
+          impl->snap_state == GDK_WIN32_AEROSNAP_STATE_UNDETERMINED)
+       {
+         snap_up (window, screen, monitor);
+       }
+      break;
+    case GDK_WIN32_AEROSNAP_COMBO_SHIFTLEFT:
+    case GDK_WIN32_AEROSNAP_COMBO_SHIFTRIGHT:
+      /* No implementation needed at the moment */
+      break;
+    }
+}
+
 static const gchar *
 get_cursor_name_from_op (GdkW32WindowDragOp op,
                          GdkWindowEdge      edge)
diff --git a/gdk/win32/gdkwindow-win32.h b/gdk/win32/gdkwindow-win32.h
index 5aa6a49..312e7f1 100644
--- a/gdk/win32/gdkwindow-win32.h
+++ b/gdk/win32/gdkwindow-win32.h
@@ -46,6 +46,44 @@ typedef struct _GdkWindowImplWin32Class GdkWindowImplWin32Class;
 #define GDK_IS_WINDOW_IMPL_WIN32_CLASS(klass)   (G_TYPE_CHECK_CLASS_TYPE ((klass), 
GDK_TYPE_WINDOW_IMPL_WIN32))
 #define GDK_WINDOW_IMPL_WIN32_GET_CLASS(obj)    (G_TYPE_INSTANCE_GET_CLASS ((obj), 
GDK_TYPE_WINDOW_IMPL_WIN32, GdkWindowImplWin32Class))
 
+enum _GdkWin32AeroSnapCombo
+{
+  GDK_WIN32_AEROSNAP_COMBO_NOTHING = 0,
+  GDK_WIN32_AEROSNAP_COMBO_UP,
+  GDK_WIN32_AEROSNAP_COMBO_DOWN,
+  GDK_WIN32_AEROSNAP_COMBO_LEFT,
+  GDK_WIN32_AEROSNAP_COMBO_RIGHT,
+  /* Same order as non-shift variants. We use it to do things like:
+   * AEROSNAP_UP + 4 = AEROSNAP_SHIFTUP
+   */
+  GDK_WIN32_AEROSNAP_COMBO_SHIFTUP,
+  GDK_WIN32_AEROSNAP_COMBO_SHIFTDOWN,
+  GDK_WIN32_AEROSNAP_COMBO_SHIFTLEFT,
+  GDK_WIN32_AEROSNAP_COMBO_SHIFTRIGHT
+};
+
+typedef enum _GdkWin32AeroSnapCombo GdkWin32AeroSnapCombo;
+
+enum _GdkWin32AeroSnapState
+{
+  GDK_WIN32_AEROSNAP_STATE_UNDETERMINED = 0,
+  GDK_WIN32_AEROSNAP_STATE_HALFLEFT,
+  GDK_WIN32_AEROSNAP_STATE_HALFRIGHT,
+  GDK_WIN32_AEROSNAP_STATE_FULLUP,
+};
+
+typedef enum _GdkWin32AeroSnapState GdkWin32AeroSnapState;
+
+struct _GdkRectangleDouble
+{
+  gdouble x;
+  gdouble y;
+  gdouble width;
+  gdouble height;
+};
+
+typedef struct _GdkRectangleDouble GdkRectangleDouble;
+
 enum _GdkW32WindowDragOp
 {
   GDK_WIN32_DRAGOP_NONE = 0,
@@ -173,6 +211,19 @@ struct _GdkWindowImplWin32
 
   GdkW32DragMoveResizeContext drag_move_resize_context;
 
+  /* Remembers where the window was snapped.
+   * Some snap operations change their meaning if
+   * the window is already snapped.
+   */
+  GdkWin32AeroSnapState snap_state;
+
+  /* Remembers window position before it was snapped.
+   * This is used to unsnap it.
+   * Position and size are percentages of the workarea
+   * of the monitor on which the window was before it was snapped.
+   */
+  GdkRectangleDouble *snap_stash;
+
   /* Decorations set by gdk_window_set_decorations() or NULL if unset */
   GdkWMDecoration* decorations;
 


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]