From 432073dbe5f89660f727789ee824d9a0b05479da Mon Sep 17 00:00:00 2001 From: Michael Stapelberg Date: Sat, 13 Nov 2010 01:19:21 +0100 Subject: [PATCH] implement support for WM_TRANSIENT_FOR, expand testcase --- include/data.h | 1 + include/handlers.h | 2 - include/window.h | 6 +++ src/handlers.c | 53 ++++++++++----------- src/main.c | 3 ++ src/manage.c | 11 ++++- src/window.c | 19 ++++++++ testcases/t/14-client-leader.t | 87 +++++++++++++++++++++++++++++++++- 8 files changed, 151 insertions(+), 31 deletions(-) diff --git a/include/data.h b/include/data.h index 7acbb52c..d34fd733 100644 --- a/include/data.h +++ b/include/data.h @@ -215,6 +215,7 @@ struct Window { /** Holds the xcb_window_t (just an ID) for the leader window (logical * parent for toolwindows and similar floating windows) */ xcb_window_t leader; + xcb_window_t transient_for; char *class_class; char *class_instance; diff --git a/include/handlers.h b/include/handlers.h index 80096e2e..71e2bc92 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -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, xcb_atom_t name, xcb_get_property_reply_t *reply); -#if 0 /** * 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, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply); -#endif /** * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a diff --git a/include/window.h b/include/window.h index 071f4e28..6621a169 100644 --- a/include/window.h +++ b/include/window.h @@ -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); +/** + * Updates the TRANSIENT_FOR (logical parent window). + * + */ +void window_update_transient_for(i3Window *win, xcb_get_property_reply_t *prop); + #endif diff --git a/src/handlers.c b/src/handlers.c index d8b230f8..72cc68c5 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -811,8 +811,6 @@ int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t return 1; } -#if 0 - /* * Handles the transient for hints set by a window, signalizing that this window is a popup 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, - xcb_atom_t name, xcb_get_property_reply_t *reply) { - Client *client = table_get(&by_child, window); - if (client == NULL) { - DLOG("No such client\n"); - return 1; - } + xcb_atom_t name, xcb_get_property_reply_t *prop) { + Con *con; - xcb_window_t transient_for; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) { + DLOG("No such window\n"); + return 1; + } - 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 (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; + } - if (client->floating == FLOATING_AUTO_OFF) { - DLOG("This is a popup window, putting into floating\n"); - toggle_floating_mode(conn, client, true); - } + window_update_transient_for(con->window, prop); - return 1; -} + // 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 + return 1; +} + /* * 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). @@ -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, 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) { prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, 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; } - Con *con; - if ((con = con_by_window_id(window)) == NULL || con->window == NULL) - return 1; - window_update_leader(con->window, prop); return 1; diff --git a/src/main.c b/src/main.c index 6ca3075e..787ff6fb 100644 --- a/src/main.c +++ b/src/main.c @@ -270,6 +270,9 @@ int main(int argc, char *argv[]) { /* 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); + /* 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 */ 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 0fb2fc9b..0307ea7a 100644 --- a/src/manage.c +++ b/src/manage.c @@ -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, 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); 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); 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); + 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); 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 */ @@ -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(cwindow, xcb_get_property_reply(conn, utf8_title_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); 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 */ + bool want_floating = false; 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_TOOLBAR]) || xcb_reply_contains_atom(reply, atoms[_NET_WM_WINDOW_TYPE_SPLASH])) { 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.y = geom->y; /* We respect the geometry wishes of floating windows, as long as they diff --git a/src/window.c b/src/window.c index e22fb1c0..1cf167d8 100644 --- a/src/window.c +++ b/src/window.c @@ -115,3 +115,22 @@ void window_update_leader(i3Window *win, xcb_get_property_reply_t *prop) { 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; +} diff --git a/testcases/t/14-client-leader.t b/testcases/t/14-client-leader.t index fb330d96..ef488f5a 100644 --- a/testcases/t/14-client-leader.t +++ b/testcases/t/14-client-leader.t @@ -1,7 +1,7 @@ #!perl # vim:ts=4:sw=4:expandtab -use i3test tests => 3; +use i3test tests => 7; use X11::XCB qw(:all); use Time::HiRes qw(sleep); @@ -15,6 +15,89 @@ my $i3 = i3("/tmp/nestedcons"); my $tmp = get_unused_workspace(); $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 ##################################################################### @@ -55,3 +138,5 @@ isnt($x->input_focus, $child->id, "Child window focused"); $i3->command("workspace $tmp")->recv; is($x->input_focus, $child->id, "Child window focused"); + +} -- 2.39.5