diff --git a/include/data.h b/include/data.h index 083d7fbd..5ac2ccf1 100644 --- a/include/data.h +++ b/include/data.h @@ -109,6 +109,12 @@ struct Stack_Window { SLIST_ENTRY(Stack_Window) stack_windows; }; +struct Ignore_Event { + int sequence; + time_t added; + + SLIST_ENTRY(Ignore_Event) ignore_events; +}; /****************************************************************************** * Major types diff --git a/include/handlers.h b/include/handlers.h index 22e61693..e3f5d144 100644 --- a/include/handlers.h +++ b/include/handlers.h @@ -15,8 +15,11 @@ int handle_key_release(void *ignored, xcb_connection_t *conn, xcb_key_release_ev int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event); int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event); int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event); -int handle_map_notify_event(void *prophs, xcb_connection_t *conn, xcb_map_notify_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_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_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); int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event); diff --git a/src/handlers.c b/src/handlers.c index 7752f68f..88a03e3d 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -12,10 +12,12 @@ #include #include #include -#include +#include +#include #include #include + #include #include "i3.h" @@ -28,11 +30,41 @@ #include "util.h" #include "xinerama.h" #include "config.h" +#include "queue.h" /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it, since it’d trigger an infinite loop of switching between the different windows when changing workspaces */ -int ignore_notify_event = -1; +SLIST_HEAD(ignore_head, Ignore_Event) ignore_events; + +static void add_ignore_event(const int sequence) { + struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event)); + + event->sequence = sequence; + event->added = time(NULL); + + LOG("Adding sequence %d to ignorelist\n", sequence); + + SLIST_INSERT_HEAD(&ignore_events, event, ignore_events); +} + +/* + * Checks if the given sequence is ignored and returns true if so. + * + */ +static bool event_is_ignored(const int sequence) { + struct Ignore_Event *event; + /* TODO: cleanup this list */ + SLIST_FOREACH(event, &ignore_events, ignore_events) { + if (event->sequence == sequence) { + LOG("Ignoring event (sequence %d)\n", sequence); + SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events); + return true; + } + } + + return false; +} /* * Due to bindings like Mode_switch + , we need to bind some keys in XCB_GRAB_MODE_SYNC. @@ -95,13 +127,11 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ * */ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_event_t *event) { - LOG("enter_notify for %08x, serial %d\n", event->event, event->sequence); + LOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n", event->event, event->mode, event->detail, event->sequence); /* Some events are not interesting, because they were not generated actively by the user, but be reconfiguration of windows */ - if (event->sequence == ignore_notify_event) { - LOG("Ignoring, because of previous map\n"); + if (event_is_ignored(event->sequence)) return 1; - } /* This was either a focus for a client’s parent (= titlebar)… */ Client *client = table_get(byParent, event->event); @@ -122,6 +152,13 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ return 1; } + /* 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 */ + if (client->container->mode == MODE_STACK && client->container->currently_focused != client) { + LOG("Plausibility check says: no\n"); + return 1; + } + set_focus(conn, client); return 1; @@ -353,16 +390,67 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ * A new window appeared on the screen (=was mapped), so let’s manage it. * */ -int handle_map_notify_event(void *prophs, xcb_connection_t *conn, xcb_map_notify_event_t *event) { +int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event) { + xcb_get_window_attributes_cookie_t cookie; + xcb_get_window_attributes_reply_t *reply; + + cookie = xcb_get_window_attributes_unchecked(conn, event->window); + + if ((reply = xcb_get_window_attributes_reply(conn, cookie, NULL)) == NULL) { + LOG("Could not get window attributes\n"); + return -1; + } + window_attributes_t wa = { TAG_VALUE }; - wa.u.override_redirect = event->override_redirect; - LOG("MapNotify for 0x%08x, serial is %d.\n", event->window, event->sequence); - LOG("setting ignore_notify_event = %d\n", event->sequence); - ignore_notify_event = event->sequence; + LOG("override_redirect = %d\n", reply->override_redirect); + wa.u.override_redirect = reply->override_redirect; + LOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence); + add_ignore_event(event->sequence); manage_window(prophs, conn, event->window, wa); return 1; } +/* + * Configure requests are received when the application wants to resize windows on their own. + * + * We generate a synthethic configure notify event to signalize the client its "new" position. + * + */ +int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event) { + LOG("configure-request, serial %d\n", event->sequence); + LOG("event->window = %08x\n", event->window); + LOG("application wants to be at %dx%d with %dx%d\n", event->x, event->y, event->width, event->height); + + Client *client = table_get(byChild, event->window); + if (client == NULL) { + LOG("No such client\n"); + return 1; + } + + xcb_configure_notify_event_t generated_event; + + generated_event.event = client->child; + generated_event.window = client->child; + generated_event.response_type = XCB_CONFIGURE_NOTIFY; + + generated_event.x = client->child_rect.x; + generated_event.y = client->child_rect.y; + generated_event.width = client->child_rect.width; + generated_event.height = client->child_rect.height; + + generated_event.border_width = 0; + generated_event.above_sibling = XCB_NONE; + generated_event.override_redirect = false; + + xcb_send_event(conn, false, client->child, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&generated_event); + xcb_flush(conn); + + LOG("Told the client to stay at %dx%d with size %dx%d\n", + client->child_rect.x, client->child_rect.y, client->child_rect.width, client->child_rect.height); + + return 1; +} + /* * Configuration notifies are only handled because we need to set up ignore for the following * enter notify events @@ -371,12 +459,10 @@ int handle_map_notify_event(void *prophs, xcb_connection_t *conn, xcb_map_notify int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) { xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - LOG("handle_configure_event\n"); + LOG("handle_configure_event for window %08x\n", event->window); LOG("event->type = %d, \n", event->response_type); LOG("event->x = %d, ->y = %d, ->width = %d, ->height = %d\n", event->x, event->y, event->width, event->height); - LOG("sequence = %d\n", event->sequence); - - ignore_notify_event = event->sequence; + add_ignore_event(event->sequence); if (event->event == root) { LOG("reconfigure of the root window, need to xinerama\n"); @@ -387,29 +473,6 @@ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_n return 1; } - Client *client = table_get(byChild, event->window); - if (client == NULL) { - LOG("client not managed, ignoring\n"); - return 1; - } - - if (client->fullscreen) { - LOG("client in fullscreen, not touching\n"); - return 1; - } - - /* Let’s see if the application has changed size/position on its own *sigh*… */ - if ((event->x != client->child_rect.x) || - (event->y != client->child_rect.y) || - (event->width != client->child_rect.width) || - (event->height != client->child_rect.height)) { - /* Who is your window manager? Who’s that, huh? I AM YOUR WINDOW MANAGER! */ - LOG("Application wanted to resize itself. Fixed that.\n"); - client->force_reconfigure = true; - render_container(conn, client->container); - xcb_flush(conn); - } - return 1; } @@ -421,9 +484,7 @@ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_n int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event) { xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - LOG("setting ignore_notify_event = %d\n", event->sequence); - - ignore_notify_event = event->sequence; + add_ignore_event(event->sequence); Client *client = table_get(byChild, event->window); /* First, we need to check if the client is awaiting an unmap-request which @@ -433,14 +494,16 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti client->awaiting_useless_unmap = false; return 1; } - client = table_remove(byChild, event->window); - LOG("UnmapNotify for 0x%08x (received from 0x%08x): ", event->window, event->event); + LOG("event->window = %08x, event->event = %08x\n", event->window, event->event); + LOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event); if (client == NULL) { LOG("not a managed window. Ignoring.\n"); return 0; } + client = table_remove(byChild, event->window); + if (client->name != NULL) free(client->name); @@ -484,6 +547,20 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti 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, I’d rather know why some applications need to have + * unmap events ignored (like VLC’s open dialog, just open it two times) while others don’t (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, event); +} + /* * Called when a window changes its title * @@ -505,6 +582,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)); /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ char *ucs2_name = convert_utf8_to_ucs2(new_name, &new_len); + LOG("Name should change to \"%s\"\n", new_name); free(new_name); /* Check if they are the same and don’t update if so. @@ -525,7 +603,6 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, if (old_name != NULL) free(old_name); - LOG("rename to \"%s\".\n", client->name); if (client->container->mode == MODE_STACK) render_container(conn, client->container); diff --git a/src/mainx.c b/src/mainx.c index 75a45c53..4d96a8c4 100644 --- a/src/mainx.c +++ b/src/mainx.c @@ -131,6 +131,9 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, 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); + /* Map the window first to avoid flickering */ + xcb_map_window(conn, child); + Client *new = table_get(byChild, child); /* Events for already managed windows should already be filtered in manage_window() */ @@ -158,10 +161,11 @@ void reparent_window(xcb_connection_t *conn, xcb_window_t child, /* We want to know when… */ mask |= XCB_CW_EVENT_MASK; - values[1] = XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */ + values[1] = XCB_EVENT_MASK_BUTTON_PRESS | /* …mouse is pressed/released */ XCB_EVENT_MASK_BUTTON_RELEASE | - XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */ - XCB_EVENT_MASK_ENTER_WINDOW; /* …user moves cursor inside our window */ + XCB_EVENT_MASK_EXPOSURE | /* …our window needs to be redrawn */ + XCB_EVENT_MASK_ENTER_WINDOW | /* …user moves cursor inside our window */ + XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT; /* …the application tries to resize itself */ LOG("Reparenting 0x%08x under 0x%08x.\n", child, new->frame); @@ -385,32 +389,39 @@ int main(int argc, char *argv[], char *env[]) { xcb_event_set_error_handler(&evenths, i, (xcb_generic_error_handler_t)handle_event, 0); /* Expose = an Application should redraw itself, in this case it’s our titlebars. */ - xcb_event_set_expose_handler(&evenths, handle_expose_event, 0); + xcb_event_set_expose_handler(&evenths, handle_expose_event, NULL); /* Key presses/releases are pretty obvious, I think */ - xcb_event_set_key_press_handler(&evenths, handle_key_press, 0); - xcb_event_set_key_release_handler(&evenths, handle_key_release, 0); + xcb_event_set_key_press_handler(&evenths, handle_key_press, NULL); + xcb_event_set_key_release_handler(&evenths, handle_key_release, NULL); /* Enter window = user moved his mouse over the window */ - xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, 0); + xcb_event_set_enter_notify_handler(&evenths, handle_enter_notify, NULL); /* Button press = user pushed a mouse button over one of our windows */ - xcb_event_set_button_press_handler(&evenths, handle_button_press, 0); + xcb_event_set_button_press_handler(&evenths, handle_button_press, NULL); /* Map notify = there is a new window */ - xcb_event_set_map_notify_handler(&evenths, handle_map_notify_event, &prophs); + xcb_event_set_map_request_handler(&evenths, handle_map_request, &prophs); /* Unmap notify = window disappeared. When sent from a client, we don’t manage it any longer. Usually, the client destroys the window shortly afterwards. */ - xcb_event_set_unmap_notify_handler(&evenths, handle_unmap_notify_event, 0); + 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 = window’s configuration (geometry, stacking, …). We only need it to set up ignore the following enter_notify events */ - xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, 0); + xcb_event_set_configure_notify_handler(&evenths, handle_configure_event, NULL); + + /* Configure request = window tried to change size on its own */ + xcb_event_set_configure_request_handler(&evenths, handle_configure_request, NULL); /* Client message = client changed its properties (EWMH) */ /* TODO: can’t we do this via property handlers? */ - xcb_event_set_client_message_handler(&evenths, handle_client_message, 0); + xcb_event_set_client_message_handler(&evenths, handle_client_message, NULL); /* Initialize the property handlers */ xcb_property_handlers_init(&prophs, &evenths); @@ -422,7 +433,7 @@ int main(int argc, char *argv[], char *env[]) { root = xcb_aux_get_screen(conn, screens)->root; uint32_t mask = XCB_CW_EVENT_MASK; - uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_NOTIFY | + uint32_t values[] = { XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT | XCB_EVENT_MASK_STRUCTURE_NOTIFY | /* when the user adds a screen (e.g. video projector), the root window gets a ConfigureNotify */