Bugfix: Eliminate race condition, fix dock windows

There was a race condition when mapping a window and not setting the event mask
before. Therefore, the ReparentNotify and (more important) the UnmapNotify generated
by reparenting were not received, thus leaving the awaiting_useless_unmap variable
of the client "true". To just make it work, in previous commits the DestroyNotify
handler was introduced. Fortunately, with fixing this race condition by first
setting the event mask and mapping the window afterwards, we can remove this handler.

As for the dock windows, there were quite some occurences were client->container
was used without checking if the client is inside a container at all.

Furthermore, the client’s strut containing the space to reserve at the screen edge
is now checked and the desired height is set to the window’s height if the strut
contains 0 or if no strut was specified at all.
This commit is contained in:
Michael Stapelberg 2009-03-10 20:56:25 +01:00
parent 434345aa30
commit 408b2bdb39
6 changed files with 74 additions and 64 deletions

View File

@ -204,6 +204,8 @@ struct Client {
/* Backpointer. A client is inside a container */ /* Backpointer. A client is inside a container */
Container *container; Container *container;
/* Because dock clients dont have a container, we have this workspace-backpointer */
Workspace *workspace;
/* x, y, width, height of the frame */ /* x, y, width, height of the frame */
Rect rect; Rect rect;

View File

@ -18,7 +18,6 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_
int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event); int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event);
int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event); int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event);
int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event); int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event);
int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event);
int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event); int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event);
int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop); xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop);

View File

@ -158,7 +158,9 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_
/* Do plausibility checks: This event may be useless for us if it occurs on a window /* Do plausibility checks: This event may be useless for us if it occurs on a window
which is in a stacked container but not the focused one */ which is in a stacked container but not the focused one */
if (client->container->mode == MODE_STACK && client->container->currently_focused != client) { if (client->container != NULL &&
client->container->mode == MODE_STACK &&
client->container->currently_focused != client) {
LOG("Plausibility check says: no\n"); LOG("Plausibility check says: no\n");
return 1; return 1;
} }
@ -410,6 +412,7 @@ int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_eve
wa.u.override_redirect = reply->override_redirect; wa.u.override_redirect = reply->override_redirect;
LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
add_ignore_event(event->sequence); add_ignore_event(event->sequence);
manage_window(prophs, conn, event->window, wa); manage_window(prophs, conn, event->window, wa);
return 1; return 1;
} }
@ -536,13 +539,19 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
} }
} }
if (client->dock) {
LOG("Removing from dock clients\n");
SLIST_REMOVE(&(client->workspace->dock_clients), client, Client, dock_clients);
}
LOG("child of 0x%08x.\n", client->frame); LOG("child of 0x%08x.\n", client->frame);
xcb_reparent_window(conn, client->child, root, 0, 0); xcb_reparent_window(conn, client->child, root, 0, 0);
xcb_destroy_window(conn, client->frame); xcb_destroy_window(conn, client->frame);
xcb_flush(conn); xcb_flush(conn);
table_remove(byParent, client->frame); table_remove(byParent, client->frame);
cleanup_table(conn, client->container->workspace); if (client->container != NULL)
cleanup_table(conn, client->container->workspace);
free(client); free(client);
@ -551,20 +560,6 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti
return 1; return 1;
} }
/*
* This handler makes sure that windows are closed correctly as we drop the first unmap event we
* get because they are sometimes generated by remapping.
*
* FIXME: Rather than providing this event, Id rather know why some applications need to have
* unmap events ignored (like VLCs open dialog, just open it two times) while others dont (GIMP).
* For now, just make it work.
*
*/
int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event) {
LOG("Destroynotify, faking unmapnotify\n");
return handle_unmap_notify_event(data, conn, (xcb_unmap_notify_event_t*)event);
}
/* /*
* Called when a window changes its title * Called when a window changes its title
* *
@ -608,6 +603,10 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
if (old_name != NULL) if (old_name != NULL)
free(old_name); free(old_name);
/* If the client is a dock window, we dont need to render anything */
if (client->dock)
return 1;
if (client->container->mode == MODE_STACK) if (client->container->mode == MODE_STACK)
render_container(conn, client->container); render_container(conn, client->container);
else decorate_window(conn, client, client->frame, client->titlegc, 0); else decorate_window(conn, client, client->frame, client->titlegc, 0);
@ -649,6 +648,11 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *
} }
LOG("got client %s\n", client->name); LOG("got client %s\n", client->name);
if (client->dock) {
LOG("this is a dock\n");
return 1;
}
if (client->container->mode != MODE_STACK) if (client->container->mode != MODE_STACK)
decorate_window(conn, client, client->frame, client->titlegc, 0); decorate_window(conn, client, client->frame, client->titlegc, 0);
else { else {

View File

@ -179,7 +179,7 @@ void decorate_window(xcb_connection_t *conn, Client *client, xcb_drawable_t draw
* *
*/ */
static void reposition_client(xcb_connection_t *conn, Client *client) { static void reposition_client(xcb_connection_t *conn, Client *client) {
LOG("frame needs to be pushed to %dx%d\n", client->rect.x, client->rect.y); LOG("frame 0x%08x needs to be pushed to %dx%d\n", client->frame, client->rect.x, client->rect.y);
/* Note: We can use a pointer to client->x like an array of uint32_ts /* Note: We can use a pointer to client->x like an array of uint32_ts
because it is followed by client->y by definition */ because it is followed by client->y by definition */
xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x)); xcb_configure_window(conn, client->frame, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y, &(client->rect.x));
@ -192,7 +192,7 @@ static void reposition_client(xcb_connection_t *conn, Client *client) {
static void resize_client(xcb_connection_t *conn, Client *client) { static void resize_client(xcb_connection_t *conn, Client *client) {
i3Font *font = load_font(conn, config.font); i3Font *font = load_font(conn, config.font);
LOG("resizing client to %d x %d\n", client->rect.width, client->rect.height); LOG("resizing client 0x%08x to %d x %d\n", client->frame, client->rect.width, client->rect.height);
xcb_configure_window(conn, client->frame, xcb_configure_window(conn, client->frame,
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT,
&(client->rect.width)); &(client->rect.width));
@ -205,7 +205,7 @@ static void resize_client(xcb_connection_t *conn, Client *client) {
XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_WIDTH |
XCB_CONFIG_WINDOW_HEIGHT; XCB_CONFIG_WINDOW_HEIGHT;
Rect *rect = &(client->child_rect); Rect *rect = &(client->child_rect);
switch (client->container->mode) { switch ((client->container != NULL ? client->container->mode : MODE_DEFAULT)) {
case MODE_STACK: case MODE_STACK:
rect->x = 2; rect->x = 2;
rect->y = 0; rect->y = 0;
@ -397,6 +397,7 @@ void render_container(xcb_connection_t *conn, Container *container) {
static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) { static void render_bars(xcb_connection_t *conn, Workspace *r_ws, int width, int *height) {
Client *client; Client *client;
SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) { SLIST_FOREACH(client, &(r_ws->dock_clients), dock_clients) {
LOG("client is at %d, should be at %d\n", client->rect.y, *height);
if (client->force_reconfigure | if (client->force_reconfigure |
update_if_necessary(&(client->rect.x), 0) | update_if_necessary(&(client->rect.x), 0) |
update_if_necessary(&(client->rect.y), *height)) update_if_necessary(&(client->rect.y), *height))

View File

@ -126,14 +126,23 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
int16_t x, int16_t y, uint16_t width, uint16_t height) { int16_t x, int16_t y, uint16_t width, uint16_t height) {
xcb_get_property_cookie_t wm_type_cookie, strut_cookie; xcb_get_property_cookie_t wm_type_cookie, strut_cookie;
uint32_t mask = 0;
uint32_t values[3];
/* We are interested in property changes */
mask = XCB_CW_EVENT_MASK;
values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
XCB_EVENT_MASK_ENTER_WINDOW;
xcb_change_window_attributes(conn, child, mask, values);
/* Map the window first to avoid flickering */
xcb_map_window(conn, child);
/* Place requests for properties ASAP */ /* Place requests for properties ASAP */
wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX); wm_type_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_WINDOW_TYPE], UINT32_MAX);
strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX); strut_cookie = xcb_get_any_property_unchecked(conn, false, child, atoms[_NET_WM_STRUT_PARTIAL], UINT32_MAX);
/* Map the window first to avoid flickering */
xcb_map_window(conn, child);
Client *new = table_get(byChild, child); Client *new = table_get(byChild, child);
/* Events for already managed windows should already be filtered in manage_window() */ /* Events for already managed windows should already be filtered in manage_window() */
@ -142,19 +151,20 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
LOG("reparenting new client\n"); LOG("reparenting new client\n");
new = calloc(sizeof(Client), 1); new = calloc(sizeof(Client), 1);
new->force_reconfigure = true; new->force_reconfigure = true;
uint32_t mask = 0;
uint32_t values[3];
/* Update the data structures */ /* Update the data structures */
Client *old_focused = CUR_CELL->currently_focused; Client *old_focused = CUR_CELL->currently_focused;
new->container = CUR_CELL; new->container = CUR_CELL;
new->workspace = new->container->workspace;
new->frame = xcb_generate_id(conn); new->frame = xcb_generate_id(conn);
new->child = child; new->child = child;
new->rect.width = width; new->rect.width = width;
new->rect.height = height; new->rect.height = height;
mask = 0;
/* Dont generate events for our new window, it should *not* be managed */ /* Dont generate events for our new window, it should *not* be managed */
mask |= XCB_CW_OVERRIDE_REDIRECT; mask |= XCB_CW_OVERRIDE_REDIRECT;
values[0] = 1; values[0] = 1;
@ -199,19 +209,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
return; return;
} }
/* We are interested in property changes */
mask = XCB_CW_EVENT_MASK;
values[0] = XCB_EVENT_MASK_PROPERTY_CHANGE |
XCB_EVENT_MASK_STRUCTURE_NOTIFY |
XCB_EVENT_MASK_ENTER_WINDOW;
cookie = xcb_change_window_attributes_checked(conn, child, mask, values);
if (xcb_request_check(conn, cookie) != NULL) {
LOG("Could not change window attributes, aborting\n");
xcb_destroy_window(conn, new->frame);
free(new);
return;
}
/* Put our data structure (Client) into the table */ /* Put our data structure (Client) into the table */
table_put(byParent, new->frame, new); table_put(byParent, new->frame, new);
table_put(byChild, child, new); table_put(byChild, child, new);
@ -222,16 +219,6 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
1 /* left mouse button */, 1 /* left mouse button */,
XCB_BUTTON_MASK_ANY /* dont filter for any modifiers */); XCB_BUTTON_MASK_ANY /* dont filter for any modifiers */);
/* Focus the new window if were not in fullscreen mode */
if (CUR_CELL->workspace->fullscreen_client == NULL) {
CUR_CELL->currently_focused = new;
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
} else {
/* If we are in fullscreen, we should lower the window to not be annoying */
uint32_t values[] = { XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
}
/* Get _NET_WM_WINDOW_TYPE (to see if its a dock) */ /* Get _NET_WM_WINDOW_TYPE (to see if its a dock) */
xcb_atom_t *atom; xcb_atom_t *atom;
xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL); xcb_get_property_reply_t *preply = xcb_get_property_reply(conn, wm_type_cookie, NULL);
@ -247,23 +234,44 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child,
} }
} }
/* Get _NET_WM_STRUT_PARTIAL to determine the clients requested height */ if (new->dock) {
uint32_t *strut; /* Get _NET_WM_STRUT_PARTIAL to determine the clients requested height */
preply = xcb_get_property_reply(conn, strut_cookie, NULL); uint32_t *strut;
if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) { preply = xcb_get_property_reply(conn, strut_cookie, NULL);
/* We only use a subset of the provided values, namely the reserved space at the top/bottom if (preply != NULL && preply->value_len > 0 && (strut = xcb_get_property_value(preply))) {
of the screen. This is because the only possibility for bars is at to be at the top/bottom /* We only use a subset of the provided values, namely the reserved space at the top/bottom
with maximum horizontal size. of the screen. This is because the only possibility for bars is at to be at the top/bottom
TODO: bars at the top */ with maximum horizontal size.
new->desired_height = strut[3]; TODO: bars at the top */
LOG("the client wants to be %d pixels height\n", new->desired_height); new->desired_height = strut[3];
if (new->desired_height == 0) {
LOG("Client wanted to be 0 pixels high, using the window's height (%d)\n", height);
new->desired_height = height;
}
LOG("the client wants to be %d pixels high\n", new->desired_height);
} else {
LOG("The client didn't specify space to reserve at the screen edge, using its height (%d)\n", height);
new->desired_height = height;
}
}
/* Focus the new window if were not in fullscreen mode and if it is not a dock window */
if (CUR_CELL->workspace->fullscreen_client == NULL) {
if (!new->dock) {
CUR_CELL->currently_focused = new;
xcb_set_input_focus(conn, XCB_INPUT_FOCUS_POINTER_ROOT, new->child, XCB_CURRENT_TIME);
}
} else {
/* If we are in fullscreen, we should lower the window to not be annoying */
uint32_t values[] = { XCB_STACK_MODE_BELOW };
xcb_configure_window(conn, new->frame, XCB_CONFIG_WINDOW_STACK_MODE, values);
} }
/* Insert into the currently active container, if its not a dock window */ /* Insert into the currently active container, if its not a dock window */
if (!new->dock) { if (!new->dock) {
/* Insert after the old active client, if existing. If it does not exist, the /* Insert after the old active client, if existing. If it does not exist, the
container is empty and it does not matter, where we insert it */ container is empty and it does not matter, where we insert it */
if (old_focused != NULL) if (old_focused != NULL && !old_focused->dock)
CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients); CIRCLEQ_INSERT_AFTER(&(CUR_CELL->clients), old_focused, new, clients);
else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients); else CIRCLEQ_INSERT_TAIL(&(CUR_CELL->clients), new, clients);
@ -408,10 +416,6 @@ int main(int argc, char *argv[], char *env[]) {
it any longer. Usually, the client destroys the window shortly afterwards. */ it any longer. Usually, the client destroys the window shortly afterwards. */
xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL); xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, NULL);
/* Destroy notify is a step further than unmap notify. We handle it to make sure
that windows whose unmap notify was falsely ignored will get deleted properly */
xcb_event_set_destroy_notify_handler(&evenths, handle_destroy_notify_event, NULL);
/* Configure notify = windows configuration (geometry, stacking, …). We only need /* Configure notify = windows configuration (geometry, stacking, …). We only need
it to set up ignore the following enter_notify events */ it to set up ignore the following enter_notify events */
xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL); xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL);

View File

@ -233,7 +233,7 @@ void set_focus(xcb_connection_t *conn, Client *client) {
/* If were in stacking mode, this renders the container to update changes in the title /* If were in stacking mode, this renders the container to update changes in the title
bars and to raise the focused client */ bars and to raise the focused client */
if ((old_client != NULL) && (old_client != client)) if ((old_client != NULL) && (old_client != client) && !old_client->dock)
redecorate_window(conn, old_client); redecorate_window(conn, old_client);
SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients); SLIST_REMOVE(&(client->container->workspace->focus_stack), client, Client, focus_clients);