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;
};
/**
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.
*/
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).
*
} 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. */
new->current_border_width = -1;
if (window) {
new->depth = window->depth;
- new->window->aspect_ratio = 0.0;
} else {
new->depth = root_depth;
}
}
/*
- * 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;
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;
}
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;
}
}
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
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 */
}
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)
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)
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);
}
}
- floating_check_size(json_node);
+ floating_check_size(json_node, false);
}
if (num_marks > 0) {
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);
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;
}
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/…
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
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);
}
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).
*
#
# 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;