Implement support for size hints (including test case)
This commit is contained in:
parent
0925e8b7dc
commit
5c2758af26
|
@ -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>
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
200
src/handlers.c
200
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);
|
||||
|
||||
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.
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
38
src/render.c
38
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 */
|
||||
|
|
|
@ -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" );
|
Loading…
Reference in New Issue