Implement support for size hints (including test case)

This commit is contained in:
Michael Stapelberg 2010-10-11 21:32:04 +02:00
parent 0925e8b7dc
commit 5c2758af26
8 changed files with 195 additions and 108 deletions

View 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>

View 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

View 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.
*

View 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);
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);
}
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;
}
if (changed)
DLOG("resize increments changed\n");
}
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 (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);
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);
/* Sanity checks, this is user-input, in a way */
if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0)
return 1;
/* 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;
client->force_reconfigure = true;
if (client->container != NULL) {
render_container(conn, client->container);
xcb_flush(conn);
}
Con *con = con_by_window_id(window);
if (con == NULL) {
DLOG("Received WM_NORMAL_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, con->window->id), &size_hints, NULL);
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);
}
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 (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;
}
if (changed)
DLOG("resize increments changed\n");
}
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 (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 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;
}
/* 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.

View 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 managers name */

View 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);

View 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 */

View File

@ -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" );