From 5c2758af26587b04eb3ac22f3a7fb5f8a89a805f Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Mon, 11 Oct 2010 21:32:04 +0200 Subject: [PATCH] Implement support for size hints (including test case) --- include/all.h | 1 + include/data.h | 13 +++ include/handlers.h | 2 +- src/handlers.c | 180 +++++++++++++++++------------------- src/main.c | 3 + src/manage.c | 2 +- src/render.c | 38 ++++++++ testcases/t/33-size-hints.t | 44 +++++++++ 8 files changed, 185 insertions(+), 98 deletions(-) create mode 100644 testcases/t/33-size-hints.t diff --git a/include/all.h b/include/all.h index 5851141e..2096016d 100644 --- a/include/all.h +++ b/include/all.h @@ -22,6 +22,7 @@ #include #include #include +#include #include #include diff --git a/include/data.h b/include/data.h index 8341e5eb..3d32a396 100644 --- a/include/data.h +++ b/include/data.h @@ -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 diff --git a/include/handlers.h b/include/handlers.h index 73cafe4b..b4a9486a 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -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. * diff --git a/src/handlers.c b/src/handlers.c index 500bdb7f..0e497b04 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -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. diff --git a/src/main.c b/src/main.c index 2fe64b80..a0eb9192 100644 --- a/src/main.c +++ b/src/main.c @@ -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 */ diff --git a/src/manage.c b/src/manage.c index 79200882..134abe9e 100644 --- a/src/manage.c +++ b/src/manage.c @@ -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); diff --git a/src/render.c b/src/render.c index 943ec895..e40b43eb 100644 --- a/src/render.c +++ b/src/render.c @@ -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 index 00000000..c4bd72af --- /dev/null +++ b/testcases/t/33-size-hints.t @@ -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" ); -- 2.39.5