implement support for WM_TRANSIENT_FOR, expand testcase

This commit is contained in:
Michael Stapelberg 2010-11-13 01:19:21 +01:00
parent ad9be5402a
commit 432073dbe5
8 changed files with 153 additions and 33 deletions

View File

@ -215,6 +215,7 @@ struct Window {
/** Holds the xcb_window_t (just an ID) for the leader window (logical /** Holds the xcb_window_t (just an ID) for the leader window (logical
* parent for toolwindows and similar floating windows) */ * parent for toolwindows and similar floating windows) */
xcb_window_t leader; xcb_window_t leader;
xcb_window_t transient_for;
char *class_class; char *class_class;
char *class_instance; char *class_instance;

View File

@ -179,7 +179,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state,
*/ */
int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, int handle_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);
#if 0
/** /**
* Handles the transient for hints set by a window, signalizing that this * Handles the transient for hints set by a window, signalizing that this
@ -191,7 +190,6 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, int handle_transient_for(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 changes of the WM_CLIENT_LEADER atom which specifies if this is a * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a

View File

@ -30,4 +30,10 @@ void window_update_name_legacy(i3Window *win, xcb_get_property_reply_t *prop);
*/ */
void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop); void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop);
/**
* Updates the TRANSIENT_FOR (logical parent window).
*
*/
void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop);
#endif #endif

View File

@ -811,8 +811,6 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
return 1; return 1;
} }
#if 0
/* /*
* Handles the transient for hints set by a window, signalizing that this window is a popup window * Handles the transient for hints set by a window, signalizing that this window is a popup window
* for some other window. * for some other window.
@ -821,33 +819,34 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t
* *
*/ */
int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, int handle_transient_for(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 *prop) {
Client *client = table_get(&by_child, window); Con *con;
if (client == NULL) {
DLOG("No such client\n");
return 1;
}
xcb_window_t transient_for;
if (reply != NULL) {
if (!xcb_get_wm_transient_for_from_reply(&transient_for, reply))
return 1;
} else {
if (!xcb_get_wm_transient_for_reply(conn, xcb_get_wm_transient_for_unchecked(conn, window),
&transient_for, NULL))
return 1;
}
if (client->floating == FLOATING_AUTO_OFF) {
DLOG("This is a popup window, putting into floating\n");
toggle_floating_mode(conn, client, true);
}
if ((con = con_by_window_id(window)) == NULL || con->window == NULL) {
DLOG("No such window\n");
return 1; return 1;
} }
if (prop == NULL) {
prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
false, window, WM_TRANSIENT_FOR, WINDOW, 0, 32), NULL);
if (prop == NULL)
return 1;
}
window_update_transient_for(con->window, prop);
// TODO: put window in floating mode if con->window->transient_for != XCB_NONE:
#if 0
if (client->floating == FLOATING_AUTO_OFF) {
DLOG("This is a popup window, putting into floating\n");
toggle_floating_mode(conn, client, true);
}
#endif #endif
return 1;
}
/* /*
* Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a
* toolwindow (or similar) and to which window it belongs (logical parent). * toolwindow (or similar) and to which window it belongs (logical parent).
@ -855,6 +854,10 @@ int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_
*/ */
int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
xcb_atom_t name, xcb_get_property_reply_t *prop) { xcb_atom_t name, xcb_get_property_reply_t *prop) {
Con *con;
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
return 1;
if (prop == NULL) { if (prop == NULL) {
prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL); false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL);
@ -862,10 +865,6 @@ int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state
return 1; return 1;
} }
Con *con;
if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
return 1;
window_update_leader(con->window, prop); window_update_leader(con->window, prop);
return 1; return 1;

View File

@ -270,6 +270,9 @@ int main(int argc, char *argv[]) {
/* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */ /* Watch WM_CLIENT_LEADER (= logical parent window for toolbars etc.) */
xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL); xcb_property_set_handler(&prophs, atoms[WM_CLIENT_LEADER], UINT_MAX, handle_clientleader_change, NULL);
/* Watch WM_TRANSIENT_FOR property (to which client this popup window belongs) */
xcb_property_set_handler(&prophs, WM_TRANSIENT_FOR, UINT_MAX, handle_transient_for, 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 managers name */ /* Set up the window managers name */

View File

@ -79,13 +79,14 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie, xcb_get_property_cookie_t wm_type_cookie, strut_cookie, state_cookie,
utf8_title_cookie, title_cookie, utf8_title_cookie, title_cookie,
class_cookie, leader_cookie; class_cookie, leader_cookie, transient_cookie;
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); wm_type_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
strut_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); strut_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
state_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STATE], UINT32_MAX); state_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_STATE], UINT32_MAX);
utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_NAME], 128); utf8_title_cookie = xcb_get_any_property_unchecked(conn, false, window, atoms[_NET_WM_NAME], 128);
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);
transient_cookie = xcb_get_any_property_unchecked(conn, false, window, WM_TRANSIENT_FOR, 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 */ /* TODO: also get wm_normal_hints here. implement after we got rid of xcb-event */
@ -145,6 +146,7 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL)); window_update_name_legacy(cwindow, xcb_get_property_reply(conn, title_cookie, NULL));
window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL)); window_update_name(cwindow, xcb_get_property_reply(conn, utf8_title_cookie, NULL));
window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL)); window_update_leader(cwindow, xcb_get_property_reply(conn, leader_cookie, NULL));
window_update_transient_for(cwindow, xcb_get_property_reply(conn, transient_cookie, NULL));
xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, wm_type_cookie, NULL); xcb_get_property_reply_t *reply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DOCK])) { if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DOCK])) {
@ -185,12 +187,19 @@ void manage_window(xcb_window_t window, xcb_get_window_attributes_cookie_t cooki
/* set floating if necessary */ /* set floating if necessary */
bool want_floating = false;
if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DIALOG]) || if (xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_DIALOG]) ||
xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_UTILITY]) || xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_UTILITY]) ||
xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_TOOLBAR]) || xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_TOOLBAR]) ||
xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_SPLASH])) { xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_SPLASH])) {
LOG("This window is a dialog window, setting floating\n"); LOG("This window is a dialog window, setting floating\n");
want_floating = true;
}
if (cwindow->transient_for != XCB_NONE)
want_floating = true;
if (want_floating) {
nc->rect.x = geom->x; nc->rect.x = geom->x;
nc->rect.y = geom->y; nc->rect.y = geom->y;
/* We respect the geometry wishes of floating windows, as long as they /* We respect the geometry wishes of floating windows, as long as they

View File

@ -115,3 +115,22 @@ void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) {
win->leader = *leader; win->leader = *leader;
} }
/**
* Updates the TRANSIENT_FOR (logical parent window).
*
*/
void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop) {
if (prop == NULL || xcb_get_property_value_length(prop) == 0) {
DLOG("prop == NULL\n");
return;
}
xcb_window_t transient_for;
if (!xcb_get_wm_transient_for_from_reply(&transient_for, prop))
return;
DLOG("Transient for changed to %08x\n", transient_for);
win->transient_for = transient_for;
}

View File

@ -1,7 +1,7 @@
#!perl #!perl
# vim:ts=4:sw=4:expandtab # vim:ts=4:sw=4:expandtab
use i3test tests => 3; use i3test tests => 7;
use X11::XCB qw(:all); use X11::XCB qw(:all);
use Time::HiRes qw(sleep); use Time::HiRes qw(sleep);
@ -15,6 +15,89 @@ my $i3 = i3("/tmp/nestedcons");
my $tmp = get_unused_workspace(); my $tmp = get_unused_workspace();
$i3->command("workspace $tmp")->recv; $i3->command("workspace $tmp")->recv;
####################################################################################
# first part: test if a floating window will be correctly positioned above its leader
#
# This is verified by opening two windows, then opening a floating window above the
# right one, then above the left one. If the floating windows are all positioned alike,
# one of both (depending on your screen resolution) will be positioned wrong.
####################################################################################
my $left = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [0, 0, 30, 30],
background_color => '#FF0000',
);
$left->name('Left');
$left->map;
my $right = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [0, 0, 30, 30],
background_color => '#FF0000',
);
$right->name('Right');
$right->map;
sleep 0.25;
my ($abs, $rgeom) = $right->rect;
my $child = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#C0C0C0',
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
);
$child->name('Child window');
$child->client_leader($right);
$child->map;
sleep 0.25;
my $cgeom;
($abs, $cgeom) = $child->rect;
cmp_ok($cgeom->x, '>=', $rgeom->x, 'Child X >= right container X');
my $child2 = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30 ],
background_color => '#C0C0C0',
window_type => $x->atom(name => '_NET_WM_WINDOW_TYPE_UTILITY'),
);
$child2->name('Child window 2');
$child2->client_leader($left);
$child2->map;
sleep 0.25;
($abs, $cgeom) = $child2->rect;
cmp_ok(($cgeom->x + $cgeom->width), '<', $rgeom->x, 'child above left window');
# check wm_transient_for
my $fwindow = $x->root->create_child(
class => WINDOW_CLASS_INPUT_OUTPUT,
rect => [ 0, 0, 30, 30],
background_color => '#FF0000',
);
$fwindow->transient_for($right);
$fwindow->map;
sleep 0.25;
my ($absolute, $top) = $fwindow->rect;
ok($absolute->{x} != 0 && $absolute->{y} != 0, 'i3 did not map it to (0x0)');
SKIP: {
skip "(workspace placement by client_leader not yet implemented)", 3;
##################################################################### #####################################################################
# Create a parent window # Create a parent window
##################################################################### #####################################################################
@ -55,3 +138,5 @@ isnt($x->input_focus, $child->id, "Child window focused");
$i3->command("workspace $tmp")->recv; $i3->command("workspace $tmp")->recv;
is($x->input_focus, $child->id, "Child window focused"); is($x->input_focus, $child->id, "Child window focused");
}