]> git.sur5r.net Git - i3/i3/commitdiff
Implement support for size hints (including test case)
authorMichael Stapelberg <michael@stapelberg.de>
Mon, 11 Oct 2010 19:32:04 +0000 (21:32 +0200)
committerMichael Stapelberg <michael@stapelberg.de>
Mon, 11 Oct 2010 19:32:29 +0000 (21:32 +0200)
include/all.h
include/data.h
include/handlers.h
src/handlers.c
src/main.c
src/manage.c
src/render.c
testcases/t/33-size-hints.t [new file with mode: 0644]

index 5851141e65bdb0225d8cb1294f9c8507646741c6..2096016d3291c252e982a2285ec5c91ee7c361a1 100644 (file)
@@ -22,6 +22,7 @@
 #include <errno.h>
 #include <err.h>
 #include <stdint.h>
+#include <math.h>
 
 #include <xcb/xcb.h>
 #include <xcb/xcb_aux.h>
index 8341e5eb0b00b1a767332f50d30acbf2ef0e007b..3d32a396e3bfe37f081a8571fc111c9ced92d820 100644 (file)
@@ -283,6 +283,19 @@ struct Con {
 
     double percent;
 
+    /* proportional width/height, calculated from WM_NORMAL_HINTS, used to
+     * apply an aspect ratio to windows (think of MPlayer) */
+    int proportional_width;
+    int proportional_height;
+    /* the wanted size of the window, used in combination with size
+     * increments (see below). */
+    int base_width;
+    int base_height;
+
+    /* minimum increment size specified for the window (in pixels) */
+    int width_increment;
+    int height_increment;
+
     struct Window *window;
 
     /* Should this container be marked urgent? This gets set when the window
index 73cafe4b42a44b4fd15eb0f1586ad08599ab9251..b4a9486a22eb609e77d05e521c303e4eed3f8b8f 100644 (file)
@@ -158,6 +158,7 @@ int handle_client_message(void *data, xcb_connection_t *conn,
 int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state,
                        xcb_window_t window, xcb_atom_t atom,
                        xcb_get_property_reply_t *property);
+#endif
 
 /**
  * Handles the size hints set by a window, but currently only the part
@@ -171,7 +172,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state,
                         xcb_window_t window, xcb_atom_t name,
                         xcb_get_property_reply_t *reply);
 
-#endif
 /**
  * Handles the WM_HINTS property for extracting the urgency state of the window.
  *
index 500bdb7f64c7fb910bc469bba7fa2703a6871c70..0e497b042e44c27c81cd5cbe3c0cbbcf6249675e 100644 (file)
@@ -693,6 +693,7 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
         ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
         return 0;
 }
+#endif
 
 /*
  * Handles the size hints set by a window, but currently only the part necessary for displaying
@@ -703,114 +704,101 @@ int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_wi
  */
 int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
                         xcb_atom_t name, xcb_get_property_reply_t *reply) {
-        Client *client = table_get(&by_child, window);
-        if (client == NULL) {
-                DLOG("Received WM_SIZE_HINTS for unknown client\n");
-                return 1;
-        }
-        xcb_size_hints_t size_hints;
-
-        CLIENT_LOG(client);
-
-        /* If the hints were already in this event, use them, if not, request them */
-        if (reply != NULL)
-                xcb_get_wm_size_hints_from_reply(&size_hints, reply);
-        else
-                xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, client->child), &size_hints, NULL);
+    Con *con = con_by_window_id(window);
+    if (con == NULL) {
+        DLOG("Received WM_NORMAL_HINTS for unknown client\n");
+        return 1;
+    }
 
-        if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
-                // TODO: Minimum size is not yet implemented
-                DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
-        }
+    xcb_size_hints_t size_hints;
 
-        bool changed = false;
-        if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) {
-                if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
-                        if (client->width_increment != size_hints.width_inc) {
-                                client->width_increment = size_hints.width_inc;
-                                changed = true;
-                        }
-                if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
-                        if (client->height_increment != size_hints.height_inc) {
-                                client->height_increment = size_hints.height_inc;
-                                changed = true;
-                        }
+        //CLIENT_LOG(client);
 
-                if (changed)
-                        DLOG("resize increments changed\n");
-        }
+    /* If the hints were already in this event, use them, if not, request them */
+    if (reply != NULL)
+        xcb_get_wm_size_hints_from_reply(&size_hints, reply);
+    else
+        xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL);
 
-        int base_width = 0, base_height = 0;
-
-        /* base_width/height are the desired size of the window.
-           We check if either the program-specified size or the program-specified
-           min-size is available */
-        if (size_hints.flags & XCB_SIZE_HINT_BASE_SIZE) {
-                base_width = size_hints.base_width;
-                base_height = size_hints.base_height;
-        } else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) {
-                /* TODO: is this right? icccm says not */
-                base_width = size_hints.min_width;
-                base_height = size_hints.min_height;
-        }
+    if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) {
+        // TODO: Minimum size is not yet implemented
+        DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
+    }
 
-        if (base_width != client->base_width ||
-            base_height != client->base_height) {
-                client->base_width = base_width;
-                client->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);
+    bool changed = false;
+    if ((size_hints.flags & XCB_SIZE_HINT_P_RESIZE_INC)) {
+        if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
+            if (con->width_increment != size_hints.width_inc) {
+                con->width_increment = size_hints.width_inc;
                 changed = true;
-        }
-
-        if (changed) {
-                if (client->fullscreen)
-                        DLOG("Not resizing client, it is in fullscreen mode\n");
-                else {
-                        resize_client(conn, client);
-                        xcb_flush(conn);
-                }
-        }
-
-        /* If no aspect ratio was set or if it was invalid, we ignore the hints */
-        if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) ||
-            (size_hints.min_aspect_num <= 0) ||
-            (size_hints.min_aspect_den <= 0)) {
-                return 1;
-        }
-
-        double width = client->rect.width - base_width;
-        double height = client->rect.height - base_height;
-        /* 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.min_aspect_den;
-
-        DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
-        DLOG("width = %f, height = %f\n", width, height);
+            }
+        if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
+            if (con->height_increment != size_hints.height_inc) {
+                con->height_increment = size_hints.height_inc;
+                changed = true;
+            }
 
-        /* Sanity checks, this is user-input, in a way */
-        if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0)
-                return 1;
+        if (changed)
+            DLOG("resize increments changed\n");
+    }
 
-        /* Check if we need to set proportional_* variables using the correct ratio */
-        if ((width / height) < min_aspect) {
-                client->proportional_width = width;
-                client->proportional_height = width / min_aspect;
-        } else if ((width / height) > max_aspect) {
-                client->proportional_width = width;
-                client->proportional_height = width / max_aspect;
-        } else return 1;
+    int base_width = 0, base_height = 0;
+
+    /* base_width/height are the desired size of the window.
+       We check if either the program-specified size or the program-specified
+       min-size is available */
+    if (size_hints.flags & XCB_SIZE_HINT_BASE_SIZE) {
+        base_width = size_hints.base_width;
+        base_height = size_hints.base_height;
+    } else if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) {
+        /* TODO: is this right? icccm says not */
+        base_width = size_hints.min_width;
+        base_height = size_hints.min_height;
+    }
 
-        client->force_reconfigure = true;
+    if (base_width != con->base_width ||
+        base_height != con->base_height) {
+        con->base_width = base_width;
+        con->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 (client->container != NULL) {
-                render_container(conn, client->container);
-                xcb_flush(conn);
-        }
+    /* If no aspect ratio was set or if it was invalid, we ignore the hints */
+    if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) ||
+        (size_hints.min_aspect_num <= 0) ||
+        (size_hints.min_aspect_den <= 0)) {
+        goto render_and_return;
+    }
 
-        return 1;
+    /* XXX: do we really use rect here, not window_rect? */
+    double width = con->rect.width - base_width;
+    double height = con->rect.height - base_height;
+    /* 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.min_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 */
+    if ((width / height) < min_aspect) {
+        con->proportional_width = width;
+        con->proportional_height = width / min_aspect;
+    } else if ((width / height) > max_aspect) {
+        con->proportional_width = width;
+        con->proportional_height = width / max_aspect;
+    } else goto render_and_return;
+
+render_and_return:
+    tree_render();
+    return 1;
 }
-#endif
 
 /*
  * Handles the WM_HINTS property for extracting the urgency state of the window.
index 2fe64b807df5b97d9c4c917b6e289a92d9864326..a0eb91922ca820bef9137c338d9e3ceaae1ff210 100644 (file)
@@ -261,6 +261,9 @@ int main(int argc, char *argv[]) {
     /* Watch WM_NAME (title of the window encoded in COMPOUND_TEXT) */
     xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL);
 
+    /* Watch WM_NORMAL_HINTS (aspect ratio, size increments, …) */
+    xcb_property_set_handler(&prophs, WM_NORMAL_HINTS, UINT_MAX, handle_normal_hints, NULL);
+
     /* Set up the atoms we support */
     xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms);
     /* Set up the window manager’s name */
index 792008828ec6c44d7c0eebf307fde7c68a7a371f..134abe9e51f972e125d7288afdab2435d4b68e01 100644 (file)
@@ -88,7 +88,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
     leader_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[WM_CLIENT_LEADER], UINT32_MAX);
     title_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_NAME, 128);
     class_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_CLASS, 128);
-
+    /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */
 
     geomc = xcb_get_geometry(conn, d);
 
index 943ec895c2429eb28a044be3e3c222a52d23c41c..e40b43eb68db2a120b3efba98b2aa17ad263beea 100644 (file)
@@ -47,6 +47,44 @@ void render_con(Con *con) {
         inset->x += 2;
         inset->width -= 2 * 2;
         inset->height -= 2;
+
+        /* Obey the aspect ratio, if any */
+        if (con->proportional_height != 0 &&
+            con->proportional_width != 0) {
+            DLOG("proportional height = %d, width = %d\n", con->proportional_height, con->proportional_width);
+            double new_height = inset->height + 1;
+            int new_width = inset->width;
+
+            while (new_height > inset->height) {
+                new_height = ((double)con->proportional_height / con->proportional_width) * new_width;
+
+                if (new_height > inset->height)
+                    new_width--;
+            }
+            /* Center the window */
+            inset->y += ceil(inset->height / 2) - floor(new_height / 2);
+            inset->x += ceil(inset->width / 2) - floor(new_width / 2);
+
+            inset->height = new_height;
+            inset->width = new_width;
+            DLOG("new_height = %f, new_width = %d\n", new_height, new_width);
+        }
+
+        if (con->height_increment > 1) {
+            int old_height = inset->height;
+            inset->height -= (inset->height - con->base_height) % con->height_increment;
+            DLOG("Lost %d pixel due to client's height_increment (%d px, base_height = %d)\n",
+                old_height - inset->height, con->height_increment, con->base_height);
+        }
+
+        if (con->width_increment > 1) {
+            int old_width = inset->width;
+            inset->width -= (inset->width - con->base_width) % con->width_increment;
+            DLOG("Lost %d pixel due to client's width_increment (%d px, base_width = %d)\n",
+                old_width - inset->width, con->width_increment, con->base_width);
+        }
+
+        DLOG("child will be at %dx%d with size %dx%d\n", inset->x, inset->y, inset->width, inset->height);
     }
 
     /* Check for fullscreen nodes */
diff --git a/testcases/t/33-size-hints.t b/testcases/t/33-size-hints.t
new file mode 100644 (file)
index 0000000..c4bd72a
--- /dev/null
@@ -0,0 +1,44 @@
+#!perl
+# vim:ts=4:sw=4:expandtab
+#
+# Checks if size hints are interpreted correctly.
+#
+use i3test tests => 2;
+use Time::HiRes qw(sleep);
+
+my $i3 = i3("/tmp/nestedcons");
+
+my $x = X11::XCB::Connection->new;
+
+my $tmp = get_unused_workspace();
+$i3->command("workspace $tmp")->recv;
+
+ok(@{get_ws_content($tmp)} == 0, 'no containers yet');
+
+my $win = $x->root->create_child(
+    class => WINDOW_CLASS_INPUT_OUTPUT,
+    rect => X11::XCB::Rect->new(x => 0, y => 0, width => 30, height => 30),
+    background_color => '#C0C0C0',
+);
+
+# 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;
+sleep 0.25;
+$win->hints->aspect($aspect);
+$x->flush;
+
+sleep 0.25;
+
+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');
+
+diag( "Testing i3, Perl $], $^X" );