]> git.sur5r.net Git - i3/i3/commitdiff
Fix aspect ratio bugs
authorOrestis Floros <orestisf1993@gmail.com>
Mon, 1 Oct 2018 16:47:33 +0000 (19:47 +0300)
committerOrestis Floros <orestisf1993@gmail.com>
Mon, 12 Nov 2018 16:45:00 +0000 (18:45 +0200)
- ICCCM says: > If a base size is not provided, the minimum size is to
be used in its place and vice versa.
i3 didn't obey the "vice versa" part. Min size and base size are both
saved without replacements in window_update_normal_hints,
floating_check_size makes the needed replacements if either was not
provided.
- Aspect ratio is now saved correctly in manage_window because
window_update_normal_hints is called.
- i3 didn't save the aspect ratio if the window conformed the given
aspect ratio range when handle_normal_hints was called. If the window
was resized to a size outside of the given bounds, i3 didn't correct it.
- Aspect ratio now affects only tiling windows, like the rest of the
normal size hints
- The aspect ratio calculation is now done without a loop

A real life example of how these changes affect the workflow:
An mpv window, when playing a video, sets its min == max aspect ratio
during mapping. i3 ignored these hints. When resized, the window's
aspect ratio was not preserved. With this commit, resizing floating mpv
windows will always preserve the aspect ratio.

13 files changed:
include/data.h
include/floating.h
include/window.h
src/commands.c
src/con.c
src/floating.c
src/handlers.c
src/load_layout.c
src/manage.c
src/render.c
src/scratchpad.c
src/window.c
testcases/t/133-size-hints.t

index f55e003d0bd2abcea36f20d89cf992fb5f9fab87..d2b501b966bb7e7497ce260dff5d21827e30d17d 100644 (file)
@@ -482,7 +482,8 @@ struct Window {
     int max_height;
 
     /* aspect ratio from WM_NORMAL_HINTS (MPlayer uses this for example) */
-    double aspect_ratio;
+    double min_aspect_ratio;
+    double max_aspect_ratio;
 };
 
 /**
index 368183bc8fab5911dc5c0b884501ca4ba4ee0dda..a7813099c3283117265364a62dc6f23284ee5754 100644 (file)
@@ -94,12 +94,17 @@ void floating_drag_window(Con *con, const xcb_button_press_event_t *event);
 void floating_resize_window(Con *con, const bool proportional, const xcb_button_press_event_t *event);
 
 /**
- * Called when a floating window is created or resized.
- * This function resizes the window if its size is higher or lower than the
- * configured maximum/minimum size, respectively.
+ * Called when a floating window is created or resized.  This function resizes
+ * the window if its size is higher or lower than the configured maximum/minimum
+ * size, respectively or when adjustments are needed to conform to the
+ * configured size increments or aspect ratio limits.
+ *
+ * When prefer_height is true and the window needs to be resized because of the
+ * configured aspect ratio, the width is adjusted first, preserving the previous
+ * height.
  *
  */
-void floating_check_size(Con *floating_con);
+void floating_check_size(Con *floating_con, bool prefer_height);
 
 /**
  * This is the return value of a drag operation like drag_pointer.
index 77e3f48f2b0caca04c08a1ecae328b88256c76f9..b03f9c14e72741795cbd826ab5e3cb08bc488752 100644 (file)
@@ -70,6 +70,12 @@ void window_update_role(i3Window *win, xcb_get_property_reply_t *prop, bool befo
  */
 void window_update_type(i3Window *window, xcb_get_property_reply_t *reply);
 
+/**
+ * Updates the WM_NORMAL_HINTS
+ *
+ */
+bool window_update_normal_hints(i3Window *win, xcb_get_property_reply_t *reply, xcb_get_geometry_reply_t *geom);
+
 /**
  * Updates the WM_HINTS (we only care about the input focus handling part).
  *
index 57dc58b4c1633ca367d0259a4e517f5b5d0a82ef..ee402846a9e68e8025852e47cf08624c6be30744 100644 (file)
@@ -467,7 +467,7 @@ static void cmd_resize_floating(I3_CMD, const char *way, const char *direction_s
     } else {
         floating_con->rect.width += px;
     }
-    floating_check_size(floating_con);
+    floating_check_size(floating_con, orientation == VERT);
 
     /* Did we actually resize anything or did the size constraints prevent us?
      * If we could not resize, exit now to not move the window. */
index 51c2c48bbc8d75f6176a5b9e51e28775a42d7d68..764419dce0ca4745ab4f687f646e81eb04f70db1 100644 (file)
--- a/src/con.c
+++ b/src/con.c
@@ -46,7 +46,6 @@ Con *con_new_skeleton(Con *parent, i3Window *window) {
     new->current_border_width = -1;
     if (window) {
         new->depth = window->depth;
-        new->window->aspect_ratio = 0.0;
     } else {
         new->depth = root_depth;
     }
index 0c7b43b22ea868f7e679a922cc0c41b99c399986..4f2760a7e1013b45518f30291395ec923eb1f069 100644 (file)
@@ -60,12 +60,17 @@ static void floating_set_hint_atom(Con *con, bool floating) {
 }
 
 /*
- * Called when a floating window is created or resized.
- * This function resizes the window if its size is higher or lower than the
- * configured maximum/minimum size, respectively.
+ * Called when a floating window is created or resized.  This function resizes
+ * the window if its size is higher or lower than the configured maximum/minimum
+ * size, respectively or when adjustments are needed to conform to the
+ * configured size increments or aspect ratio limits.
+ *
+ * When prefer_height is true and the window needs to be resized because of the
+ * configured aspect ratio, the width is adjusted first, preserving the previous
+ * height.
  *
  */
-void floating_check_size(Con *floating_con) {
+void floating_check_size(Con *floating_con, bool prefer_height) {
     /* Define reasonable minimal and maximal sizes for floating windows */
     const int floating_sane_min_height = 50;
     const int floating_sane_min_width = 75;
@@ -85,15 +90,22 @@ void floating_check_size(Con *floating_con) {
 
     i3Window *window = focused_con->window;
     if (window != NULL) {
-        if (window->min_width) {
+        /* ICCCM says: If a base size is not provided, the minimum size is to be used in its place
+         * and vice versa. */
+        int min_width = (window->min_width ? window->min_width : window->base_width);
+        int min_height = (window->min_height ? window->min_height : window->base_height);
+        int base_width = (window->base_width ? window->base_width : window->min_width);
+        int base_height = (window->base_height ? window->base_height : window->min_height);
+
+        if (min_width) {
             floating_con->rect.width -= border_rect.width;
-            floating_con->rect.width = max(floating_con->rect.width, window->min_width);
+            floating_con->rect.width = max(floating_con->rect.width, min_width);
             floating_con->rect.width += border_rect.width;
         }
 
-        if (window->min_height) {
+        if (min_height) {
             floating_con->rect.height -= border_rect.height;
-            floating_con->rect.height = max(floating_con->rect.height, window->min_height);
+            floating_con->rect.height = max(floating_con->rect.height, min_height);
             floating_con->rect.height += border_rect.height;
         }
 
@@ -109,18 +121,56 @@ void floating_check_size(Con *floating_con) {
             floating_con->rect.height += border_rect.height;
         }
 
+        /* Obey the aspect ratio, if any, unless we are in fullscreen mode.
+         *
+         * The spec isn’t explicit on whether the aspect ratio hints should be
+         * respected during fullscreen mode. Other WMs such as Openbox don’t do
+         * that, and this post suggests that this is the correct way to do it:
+         * https://mail.gnome.org/archives/wm-spec-list/2003-May/msg00007.html
+         *
+         * Ignoring aspect ratio during fullscreen was necessary to fix MPlayer
+         * subtitle rendering, see https://bugs.i3wm.org/594 */
+        const double min_ar = window->min_aspect_ratio;
+        const double max_ar = window->max_aspect_ratio;
+        if (floating_con->fullscreen_mode == CF_NONE && (min_ar > 0 || max_ar > 0)) {
+            /* The ICCCM says to subtract the base size from the window size for
+             * aspect ratio calculations. However, unlike determining the base
+             * size itself we must not fall back to using the minimum size in
+             * this case according to the ICCCM. */
+            double width = floating_con->rect.width - window->base_width - border_rect.width;
+            double height = floating_con->rect.height - window->base_height - border_rect.height;
+            const double ar = (double)width / (double)height;
+            double new_ar = -1;
+            if (min_ar > 0 && ar < min_ar) {
+                new_ar = min_ar;
+            } else if (max_ar > 0 && ar > max_ar) {
+                new_ar = max_ar;
+            }
+            if (new_ar > 0) {
+                if (prefer_height) {
+                    width = round(height * new_ar);
+                    height = round(width / new_ar);
+                } else {
+                    height = round(width / new_ar);
+                    width = round(height * new_ar);
+                }
+                floating_con->rect.width = width + window->base_width + border_rect.width;
+                floating_con->rect.height = height + window->base_height + border_rect.height;
+            }
+        }
+
         if (window->height_increment &&
-            floating_con->rect.height >= window->base_height + border_rect.height) {
-            floating_con->rect.height -= window->base_height + border_rect.height;
+            floating_con->rect.height >= base_height + border_rect.height) {
+            floating_con->rect.height -= base_height + border_rect.height;
             floating_con->rect.height -= floating_con->rect.height % window->height_increment;
-            floating_con->rect.height += window->base_height + border_rect.height;
+            floating_con->rect.height += base_height + border_rect.height;
         }
 
         if (window->width_increment &&
-            floating_con->rect.width >= window->base_width + border_rect.width) {
-            floating_con->rect.width -= window->base_width + border_rect.width;
+            floating_con->rect.width >= base_width + border_rect.width) {
+            floating_con->rect.width -= base_width + border_rect.width;
             floating_con->rect.width -= floating_con->rect.width % window->width_increment;
-            floating_con->rect.width += window->base_width + border_rect.width;
+            floating_con->rect.width += base_width + border_rect.width;
         }
     }
 
@@ -314,7 +364,7 @@ void floating_enable(Con *con, bool automatic) {
     nc->rect.height += con->border_width * 2;
     nc->rect.width += con->border_width * 2;
 
-    floating_check_size(nc);
+    floating_check_size(nc, false);
 
     /* Some clients (like GIMP’s color picker window) get mapped
      * to (0, 0), so we push them to a reasonable position
@@ -612,7 +662,7 @@ DRAGGING_CB(resize_window_callback) {
     con->rect = (Rect){dest_x, dest_y, dest_width, dest_height};
 
     /* Obey window size */
-    floating_check_size(con);
+    floating_check_size(con, false);
 
     /* If not the lower right corner is grabbed, we must also reposition
      * the client by exactly the amount we resized it */
@@ -931,6 +981,7 @@ void floating_resize(Con *floating_con, uint32_t x, uint32_t y) {
     }
     int wi = focused_con->window->width_increment;
     int hi = focused_con->window->height_increment;
+    bool prefer_height = (rect->width == x);
     rect->width = x;
     rect->height = y;
     if (wi)
@@ -938,7 +989,7 @@ void floating_resize(Con *floating_con, uint32_t x, uint32_t y) {
     if (hi)
         rect->height += (hi - 1 - rect->height) % hi;
 
-    floating_check_size(floating_con);
+    floating_check_size(floating_con, prefer_height);
 
     /* If this is a scratchpad window, don't auto center it from now on. */
     if (floating_con->scratchpad_state == SCRATCHPAD_FRESH)
index b96779178c35151fbefa2e6ac159d18f704ab27c..c4b8cd087b16b9ccc9026d152a18647bf089bc6c 100644 (file)
@@ -974,132 +974,14 @@ static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t stat
         return false;
     }
 
-    xcb_size_hints_t size_hints;
+    bool changed = window_update_normal_hints(con->window, reply, NULL);
 
-    /* If the hints were already in this event, use them, if not, request them */
-    if (reply != NULL) {
-        xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
-    } else {
-        xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL);
-    }
-
-    int win_width = con->window_rect.width;
-    int win_height = con->window_rect.height;
-
-    if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) {
-        DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
-
-        con->window->min_width = size_hints.min_width;
-        con->window->min_height = size_hints.min_height;
-    }
-
-    if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) {
-        DLOG("Maximum size: %d (width) x %d (height)\n", size_hints.max_width, size_hints.max_height);
-
-        con->window->max_width = size_hints.max_width;
-        con->window->max_height = size_hints.max_height;
-    }
-
-    if (con_is_floating(con)) {
-        win_width = MAX(win_width, con->window->min_width);
-        win_height = MAX(win_height, con->window->min_height);
-        win_width = MIN(win_width, con->window->max_width);
-        win_height = MIN(win_height, con->window->max_height);
-    }
-
-    bool changed = false;
-    if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
-        if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) {
-            if (con->window->width_increment != size_hints.width_inc) {
-                con->window->width_increment = size_hints.width_inc;
-                changed = true;
-            }
-        }
-
-        if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) {
-            if (con->window->height_increment != size_hints.height_inc) {
-                con->window->height_increment = size_hints.height_inc;
-                changed = true;
-            }
-        }
-
-        if (changed) {
-            DLOG("resize increments changed\n");
-        }
-    }
-
-    bool has_base_size = false;
-    int base_width = 0;
-    int base_height = 0;
-
-    /* The base width / height is the desired size of the window. */
-    if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) {
-        base_width = size_hints.base_width;
-        base_height = size_hints.base_height;
-        has_base_size = true;
-    }
-
-    /* If the window didn't specify a base size, the ICCCM tells us to fall
-     * back to the minimum size instead, if available. */
-    if (!has_base_size && size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
-        base_width = size_hints.min_width;
-        base_height = size_hints.min_height;
-    }
-
-    // TODO XXX Should we only do this is the base size is > 0?
-    if (base_width != con->window->base_width || base_height != con->window->base_height) {
-        con->window->base_width = base_width;
-        con->window->base_height = base_height;
-
-        DLOG("client's base_height changed to %d\n", base_height);
-        DLOG("client's base_width changed to %d\n", base_width);
-        changed = true;
-    }
-
-    /* If no aspect ratio was set or if it was invalid, we ignore the hints */
-    if (!(size_hints.flags & XCB_ICCCM_SIZE_HINT_P_ASPECT) ||
-        (size_hints.min_aspect_num <= 0) ||
-        (size_hints.min_aspect_den <= 0)) {
-        goto render_and_return;
-    }
-
-    /* The ICCCM says to subtract the base size from the window size for aspect
-     * ratio calculations. However, unlike determining the base size itself we
-     * must not fall back to using the minimum size in this case according to
-     * the ICCCM. */
-    double width = win_width - base_width * has_base_size;
-    double height = win_height - base_height * has_base_size;
-
-    /* Convert numerator/denominator to a double */
-    double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
-    double max_aspect = (double)size_hints.max_aspect_num / size_hints.max_aspect_den;
-
-    DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
-    DLOG("width = %f, height = %f\n", width, height);
-
-    /* Sanity checks, this is user-input, in a way */
-    if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0) {
-        goto render_and_return;
-    }
-
-    /* Check if we need to set proportional_* variables using the correct ratio */
-    double aspect_ratio = 0.0;
-    if ((width / height) < min_aspect) {
-        aspect_ratio = min_aspect;
-    } else if ((width / height) > max_aspect) {
-        aspect_ratio = max_aspect;
-    } else {
-        goto render_and_return;
-    }
-
-    if (fabs(con->window->aspect_ratio - aspect_ratio) > DBL_EPSILON) {
-        con->window->aspect_ratio = aspect_ratio;
-        changed = true;
-    }
-
-render_and_return:
     if (changed) {
-        tree_render();
+        Con *floating = con_inside_floating(con);
+        if (floating) {
+            floating_check_size(con, false);
+            tree_render();
+        }
     }
 
     FREE(reply);
index b4d2a688383693703d6c0cbb9d52d8db072191ad..003affeee35385ec1dac77a24bf213e0ac8b9f3d 100644 (file)
@@ -152,7 +152,7 @@ static int json_end_map(void *ctx) {
                 }
             }
 
-            floating_check_size(json_node);
+            floating_check_size(json_node, false);
         }
 
         if (num_marks > 0) {
index c4706b0dd20892b4c2493f9344f34ce753e31dac..c222d35178264a6b4e3468f88c3dd89aa5337ec9 100644 (file)
@@ -185,9 +185,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     window_update_hints(cwindow, xcb_get_property_reply(conn, wm_hints_cookie, NULL), &urgency_hint);
     border_style_t motif_border_style = BS_NORMAL;
     window_update_motif_hints(cwindow, xcb_get_property_reply(conn, motif_wm_hints_cookie, NULL), &motif_border_style);
-    xcb_size_hints_t wm_size_hints;
-    if (!xcb_icccm_get_wm_size_hints_reply(conn, wm_normal_hints_cookie, &wm_size_hints, NULL))
-        memset(&wm_size_hints, '\0', sizeof(xcb_size_hints_t));
+    window_update_normal_hints(cwindow, xcb_get_property_reply(conn, wm_normal_hints_cookie, NULL), geom);
     xcb_get_property_reply_t *type_reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
     xcb_get_property_reply_t *state_reply = xcb_get_property_reply(conn, state_cookie, NULL);
 
@@ -437,10 +435,9 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
         xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_TOOLBAR) ||
         xcb_reply_contains_atom(type_reply, A__NET_WM_WINDOW_TYPE_SPLASH) ||
         xcb_reply_contains_atom(state_reply, A__NET_WM_STATE_MODAL) ||
-        (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE &&
-         wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE &&
-         wm_size_hints.min_height == wm_size_hints.max_height &&
-         wm_size_hints.min_width == wm_size_hints.max_width)) {
+        (cwindow->max_width > 0 && cwindow->max_height > 0 &&
+         cwindow->min_height == cwindow->max_height &&
+         cwindow->min_width == cwindow->max_width)) {
         LOG("This window is a dialog window, setting floating\n");
         want_floating = true;
     }
@@ -499,29 +496,6 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     if (cwindow->dock)
         want_floating = false;
 
-    /* Plasma windows set their geometry in WM_SIZE_HINTS. */
-    if ((wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_US_POSITION || wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_POSITION) &&
-        (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_US_SIZE || wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_SIZE)) {
-        DLOG("We are setting geometry according to wm_size_hints x=%d y=%d w=%d h=%d\n",
-             wm_size_hints.x, wm_size_hints.y, wm_size_hints.width, wm_size_hints.height);
-        geom->x = wm_size_hints.x;
-        geom->y = wm_size_hints.y;
-        geom->width = wm_size_hints.width;
-        geom->height = wm_size_hints.height;
-    }
-
-    if (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
-        DLOG("Window specifies minimum size %d x %d\n", wm_size_hints.min_width, wm_size_hints.min_height);
-        nc->window->min_width = wm_size_hints.min_width;
-        nc->window->min_height = wm_size_hints.min_height;
-    }
-
-    if (wm_size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE) {
-        DLOG("Window specifies maximum size %d x %d\n", wm_size_hints.max_width, wm_size_hints.max_height);
-        nc->window->max_width = wm_size_hints.max_width;
-        nc->window->max_height = wm_size_hints.max_height;
-    }
-
     /* Store the requested geometry. The width/height gets raised to at least
      * 75x50 when entering floating mode, which is the minimum size for a
      * window to be useful (smaller windows are usually overlays/toolbars/…
index 8ea21f274d7181b4aed322be00193e5490f3e089..518d436b3849c90fb2204ca1e481864b8687cc03 100644 (file)
@@ -64,35 +64,6 @@ void render_con(Con *con, bool render_fullscreen) {
         inset->width -= (2 * con->border_width);
         inset->height -= (2 * con->border_width);
 
-        /* Obey the aspect ratio, if any, unless we are in fullscreen mode.
-         *
-         * The spec isn’t explicit on whether the aspect ratio hints should be
-         * respected during fullscreen mode. Other WMs such as Openbox don’t do
-         * that, and this post suggests that this is the correct way to do it:
-         * https://mail.gnome.org/archives/wm-spec-list/2003-May/msg00007.html
-         *
-         * Ignoring aspect ratio during fullscreen was necessary to fix MPlayer
-         * subtitle rendering, see https://bugs.i3wm.org/594 */
-        if (!render_fullscreen && con->window->aspect_ratio > 0.0) {
-            DLOG("aspect_ratio = %f, current width/height are %d/%d\n",
-                 con->window->aspect_ratio, inset->width, inset->height);
-            double new_height = inset->height + 1;
-            int new_width = inset->width;
-
-            while (new_height > inset->height) {
-                new_height = (1.0 / con->window->aspect_ratio) * new_width;
-
-                if (new_height > inset->height)
-                    new_width--;
-            }
-            /* Center the window */
-            inset->y += ceil(inset->height / 2) - floor((new_height + .5) / 2);
-            inset->x += ceil(inset->width / 2) - floor(new_width / 2);
-
-            inset->height = new_height + .5;
-            inset->width = new_width;
-        }
-
         /* NB: We used to respect resize increment size hints for tiling
          * windows up until commit 0db93d9 here. However, since all terminal
          * emulators cope with ignoring the size hints in a better way than we
index d564bf3208e1ac7768522db2c9ca9a09295a0a44..a679f20a666dd2f239a39f28c7997aed764615e7 100644 (file)
@@ -196,7 +196,7 @@ bool scratchpad_show(Con *con) {
         Con *output = con_get_output(con);
         con->rect.width = output->rect.width * 0.5;
         con->rect.height = output->rect.height * 0.75;
-        floating_check_size(con);
+        floating_check_size(con, false);
         floating_center(con, con_get_workspace(con)->rect);
     }
 
index 6128255686942f80b74f491a22fb296ef1d47da6..bec4c691cf7d142a945e8a885e0bb4a0b2a969d7 100644 (file)
@@ -272,6 +272,127 @@ void window_update_type(i3Window *window, xcb_get_property_reply_t *reply) {
     run_assignments(window);
 }
 
+/*
+ * Updates the WM_NORMAL_HINTS
+ *
+ */
+bool window_update_normal_hints(i3Window *win, xcb_get_property_reply_t *reply, xcb_get_geometry_reply_t *geom) {
+    bool changed = false;
+    xcb_size_hints_t size_hints;
+
+    /* If the hints were already in this event, use them, if not, request them */
+    bool success;
+    if (reply != NULL) {
+        success = xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
+    } else {
+        success = xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, win->id), &size_hints, NULL);
+    }
+    if (!success) {
+        DLOG("Could not get WM_NORMAL_HINTS\n");
+        return false;
+    }
+
+#define ASSIGN_IF_CHANGED(original, new) \
+    do {                                 \
+        if (original != new) {           \
+            original = new;              \
+            changed = true;              \
+        }                                \
+    } while (0)
+
+    if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) {
+        DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
+
+        ASSIGN_IF_CHANGED(win->min_width, size_hints.min_width);
+        ASSIGN_IF_CHANGED(win->min_height, size_hints.min_height);
+    }
+
+    if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MAX_SIZE)) {
+        DLOG("Maximum size: %d (width) x %d (height)\n", size_hints.max_width, size_hints.max_height);
+
+        int max_width = max(0, size_hints.max_width);
+        int max_height = max(0, size_hints.max_height);
+
+        ASSIGN_IF_CHANGED(win->max_width, max_width);
+        ASSIGN_IF_CHANGED(win->max_height, max_height);
+    } else {
+        DLOG("Clearing maximum size \n");
+
+        ASSIGN_IF_CHANGED(win->max_width, 0);
+        ASSIGN_IF_CHANGED(win->max_height, 0);
+    }
+
+    if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
+        DLOG("Size increments: %d (width) x %d (height)\n", size_hints.width_inc, size_hints.height_inc);
+
+        if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF) {
+            ASSIGN_IF_CHANGED(win->width_increment, size_hints.width_inc);
+        } else {
+            ASSIGN_IF_CHANGED(win->width_increment, 0);
+        }
+
+        if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF) {
+            ASSIGN_IF_CHANGED(win->height_increment, size_hints.height_inc);
+        } else {
+            ASSIGN_IF_CHANGED(win->height_increment, 0);
+        }
+    } else {
+        DLOG("Clearing size increments\n");
+
+        ASSIGN_IF_CHANGED(win->width_increment, 0);
+        ASSIGN_IF_CHANGED(win->height_increment, 0);
+    }
+
+    /* The base width / height is the desired size of the window. */
+    if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE &&
+        (win->base_width >= 0) && (win->base_height >= 0)) {
+        DLOG("Base size: %d (width) x %d (height)\n", size_hints.base_width, size_hints.base_height);
+
+        ASSIGN_IF_CHANGED(win->base_width, size_hints.base_width);
+        ASSIGN_IF_CHANGED(win->base_height, size_hints.base_height);
+    } else {
+        DLOG("Clearing base size\n");
+
+        ASSIGN_IF_CHANGED(win->base_width, 0);
+        ASSIGN_IF_CHANGED(win->base_height, 0);
+    }
+
+    if (geom != NULL &&
+        (size_hints.flags & XCB_ICCCM_SIZE_HINT_US_POSITION || size_hints.flags & XCB_ICCCM_SIZE_HINT_P_POSITION) &&
+        (size_hints.flags & XCB_ICCCM_SIZE_HINT_US_SIZE || size_hints.flags & XCB_ICCCM_SIZE_HINT_P_SIZE)) {
+        DLOG("Setting geometry x=%d y=%d w=%d h=%d\n", size_hints.x, size_hints.y, size_hints.width, size_hints.height);
+        geom->x = size_hints.x;
+        geom->y = size_hints.y;
+        geom->width = size_hints.width;
+        geom->height = size_hints.height;
+    }
+
+    /* If no aspect ratio was set or if it was invalid, we ignore the hints */
+    if (size_hints.flags & XCB_ICCCM_SIZE_HINT_P_ASPECT &&
+        (size_hints.min_aspect_num >= 0) && (size_hints.min_aspect_den > 0) &&
+        (size_hints.max_aspect_num >= 0) && (size_hints.max_aspect_den > 0)) {
+        /* Convert numerator/denominator to a double */
+        double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
+        double max_aspect = (double)size_hints.max_aspect_num / size_hints.max_aspect_den;
+        DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
+        if (fabs(win->min_aspect_ratio - min_aspect) > DBL_EPSILON) {
+            win->min_aspect_ratio = min_aspect;
+            changed = true;
+        }
+        if (fabs(win->max_aspect_ratio - max_aspect) > DBL_EPSILON) {
+            win->max_aspect_ratio = max_aspect;
+            changed = true;
+        }
+    } else {
+        DLOG("Clearing aspect ratios\n");
+
+        ASSIGN_IF_CHANGED(win->min_aspect_ratio, 0.0);
+        ASSIGN_IF_CHANGED(win->max_aspect_ratio, 0.0);
+    }
+
+    return changed;
+}
+
 /*
  * Updates the WM_HINTS (we only care about the input focus handling part).
  *
index a9a43cb52bdf68ab73f2acb36e078437e13785dc..a16c5399eaa8b9a6a095c4cb3831232a3cc20f5c 100644 (file)
 #
 # Checks if size hints are interpreted correctly.
 #
-use i3test;
+use i3test i3_config => <<EOT;
+# i3 config file (v4)
+font -misc-fixed-medium-r-normal--13-120-75-75-C-70-iso10646-1
 
-my $tmp = fresh_workspace;
+default_floating_border none
+floating_minimum_size -1 x -1
+floating_maximum_size -1 x -1
+EOT
 
-ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+sub open_with_aspect {
+    my ($min_num, $min_den, $max_num, $max_den) = @_;
+    open_floating_window(
+        rect => [0, 0, 100, 100],
+        before_map => sub {
+            my ($window) = @_;
+            my $aspect = X11::XCB::Sizehints::Aspect->new;
+            $aspect->min_num($min_num);
+            $aspect->min_den($min_den);
+            $aspect->max_num($max_num);
+            $aspect->max_den($max_den);
+            $window->hints->aspect($aspect);
+        });
+}
 
-my $win = open_window({ dont_map => 1 });
-# XXX: we should check screen size. in screens with an AR of 2.0,
-# this is not a good idea.
-my $aspect = X11::XCB::Sizehints::Aspect->new;
-$aspect->min_num(600);
-$aspect->min_den(300);
-$aspect->max_num(600);
-$aspect->max_den(300);
-$win->_create;
-$win->map;
-wait_for_map $win;
-$win->hints->aspect($aspect);
-$x->flush;
+################################################################################
+# Test aspect ratio set exactly to 2.0
+################################################################################
 
-sync_with_i3;
+fresh_workspace;
+my $win = open_with_aspect(600, 300, 600, 300);
 
 my $rect = $win->rect;
 my $ar = $rect->width / $rect->height;
-diag("Aspect ratio = $ar");
-ok(($ar > 1.90) && ($ar < 2.10), 'Aspect ratio about 2.0');
+cmp_float($ar, 2, 'Window set to floating with aspect ratio 2.0');
+
+cmd 'resize set 100';
+$rect = $win->rect;
+$ar = $rect->width / $rect->height;
+cmp_float($ar, 2, 'Window resized with aspect ratio kept to 2.0');
+
+cmd 'resize set 400 100';
+$rect = $win->rect;
+$ar = $rect->width / $rect->height;
+cmp_float($ar, 2, 'Window resized with aspect ratio kept to 2.0');
+
+# Also check that it is possible to resize by height only
+cmd 'resize set height 400';
+$rect = $win->rect;
+$ar = $rect->width / $rect->height;
+is($rect->height, 400, 'Window height is 400px');
+cmp_float($ar, 2, 'Window resized with aspect ratio kept to 2.0');
+
+cmd 'resize grow height 10';
+$rect = $win->rect;
+$ar = $rect->width / $rect->height;
+is($rect->height, 410, 'Window grew by 10px');
+cmp_float($ar, 2, 'Window resized with aspect ratio kept to 2.0');
+
+################################################################################
+# Test aspect ratio between 0.5 and 2.0
+################################################################################
+
+fresh_workspace;
+$win = open_with_aspect(1, 2, 2, 1);
+
+$rect = $win->rect;
+$ar = $rect->width / $rect->height;
+cmp_float($ar, 1, 'Window set to floating with aspect ratio 1.0');
+
+cmd 'resize set 200';
+$rect = $win->rect;
+$ar = $rect->width / $rect->height;
+is($rect->width, 200, 'Window width is 200px');
+is($rect->height, 100, 'Window height stayed 100px');
+cmp_float($ar, 2, 'Window resized, aspect ratio changed to 2.0');
+
+cmd 'resize set 100 200';
+$rect = $win->rect;
+$ar = $rect->width / $rect->height;
+is($rect->width, 100, 'Window width is 100px');
+is($rect->height, 200, 'Window height is 200px');
+cmp_float($ar, 0.5, 'Window resized, aspect ratio changed to 0.5');
+
+cmd 'resize set 500';
+$rect = $win->rect;
+$ar = $rect->width / $rect->height;
+cmp_float($ar, 2, 'Window resized, aspect ratio changed to maximum 2.0');
+
+cmd 'resize set 100 400';
+$rect = $win->rect;
+$ar = $rect->width / $rect->height;
+cmp_float($ar, 0.5, 'Window resized, aspect ratio changed to minimum 0.5');
 
 done_testing;