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 <errno.h>
|
||||||
#include <err.h>
|
#include <err.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
|
#include <math.h>
|
||||||
|
|
||||||
#include <xcb/xcb.h>
|
#include <xcb/xcb.h>
|
||||||
#include <xcb/xcb_aux.h>
|
#include <xcb/xcb_aux.h>
|
||||||
|
@ -283,6 +283,19 @@ struct Con {
|
|||||||
|
|
||||||
double percent;
|
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;
|
struct Window *window;
|
||||||
|
|
||||||
/* Should this container be marked urgent? This gets set when the 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,
|
int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state,
|
||||||
xcb_window_t window, xcb_atom_t atom,
|
xcb_window_t window, xcb_atom_t atom,
|
||||||
xcb_get_property_reply_t *property);
|
xcb_get_property_reply_t *property);
|
||||||
|
#endif
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles the size hints set by a window, but currently only the part
|
* 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_window_t window, xcb_atom_t name,
|
||||||
xcb_get_property_reply_t *reply);
|
xcb_get_property_reply_t *reply);
|
||||||
|
|
||||||
#endif
|
|
||||||
/**
|
/**
|
||||||
* Handles the WM_HINTS property for extracting the urgency state of the window.
|
* 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");
|
ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Handles the size hints set by a window, but currently only the part necessary for displaying
|
* 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,
|
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) {
|
xcb_atom_t name, xcb_get_property_reply_t *reply) {
|
||||||
Client *client = table_get(&by_child, window);
|
Con *con = con_by_window_id(window);
|
||||||
if (client == NULL) {
|
if (con == NULL) {
|
||||||
DLOG("Received WM_SIZE_HINTS for unknown client\n");
|
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, 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 1;
|
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.
|
* 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) */
|
/* Watch WM_NAME (title of the window encoded in COMPOUND_TEXT) */
|
||||||
xcb_watch_wm_name(&prophs, 128, handle_windowname_change_legacy, NULL);
|
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 */
|
/* Set up the atoms we support */
|
||||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms);
|
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, root, atoms[_NET_SUPPORTED], ATOM, 32, 7, atoms);
|
||||||
/* Set up the window manager’s name */
|
/* 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);
|
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);
|
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);
|
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);
|
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->x += 2;
|
||||||
inset->width -= 2 * 2;
|
inset->width -= 2 * 2;
|
||||||
inset->height -= 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 */
|
/* Check for fullscreen nodes */
|
||||||
|
44
testcases/t/33-size-hints.t
Normal file
44
testcases/t/33-size-hints.t
Normal 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" );
|
Loading…
x
Reference in New Issue
Block a user