/** 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;
*/
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
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
*/
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
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.
*
*/
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).
*/
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);
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;
/* 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 */
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 */
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])) {
/* 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
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;
+}
#!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);
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
#####################################################################
$i3->command("workspace $tmp")->recv;
is($x->input_focus, $child->id, "Child window focused");
+
+}