After an evaluation, GNOME has moved from Bugzilla to GitLab. Learn more about GitLab.
No new issues can be reported in GNOME Bugzilla anymore.
To report an issue in a GNOME project, go to GNOME GitLab.
Do not go to GNOME Gitlab for: Bluefish, Doxygen, GnuCash, GStreamer, java-gnome, LDTP, NetworkManager, Tomboy.
Bug 761629 - W32: WM window drag-resizing code does not work well with GTK
W32: WM window drag-resizing code does not work well with GTK
Status: RESOLVED FIXED
Product: gtk+
Classification: Platform
Component: Backend: Win32
unspecified
Other All
: Normal normal
: ---
Assigned To: gtk-win32 maintainers
gtk-bugs
Depends on:
Blocks: 748872 759826 762711
 
 
Reported: 2016-02-06 12:24 UTC by LRN
Modified: 2016-03-02 21:42 UTC
See Also:
GNOME target: ---
GNOME version: ---


Attachments
GDK W32: custom (non-WM) drag-move and drag-resize code (24.63 KB, patch)
2016-02-06 12:24 UTC, LRN
none Details | Review
GDK W32: custom (non-WM) drag-move and drag-resize code (27.32 KB, patch)
2016-02-23 08:48 UTC, LRN
none Details | Review
GDK W32: custom (non-WM) drag-move and drag-resize code (28.46 KB, patch)
2016-02-23 14:03 UTC, LRN
none Details | Review
GDK W32: custom (non-WM) drag-move and drag-resize code (28.46 KB, patch)
2016-02-24 02:45 UTC, LRN
none Details | Review
GDK W32: custom (non-WM) drag-move and drag-resize code (29.63 KB, patch)
2016-02-24 16:13 UTC, LRN
none Details | Review
GDK W32: custom (non-WM) drag-move and drag-resize code (30.07 KB, patch)
2016-02-24 23:25 UTC, LRN
none Details | Review
GDK W32: custom (non-WM) drag-move and drag-resize code (29.89 KB, patch)
2016-03-01 03:19 UTC, LRN
committed Details | Review

Description LRN 2016-02-06 12:24:08 UTC
WM just spews window-size-changed messages at us as the user moves
the mouse, changing the actual native window size with each message.
It is against GTK drawing policy to redraw *that* often, therefore
it is possible (when resiging quickly, and/or when drawing is slow)
to encounter situations where underlying native window is too small
to contain current window contents, which makes the window look as if
it has been cut off, horizontally or vertically.

This does not happen with non-CSD windows, because there WM draws
the decorations, and WM *is* able to do that as fast as it needs to.

Also, the modal message loop used by WM to communicate the resizing process
to the application is inconvenient for us, as it basically traps us,
forcing us to manually iterate the main context fromt time to time to
avoid hanging up.

This technically also apply to drag-moving, but compositing WM hides all
the problems, so it looks OK.

The solution for this would be to re-implement drag-resizing (and drag-moving,
while we're at it) code ourselves, in GDK W32 backend, at least for CSD windows.
Such code would only change the underlying native window size when GTK is ready to
draw. This ensures that actual window size and drawn window size always.
This can result in resizing being less responsive than WM resizing, if
drawing is slow.
Comment 1 LRN 2016-02-06 12:24:16 UTC
Created attachment 320531 [details] [review]
GDK W32: custom (non-WM) drag-move and drag-resize code

Works only on CSD windows (presumably, because the trigger
points are only called for CSD windows), non-CSD windows continue
to use WM modal loop for drag-resizing (and drag-moving, i think).

Has the advantage of being completely immune to AeroSnap.
AeroSnap only worked partially on CSD windows, with the only part
that worked being "don't let users drag window titlebar outside of
the desktop". Now AeroSnap doesn't work on CSD windows at all,
which is good, since they currently don't work well with it due to
the way shadows are drawn.

It's possible to also re-implement AeroSnap (or something similar),
but that is a story for another commit.
Comment 2 LRN 2016-02-06 12:28:43 UTC
I would like to point out that this patch is somewhat experimental. That is another way of saying "nacho, stop with the nitpicking and style-bashing already!". I'm more interested in the implementation feedback than in style (that said, variable and function names are kind of awkward...).

Also, this change will work well with bug 748872, as doing window move, resize and contents update in a single call is what layered windows do.
Comment 3 LRN 2016-02-06 17:31:55 UTC
nacho: LRN, it is hard to follow those changes by just looking at the patch

nacho: LRN, next week I'll try to have a look at the code and try to understand it and provide you some comments

LRN: yeah, well, it's kind of long, but nothing too complex. Basically, instead of telling WM that someone clicked on resize area or titlebar, we just record the data (starting point, window pointer, which edge was clicked) and process mouse events ourselves

LRN: calculation is, again, long, but simple: diff between the starting point and current point is the adjustment. It adjusts the rectangle, taking minmaxinfo into account, and checks whether it changed or not. If it changed, it emits a configure event.

LRN: Then it records that fact and waits for a redraw. While it waits, no configure events (the ones caused by mouse cursor movement anyway) are emitted (this is probably unnecessary).

LRN: Immediately before redraw, if there's a resize pending, it actually takes the window size (from GTK/GDK point of view) and applies it to the underlying native window, so that actual drawing is done on the correctly-sized surface

LRN: and so on, until you release the button

I've also separated some code into functions, because i needed a way to do what configure emitting function does, but in 2 steps. And i needed to get minmax info, so i've made that code into a function too.
Comment 4 LRN 2016-02-07 00:35:02 UTC
Well, after more thorough examination i've discovered that this patch does not achieve the desired effect. There's still a discrepancy between window contents and window size.
Comment 5 LRN 2016-02-23 08:48:59 UTC
Created attachment 321929 [details] [review]
GDK W32: custom (non-WM) drag-move and drag-resize code

v2:
* cleanup
* made context part of the window
* use window end_paint instead of clock before-paint
  (implementation detail: end-paint is called just before
   GTK copies temporary buffer into window surface).
* changed size-calculation logic (now precisely follows the
  cursor, no matter how fast it moves around)
* still doesn't achieve the ultimate goal, but that's OK

Notes:
* Allows resizing the window farther than the taskbar edge
  (native W32 resize code does not allow that).
  This is fixable, but i'm not sure whether it should block
  this patch.
Comment 6 Ignacio Casal Quinteiro (nacho) 2016-02-23 08:58:14 UTC
Review of attachment 321929 [details] [review]:

See some nitpicks while I try to understand how this is going on.

::: gdk/win32/gdkevents-win32.c
@@ +1416,3 @@
 
+gboolean
+_gdk_win32_get_window_rect (GdkWindow *window, RECT *rect)

split params

@@ +1903,3 @@
 
+gboolean
+_gdk_win32_window_fill_minmaxinfo (GdkWindow *window, MINMAXINFO *mmi)

split parameters and the name of the method should be min_max_info

@@ +1914,3 @@
+
+  GDK_NOTE (EVENTS, g_print (" (mintrack:%ldx%ld maxtrack:%ldx%ld "
+				 "maxpos:%+ld%+ld maxsize:%ldx%ld)",

this seems wrongly indented

@@ +1950,3 @@
+  else
+    {
+      HMONITOR winmon;

call it monitor?

@@ +1951,3 @@
+    {
+      HMONITOR winmon;
+      MONITORINFO moninfo;

I'd say monitor_info

@@ +2571,3 @@
+
+      if (impl->dragmoveresize_context.op != GDK_WIN32_DRAGOP_NONE)
+      {

wrong indentation

@@ +2577,3 @@
+      {
+        GDK_NOTE (EVENTS,
+		g_print (" don't do moreresize because op %u == 0",

wrong indentation

@@ +2582,3 @@
+
+      if (impl->dragmoveresize_context.op != GDK_WIN32_DRAGOP_NONE)
+        {

I do not like empty ifs, just reverse the check

@@ +2894,3 @@
+
+      if (impl->dragmoveresize_context.op != GDK_WIN32_DRAGOP_NONE)
+        gdk_win32_window_end_moveresize_drag (window);

move_resize

@@ +3254,2 @@
+      if (_gdk_win32_window_fill_minmaxinfo (window, mmi))
+      {

wrong indentation

::: gdk/win32/gdkprivate-win32.h
@@ +545,3 @@
+void     _gdk_win32_do_emit_configure_event    (GdkWindow *window, RECT rect);
+void      gdk_win32_window_do_moveresize_drag  (GdkWindow *window, gint x, gint y);
+void      gdk_win32_window_end_moveresize_drag (GdkWindow *window);

move_resize...

::: gdk/win32/gdkwindow-win32.c
@@ +2662,2 @@
 static void
+setup_dragmoveresize_context (GdkWindow                   *window,

drag_move_resize...

@@ +2686,2 @@
+  GDK_NOTE (EVENTS, g_print ("begin drag moveresize: op %u, edge %d, device %p, button %d, coord %d:%d, time %u\n",
+                             context->op, context->edge, context->device, context->button, context->start_root_x, context->start_root_y, context->timestamp));

this is really long... split it in several lines

@@ +2696,3 @@
+  context->op = GDK_WIN32_DRAGOP_NONE;
+  GDK_NOTE (EVENTS, g_print ("end drag moveresize: op %u, edge %d, device %p, button %d, coord %d:%d, time %u\n",
+                             context->op, context->edge, context->device, context->button, context->start_root_x, context->start_root_y, context->timestamp));

ditto

@@ +2810,3 @@
 
+    if (height > mmi.ptMaxTrackSize.y)
+    {

indentation

::: gdk/win32/gdkwindow-win32.h
@@ +89,3 @@
+   * the window size and poistion to the native window.
+   */
+  gboolean           native_moveresize_pending;

move_resize...

@@ +132,3 @@
   HBITMAP          saved_dc_bitmap; /* Original bitmap for dc */
+
+  GdkW32DragMoveResizeContext dragmoveresize_context;

drag_move_resize_context
Comment 7 Ignacio Casal Quinteiro (nacho) 2016-02-23 11:21:02 UTC
Review of attachment 321929 [details] [review]:

In general the patch looks ok to me. See though some other questions and nitpicks around. Also, have you tested this with multiple monitors? if not we should probably point it out somewhere that we should test it for that.

::: gdk/win32/gdkevents-win32.c
@@ +1957,3 @@
+      moninfo.cbSize = sizeof (moninfo);
+
+      if (GetMonitorInfoA (winmon, &moninfo))

should we use the W version? though I guess we could do it on a different patch

::: gdk/win32/gdkwindow-win32.c
@@ +2725,2 @@
+  switch (context->op)
+  {

wrong indent

@@ +2729,1 @@
     {

wrong indent

@@ +2862,3 @@
+  {
+    context->native_moveresize_pending = TRUE;
+    _gdk_win32_do_emit_configure_event (window, new_rect);

why do we emit it here? is the size to change already at this point? I guess the magic happens on the end_paint so you just do the configure here but the actual size/position changes on the end_paint?

@@ +2917,3 @@
+
+  if (impl->dragmoveresize_context.op != GDK_WIN32_DRAGOP_NONE)
+    gdk_win32_window_end_moveresize_drag (window);

why do we need to end the move_resize_drag? I would expect this to happen when end_resize_drag is called...

@@ +2919,3 @@
+    gdk_win32_window_end_moveresize_drag (window);
+
+  setup_dragmoveresize_context (window, &impl->dragmoveresize_context, GDK_WIN32_DRAGOP_RESIZE, edge, device, button, root_x, root_y, timestamp);

too long

@@ +2951,3 @@
+
+  if (impl->dragmoveresize_context.op != GDK_WIN32_DRAGOP_NONE)
+    gdk_win32_window_end_moveresize_drag (window);

same question here.

::: gdk/win32/gdkwindow-win32.h
@@ +69,3 @@
+
+  /* Not used */
+  gint               button;

if not used do we need it?
Comment 8 LRN 2016-02-23 12:41:51 UTC
(In reply to Ignacio Casal Quinteiro (nacho) from comment #7)
> Review of attachment 321929 [details] [review] [review]:
> 
> In general the patch looks ok to me. See though some other questions and
> nitpicks around. Also, have you tested this with multiple monitors? if not
> we should probably point it out somewhere that we should test it for that.
I'll try to do a multi-monitor test. I have a spare screen i can connect for that purpose.

> 
> ::: gdk/win32/gdkevents-win32.c
> @@ +1957,3 @@
> +      moninfo.cbSize = sizeof (moninfo);
> +
> +      if (GetMonitorInfoA (winmon, &moninfo))
> 
> should we use the W version? though I guess we could do it on a different
> patch

There are A and W versions because there's an GetMonitorInfoEx[A|W]() function that fills up MONITORINFOEX[A|W] structure, which does have a string in it.
MONITORINFO doesn't, so it doesn't matter which kind of function we're using.

> @@ +2862,3 @@
> +  {
> +    context->native_moveresize_pending = TRUE;
> +    _gdk_win32_do_emit_configure_event (window, new_rect);
> 
> why do we emit it here? is the size to change already at this point? I guess
> the magic happens on the end_paint so you just do the configure here but the
> actual size/position changes on the end_paint?

Yes. When cursor drag triggers a size change, we don't just change the window size, we generate a configure event instead and then wait for a redraw, THEN change the size, to ensure that underlying window size change and contents update happen at the same time.
(this didn't really work out all that well, obviously, but it is required for the layered windows patch)
Except for when there's no size change, just position change, in which case we just move the window right away.

> 
> @@ +2917,3 @@
> +
> +  if (impl->dragmoveresize_context.op != GDK_WIN32_DRAGOP_NONE)
> +    gdk_win32_window_end_moveresize_drag (window);
> 
> why do we need to end the move_resize_drag? I would expect this to happen
> when end_resize_drag is called...
This is mostly cosmetic/symmetry stuff. If there's a drag op already in place, and we begin another one, we should wrap up the one we have first. Granted, gdk_win32_window_end_moveresize_drag() doesn't really do much, as moveresize drag haven't got any context to clean up, but still...

> ::: gdk/win32/gdkwindow-win32.h
> @@ +69,3 @@
> +
> +  /* Not used */
> +  gint               button;
> 
> if not used do we need it?

I just stashed all the data we get at the beginning of the drag into the context. Maybe some of it will become useful for something. Like, dragging with right or middle mouse button. Dunno.
Comment 9 LRN 2016-02-23 14:03:09 UTC
Created attachment 321961 [details] [review]
GDK W32: custom (non-WM) drag-move and drag-resize code

v3:
* Fixed behaviour in multi-monitor environment (incorrect
  handling of _gdk_offset_*)
* Fixed most nitpicks

For the record, i think that indentation rules in GTK are ridiculous.
I mean, just look at that switch...
Comment 10 LRN 2016-02-24 02:45:08 UTC
Created attachment 322199 [details] [review]
GDK W32: custom (non-WM) drag-move and drag-resize code

v4:
* fixed a few typos
Comment 11 Ignacio Casal Quinteiro (nacho) 2016-02-24 07:21:40 UTC
Review of attachment 322199 [details] [review]:

A couple more nitpicks :)

::: gdk/win32/gdkevents-win32.c
@@ +1448,3 @@
+
+void
+_gdk_win32_do_emit_configure_event (GdkWindow *window, RECT rect)

params should be split

::: gdk/win32/gdkprivate-win32.h
@@ +538,3 @@
+void      gdk_win32_window_do_move_resize_drag  (GdkWindow *window, gint x, gint y);
+void      gdk_win32_window_end_move_resize_drag (GdkWindow *window);
+gboolean _gdk_win32_window_fill_min_max_info    (GdkWindow *window, MINMAXINFO *mmi);

in the prototypes the params should be split as well
Comment 12 Alexander Larsson 2016-02-24 10:22:31 UTC
Review of attachment 322199 [details] [review]:

Its possible as a programmer to automatically trigger these things, using gtk_window_begin_resize_drag(). 
For instance, the "resize grips" test in tests/testgtk call these. Can you try those for a non-csd window?

Otherwise, from a quick glance this looks sane to me. Of course, i did zero testing, so it may be completely busted :)

::: gdk/win32/gdkwindow-win32.c
@@ +211,3 @@
+	                   x - _gdk_offset_x, y - _gdk_offset_y,
+                           w, h,
+	                   SWP_NOACTIVATE | SWP_NOZORDER));

I wonder if you should also pass SWP_NOREDRAW, as we're going to paint it anyway in a second.
Comment 13 LRN 2016-02-24 13:49:29 UTC
Works on non-CSD windows, although not without issues (fixing these...i think it's about window size vs client area size).
I also found a bug in MINMAXINFO handling, will fix that one as well.

I'll try SWP_NOREDRAW.
Comment 14 LRN 2016-02-24 16:13:41 UTC
Created attachment 322254 [details] [review]
GDK W32: custom (non-WM) drag-move and drag-resize code

v5:
* Clarified that _gdk_win32_get_window_rect() gives client rectangle.
* Use primary display for maximized window size calculation when
  filling MINMAXINFO (actual maximization, is handled elsewhere and
  does not seem to use this size).
* Use SM_CXMAXTRACK and SM_CYMAXTRACK for maximal non-maximized
  size when filling MINMAXINFO, as MSDN advices. This is the size
  of the virtual desktop.
* Fix more nitpicks.
* Call _gdk_win32_adjust_client_rect() before resizing/moving
  windows. Now customresize code should work correctly on non-CSD
  windows (when it *is* used on non-CSD windows - it requires
  explicit GDK resize calls, i.e. from resize grips test in testgtk;
  normally native W32 WM resizer is used for non-CSD windows).
* Memset MINMAXINFO to 0 when not handling WM_GETMINMAXINFO
* Use SWP_NOREDRAW flag when resizing a window shortly before redraw.
Comment 15 LRN 2016-02-24 23:25:50 UTC
Created attachment 322315 [details] [review]
GDK W32: custom (non-WM) drag-move and drag-resize code

v6:
* Split off minmaxinfo-related stuff into separate patch (bug 762629)
Comment 16 LRN 2016-03-01 03:19:18 UTC
Created attachment 322715 [details] [review]
GDK W32: custom (non-WM) drag-move and drag-resize code

v7:
* Removed a GDK_NOTE() that spammed too much
* Updated commit message to reflect actual changes, behaviour
  and future plans

This is the last version of this patch (possible nitpicks aside),
any other changes will go into separate patches
Comment 17 Paolo Borelli 2016-03-01 11:40:17 UTC
Reporting from the IRC conversation:

I think we should get this in sooner rather than later: the patch adds non trivial amount of code to the win32 backend, but it fixes real issues and does not causes regressions for the users (CSD windows was not working with half-maximixe etc even before). The patch is also somewhat self contained and easy to disable.

If someone with intimate knowledge of GDK wants to weight in, all comments are very welcome, but on the Win32 side I do not think we have anyone that is able to dig into this issue deeper than LRN, so IMHO we should not block.


One thing that was mentioned on IRC: before committing let's test on Win10
Comment 18 LRN 2016-03-01 22:45:24 UTC
Limited Windows 10 testing shows that this code and the code from other related patches do work as intended.
Comment 19 Fan, Chun-wei 2016-03-02 02:04:09 UTC
Hi,

I would say, first of all, a great thanks to LRN for this.

+1 for Paolo--I think it is better to get this code in, and let more people try this out (I will test build it with Visual Studio to see whether there are any issues), and let them see if there are any outstanding issues involved.

I don't have Windows 10 though, so I can't say anything about it, but I will see how things go on Windows 7 and 8.1.

My take on this.  With blessings!
Comment 20 LRN 2016-03-02 21:42:15 UTC
Attachment 322715 [details] pushed as e03946b - GDK W32: custom (non-WM) drag-move and drag-resize code