X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fhandlers.c;h=d5a1e8fd51267afffbd7ec6afb7842bc978c5f9a;hb=6192975a041eaacb267ddabc0c2dcb4f33e3b58b;hp=4c3024b580f07eeb4030b9ee4fd214194b685977;hpb=408b2bdb3962a6cc77635e342c5e9a41e6fc4bf7;p=i3%2Fi3 diff --git a/src/handlers.c b/src/handlers.c index 4c3024b5..d5a1e8fd 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -15,7 +15,7 @@ #include #include -#include +#include #include #include @@ -31,11 +31,15 @@ #include "xinerama.h" #include "config.h" #include "queue.h" +#include "resize.h" +#include "client.h" +#include "manage.h" +#include "floating.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 */ -SLIST_HEAD(ignore_head, Ignore_Event) ignore_events; +static 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)); @@ -54,11 +58,21 @@ static void add_ignore_event(const int sequence) { */ static bool event_is_ignored(const int sequence) { struct Ignore_Event *event; - /* TODO: cleanup this list */ + time_t now = time(NULL); + for (event = SLIST_FIRST(&ignore_events); event != SLIST_END(&ignore_events);) { + if ((now - event->added) > 5) { + struct Ignore_Event *save = event; + event = SLIST_NEXT(event, ignore_events); + SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events); + free(save); + } else event = SLIST_NEXT(event, ignore_events); + } + 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); + free(event); return true; } } @@ -84,25 +98,45 @@ 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) { - LOG("Keypress %d\n", event->detail); + LOG("Keypress %d, state raw = %d\n", event->detail, event->state); + + /* Remove the numlock bit, all other bits are modifiers we can bind to */ + uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK); + LOG("(removed numlock, state = %d)\n", state_filtered); + /* Only use the lower 8 bits of the state (modifier masks) so that mouse + * button masks are filtered out */ + state_filtered &= 0xFF; + LOG("(removed upper 8 bits, state = %d)\n", state_filtered); /* We need to get the keysym group (There are group 1 to group 4, each holding two keysyms (without shift and with shift) using Xkb because X fails to provide them reliably (it works in Xephyr, it does not in real X) */ XkbStateRec state; if (XkbGetState(xkbdpy, XkbUseCoreKbd, &state) == Success && (state.group+1) == 2) - event->state |= 0x2; - - LOG("state %d\n", event->state); + state_filtered |= BIND_MODE_SWITCH; - /* Remove the numlock bit, all other bits are modifiers we can bind to */ - uint16_t state_filtered = event->state & ~XCB_MOD_MASK_LOCK; + LOG("(checked mode_switch, state %d)\n", state_filtered); /* Find the binding */ Binding *bind; - TAILQ_FOREACH(bind, &bindings, bindings) - if (bind->keycode == event->detail && bind->mods == state_filtered) - break; + TAILQ_FOREACH(bind, &bindings, bindings) { + /* First compare the modifiers */ + if (bind->mods != state_filtered) + continue; + + /* If a symbol was specified by the user, we need to look in + * the array of translated keycodes for the event’s keycode */ + if (bind->symbol != NULL) { + if (memmem(bind->translated_to, + bind->number_keycodes * sizeof(xcb_keycode_t), + &(event->detail), sizeof(xcb_keycode_t)) != NULL) + break; + } else { + /* This case is easier: The user specified a keycode */ + if (bind->keycode == event->detail) + break; + } + } /* No match? Then it was an actively grabbed key, that is with Mode_switch, and the user did not press Mode_switch, so just pass it… */ @@ -113,7 +147,7 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ } parse_command(conn, bind->command); - if (event->state & 0x2) { + if (state_filtered & BIND_MODE_SWITCH) { LOG("Mode_switch -> allow_events(SyncKeyboard)\n"); xcb_allow_events(conn, SyncKeyboard, event->time); xcb_flush(conn); @@ -121,6 +155,29 @@ int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_ return 1; } +/* + * Called with coordinates of an enter_notify event or motion_notify event + * to check if the user crossed virtual screen boundaries and adjust the + * current workspace, if so. + * + */ +static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { + i3Screen *screen; + + if ((screen = get_screen_containing(x, y)) == NULL) { + LOG("ERROR: No such screen\n"); + return; + } + if (screen == c_ws->screen) + return; + + c_ws->current_row = current_row; + c_ws->current_col = current_col; + c_ws = &workspaces[screen->current_workspace]; + current_row = c_ws->current_row; + current_col = c_ws->current_col; + LOG("We're now on virtual screen number %d\n", screen->num); +} /* * When the user moves the mouse pointer onto a window, this callback gets called. @@ -133,26 +190,31 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ return 1; } /* Some events are not interesting, because they were not generated actively by the - user, but be reconfiguration of windows */ + user, but by reconfiguration of windows */ 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); + Client *client = table_get(&by_parent, event->event); /* …or the client itself */ if (client == NULL) - client = table_get(byChild, event->event); + client = table_get(&by_child, event->event); + + /* Check for stack windows */ + if (client == NULL) { + struct Stack_Window *stack_win; + SLIST_FOREACH(stack_win, &stack_wins, stack_windows) + if (stack_win->window == event->event) { + client = stack_win->container->currently_focused; + break; + } + } + /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */ if (client == NULL) { LOG("Getting screen at %d x %d\n", event->root_x, event->root_y); - i3Screen *screen = get_screen_containing(event->root_x, event->root_y); - if (screen == NULL) { - LOG("ERROR: No such screen\n"); - return 0; - } - c_ws = &workspaces[screen->current_workspace]; - LOG("We're now on virtual screen number %d\n", screen->num); + check_crossing_screen_boundary(event->root_x, event->root_y); return 1; } @@ -165,63 +227,191 @@ int handle_enter_notify(void *ignored, xcb_connection_t *conn, xcb_enter_notify_ return 1; } - set_focus(conn, client); + if (client->workspace != c_ws && client->workspace->screen == c_ws->screen) { + /* This can happen when a client gets assigned to a different workspace than + * the current one (see src/mainx.c:reparent_window). Shortly after it was created, + * an enter_notify will follow. */ + LOG("enter_notify for a client on a different workspace but the same screen, ignoring\n"); + return 1; + } + + set_focus(conn, client, false); return 1; } +/* + * When the user moves the mouse but does not change the active window + * (e.g. when having no windows opened but moving mouse on the root screen + * and crossing virtual screen boundaries), this callback gets called. + * + */ +int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notify_event_t *event) { + LOG("pointer motion notify, getting screen at %d x %d\n", event->root_x, event->root_y); + + check_crossing_screen_boundary(event->root_x, event->root_y); + + return 1; +} + +/* + * Called when the keyboard mapping changes (for example by using Xmodmap), + * we need to update our key bindings then (re-translate symbols). + * + */ +int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_notify_event_t *event) { + LOG("\n\nmapping notify\n\n"); + + if (event->request != XCB_MAPPING_KEYBOARD && + event->request != XCB_MAPPING_MODIFIER) + return 0; + + xcb_refresh_keyboard_mapping(keysyms, event); + + xcb_get_numlock_mask(conn); + + ungrab_all_keys(conn); + LOG("Re-grabbing...\n"); + grab_all_keys(conn); + LOG("Done\n"); + + return 0; +} + +/* + * Checks if the button press was on a stack window, handles focus setting and returns true + * if so, or false otherwise. + * + */ +static bool button_press_stackwin(xcb_connection_t *conn, xcb_button_press_event_t *event) { + struct Stack_Window *stack_win; + SLIST_FOREACH(stack_win, &stack_wins, stack_windows) { + if (stack_win->window != event->event) + continue; + + /* A stack window was clicked, we check if it was button4 or button5 + which are scroll up / scroll down. */ + if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) { + direction_t direction = (event->detail == XCB_BUTTON_INDEX_4 ? D_UP : D_DOWN); + focus_window_in_container(conn, CUR_CELL, direction); + return true; + } + + /* It was no scrolling, so we calculate the destination client by + dividing the Y position of the event through the height of a window + decoration and then set the focus to this client. */ + i3Font *font = load_font(conn, config.font); + int decoration_height = (font->height + 2 + 2); + int destination = (event->event_y / decoration_height), + c = 0; + Client *client; + + LOG("Click on stack_win for client %d\n", destination); + CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients) + if (c++ == destination) { + set_focus(conn, client, true); + return true; + } + + return true; + } + + return false; +} + +/* + * Checks if the button press was on a bar, switches to the workspace and returns true + * if so, or false otherwise. + * + */ +static bool button_press_bar(xcb_connection_t *conn, xcb_button_press_event_t *event) { + i3Screen *screen; + TAILQ_FOREACH(screen, virtual_screens, screens) { + if (screen->bar != event->event) + continue; + + LOG("Click on a bar\n"); + + /* Check if the button was one of button4 or button5 (scroll up / scroll down) */ + if (event->detail == XCB_BUTTON_INDEX_4 || event->detail == XCB_BUTTON_INDEX_5) { + int add = (event->detail == XCB_BUTTON_INDEX_4 ? -1 : 1); + for (int i = c_ws->num + add; (i >= 0) && (i < 10); i += add) + if (workspaces[i].screen == screen) { + show_workspace(conn, i+1); + return true; + } + return true; + } + int drawn = 0; + /* Because workspaces can be on different screens, we need to loop + through all of them and decide to count it based on its ->screen */ + for (int i = 0; i < 10; i++) { + if (workspaces[i].screen != screen) + continue; + LOG("Checking if click was on workspace %d with drawn = %d, tw = %d\n", + i, drawn, workspaces[i].text_width); + if (event->event_x > (drawn + 1) && + event->event_x <= (drawn + 1 + workspaces[i].text_width + 5 + 5)) { + show_workspace(conn, i+1); + return true; + } + + drawn += workspaces[i].text_width + 5 + 5 + 2; + } + return true; + } + + return false; +} + int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_event_t *event) { LOG("button press!\n"); + LOG("state = %d\n", event->state); /* This was either a focus for a client’s parent (= titlebar)… */ - Client *client = table_get(byChild, event->event); + Client *client = table_get(&by_child, event->event); bool border_click = false; if (client == NULL) { - client = table_get(byParent, event->event); + client = table_get(&by_parent, event->event); border_click = true; } + /* See if this was a click with the configured modifier. If so, we need + * to move around the client if it was floating. if not, we just process + * as usual. */ + if (config.floating_modifier != 0 && + (event->state & config.floating_modifier) != 0) { + if (client == NULL) { + LOG("Not handling, floating_modifier was pressed and no client found\n"); + return 1; + } + if (client_is_floating(client)) { + floating_drag_window(conn, client, event); + return 1; + } + } + if (client == NULL) { /* The client was neither on a client’s titlebar nor on a client itself, maybe on a stack_window? */ - struct Stack_Window *stack_win; - SLIST_FOREACH(stack_win, &stack_wins, stack_windows) - if (stack_win->window == event->event) { - /* A stack window was clicked. We calculate the destination client by - dividing the Y position of the event through the height of a window - decoration and then set the focus to this client. */ - i3Font *font = load_font(conn, config.font); - int decoration_height = (font->height + 2 + 2); - int destination = (event->event_y / decoration_height), - c = 0; - Client *client; - - LOG("Click on stack_win for client %d\n", destination); - CIRCLEQ_FOREACH(client, &(stack_win->container->clients), clients) - if (c++ == destination) { - set_focus(conn, client); - return 1; - } + if (button_press_stackwin(conn, event)) + return 1; - return 1; - } + /* Or on a bar? */ + if (button_press_bar(conn, event)) + return 1; + LOG("Could not handle this button press\n"); return 1; } - xcb_window_t root = xcb_setup_roots_iterator(xcb_get_setup(conn)).data->root; - xcb_screen_t *root_screen = xcb_setup_roots_iterator(xcb_get_setup(conn)).data; - /* Set focus in any case */ - set_focus(conn, client); + set_focus(conn, client, true); /* Let’s see if this was on the borders (= resize). If not, we’re done */ LOG("press button on x=%d, y=%d\n", event->event_x, event->event_y); + resize_orientation_t orientation = O_VERTICAL; + Container *con = client->container; + int first, second; - Container *con = client->container, - *first = NULL, - *second = NULL; - enum { O_HORIZONTAL, O_VERTICAL } orientation = O_VERTICAL; - int new_position; - - if (con == NULL) { + if (client->dock) { LOG("dock. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); xcb_flush(conn); @@ -230,166 +420,83 @@ int handle_button_press(void *ignored, xcb_connection_t *conn, xcb_button_press_ LOG("event->event_x = %d, client->rect.width = %d\n", event->event_x, client->rect.width); + /* Some clients (xfontsel for example) seem to pass clicks on their + * window to the parent window, thus we receive an event here which in + * reality is a border_click. Check for the position and fix state. */ + if (border_click && + event->event_x >= client->child_rect.x && + event->event_x <= (client->child_rect.x + client->child_rect.width) && + event->event_y >= client->child_rect.y && + event->event_y <= (client->child_rect.y + client->child_rect.height)) { + LOG("Fixing border_click = false because of click in child\n"); + border_click = false; + } + if (!border_click) { LOG("client. done.\n"); xcb_allow_events(conn, XCB_ALLOW_REPLAY_POINTER, event->time); + /* Floating clients should be raised on click */ + if (client_is_floating(client)) + xcb_raise_window(conn, client->frame); xcb_flush(conn); return 1; } + /* Don’t handle events inside the titlebar, only borders are interesting */ + i3Font *font = load_font(conn, config.font); + if (event->event_y >= 2 && event->event_y <= (font->height + 2 + 2)) { + LOG("click on titlebar\n"); + + /* Floating clients can be dragged by grabbing their titlebar */ + if (client_is_floating(client)) { + /* Firstly, we raise it. Maybe the user just wanted to raise it without grabbing */ + xcb_raise_window(conn, client->frame); + xcb_flush(conn); + + floating_drag_window(conn, client, event); + } + return 1; + } + + if (client_is_floating(client)) + return floating_border_click(conn, client, event); + if (event->event_y < 2) { /* This was a press on the top border */ if (con->row == 0) return 1; - first = con->workspace->table[con->col][con->row-1]; - second = con; + first = con->row - 1; + second = con->row; orientation = O_HORIZONTAL; } else if (event->event_y >= (client->rect.height - 2)) { /* …bottom border */ - if (con->row == (con->workspace->rows-1)) + first = con->row + (con->rowspan - 1); + if (!cell_exists(con->col, first) || + (first == (con->workspace->rows-1))) return 1; - first = con; - second = con->workspace->table[con->col][con->row+1]; + + second = first + 1; orientation = O_HORIZONTAL; } else if (event->event_x <= 2) { /* …left border */ if (con->col == 0) return 1; - first = con->workspace->table[con->col-1][con->row]; - second = con; + + first = con->col - 1; + second = con->col; } else if (event->event_x > 2) { /* …right border */ - if (con->col == (con->workspace->cols-1)) - return 1; - first = con; - second = con->workspace->table[con->col+1][con->row]; - } - - uint32_t mask = 0; - uint32_t values[2]; - - mask = XCB_CW_OVERRIDE_REDIRECT; - values[0] = 1; - - /* Open a new window, the resizebar. Grab the pointer and move the window around - as the user moves the pointer. */ - Rect grabrect = {0, 0, root_screen->width_in_pixels, root_screen->height_in_pixels}; - xcb_window_t grabwin = create_window(conn, grabrect, XCB_WINDOW_CLASS_INPUT_ONLY, -1, mask, values); - - Rect helprect; - if (orientation == O_VERTICAL) { - helprect.x = event->root_x; - helprect.y = 0; - helprect.width = 2; - helprect.height = root_screen->height_in_pixels; /* this has to be the cell’s height */ - new_position = event->root_x; - } else { - helprect.x = 0; - helprect.y = event->root_y; - helprect.width = root_screen->width_in_pixels; /* this has to be the cell’s width */ - helprect.height = 2; - new_position = event->root_y; - } - - mask = XCB_CW_BACK_PIXEL; - values[0] = get_colorpixel(conn, "#4c7899"); - - mask |= XCB_CW_OVERRIDE_REDIRECT; - values[1] = 1; - - xcb_window_t helpwin = create_window(conn, helprect, XCB_WINDOW_CLASS_INPUT_OUTPUT, - (orientation == O_VERTICAL ? - XCB_CURSOR_SB_V_DOUBLE_ARROW : - XCB_CURSOR_SB_H_DOUBLE_ARROW), mask, values); - - xcb_circulate_window(conn, XCB_CIRCULATE_RAISE_LOWEST, helpwin); - - xcb_grab_pointer(conn, false, root, XCB_EVENT_MASK_BUTTON_RELEASE | XCB_EVENT_MASK_POINTER_MOTION, - XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, grabwin, XCB_NONE, XCB_CURRENT_TIME); - - xcb_flush(conn); - - xcb_generic_event_t *inside_event; - /* I’ve always wanted to have my own eventhandler… */ - while ((inside_event = xcb_wait_for_event(conn))) { - /* Same as get_event_handler in xcb */ - int nr = inside_event->response_type; - if (nr == 0) { - /* An error occured */ - handle_event(NULL, conn, inside_event); - free(inside_event); - continue; - } - assert(nr < 256); - nr &= XCB_EVENT_RESPONSE_TYPE_MASK; - assert(nr >= 2); - - /* Check if we need to escape this loop */ - if (nr == XCB_BUTTON_RELEASE) - break; - - switch (nr) { - case XCB_MOTION_NOTIFY: - if (orientation == O_VERTICAL) { - values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_x; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_X, values); - } else { - values[0] = new_position = ((xcb_motion_notify_event_t*)inside_event)->root_y; - xcb_configure_window(conn, helpwin, XCB_CONFIG_WINDOW_Y, values); - } - - xcb_flush(conn); - break; - default: - LOG("Passing to original handler\n"); - /* Use original handler */ - xcb_event_handle(&evenths, inside_event); - break; - } - free(inside_event); - } - - xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); - xcb_destroy_window(conn, helpwin); - xcb_destroy_window(conn, grabwin); - xcb_flush(conn); - - Workspace *ws = con->workspace; - if (orientation == O_VERTICAL) { - LOG("Resize was from X = %d to X = %d\n", event->root_x, new_position); - if (event->root_x == new_position) { - LOG("Nothing changed, not updating anything\n"); - return 1; - } + first = con->col + (con->colspan - 1); + LOG("column %d\n", first); - /* Convert 0 (for default width_factor) to actual numbers */ - if (first->width_factor == 0) - first->width_factor = ((float)ws->rect.width / ws->cols) / ws->rect.width; - if (second->width_factor == 0) - second->width_factor = ((float)ws->rect.width / ws->cols) / ws->rect.width; - - first->width_factor *= (float)(first->width + (new_position - event->root_x)) / first->width; - second->width_factor *= (float)(second->width - (new_position - event->root_x)) / second->width; - } else { - LOG("Resize was from Y = %d to Y = %d\n", event->root_y, new_position); - if (event->root_y == new_position) { - LOG("Nothing changed, not updating anything\n"); + if (!cell_exists(first, con->row) || + (first == (con->workspace->cols-1))) return 1; - } - /* Convert 0 (for default height_factor) to actual numbers */ - if (first->height_factor == 0) - first->height_factor = ((float)ws->rect.height / ws->rows) / ws->rect.height; - if (second->height_factor == 0) - second->height_factor = ((float)ws->rect.height / ws->rows) / ws->rect.height; - - first->height_factor *= (float)(first->height + (new_position - event->root_y)) / first->height; - second->height_factor *= (float)(second->height - (new_position - event->root_y)) / second->height; + second = first + 1; } - render_layout(conn); - - return 1; + return resize_graphical_handler(conn, con->workspace, first, second, orientation, event); } /* @@ -398,22 +505,13 @@ 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) { 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 }; - 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); + manage_window(prophs, conn, event->window, cookie, false); return 1; } @@ -428,32 +526,78 @@ int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure 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); + Client *client = table_get(&by_child, event->window); if (client == NULL) { - LOG("No such client\n"); + LOG("This client is not mapped, so we don't care and just tell the client that he will get its size\n"); + uint32_t mask = 0; + uint32_t values[7]; + int c = 0; +#define COPY_MASK_MEMBER(mask_member, event_member) do { \ + if (event->value_mask & mask_member) { \ + mask |= mask_member; \ + values[c++] = event->event_member; \ + } \ +} while (0) + + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling); + COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode); + + xcb_configure_window(conn, event->window, mask, values); + xcb_flush(conn); + return 1; } - xcb_configure_notify_event_t generated_event; + if (client->fullscreen) { + LOG("Client is in fullscreen mode\n"); - generated_event.event = client->child; - generated_event.window = client->child; - generated_event.response_type = XCB_CONFIGURE_NOTIFY; + Rect child_rect = client->workspace->rect; + child_rect.x = child_rect.y = 0; + fake_configure_notify(conn, child_rect, client->child); - 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; + return 1; + } - generated_event.border_width = 0; - generated_event.above_sibling = XCB_NONE; - generated_event.override_redirect = false; + /* Floating clients can be reconfigured */ + if (client_is_floating(client)) { + i3Font *font = load_font(conn, config.font); + + if (event->value_mask & XCB_CONFIG_WINDOW_X) + client->rect.x = event->x; + if (event->value_mask & XCB_CONFIG_WINDOW_Y) + client->rect.y = event->y; + if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) + client->rect.width = event->width + 2 + 2; + if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) + client->rect.height = event->height + (font->height + 2 + 2) + 2; + + LOG("Accepted new position/size for floating client: (%d, %d) size %d x %d\n", + client->rect.x, client->rect.y, client->rect.width, client->rect.height); + + /* Push the new position/size to X11 */ + reposition_client(conn, client); + resize_client(conn, client); + xcb_flush(conn); - xcb_send_event(conn, false, client->child, XCB_EVENT_MASK_STRUCTURE_NOTIFY, (char*)&generated_event); - xcb_flush(conn); + return 1; + } + + if (client->fullscreen) { + LOG("Client is in fullscreen mode\n"); - 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); + Rect child_rect = client->container->workspace->rect; + child_rect.x = child_rect.y = 0; + fake_configure_notify(conn, child_rect, client->child); + + return 1; + } + + fake_absolute_configure_notify(conn, client); return 1; } @@ -469,6 +613,9 @@ int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_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); + + /* We ignore this sequence twice because events for child and frame should be ignored */ + add_ignore_event(event->sequence); add_ignore_event(event->sequence); if (event->event == root) { @@ -493,7 +640,7 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti add_ignore_event(event->sequence); - Client *client = table_get(byChild, event->window); + Client *client = table_get(&by_child, event->window); /* First, we need to check if the client is awaiting an unmap-request which was generated by us reparenting the window. In that case, we just ignore it. */ if (client != NULL && client->awaiting_useless_unmap) { @@ -506,57 +653,88 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti LOG("UnmapNotify for 0x%08x (received from 0x%08x)\n", event->window, event->event); if (client == NULL) { LOG("not a managed window. Ignoring.\n"); + + /* This was most likely the destroyed frame of a client which is + * currently being unmapped, so we add this sequence (again!) to + * the ignore list (enter_notify events will get sent for both, + * the child and its frame). */ + add_ignore_event(event->sequence); + return 0; } - client = table_remove(byChild, event->window); + client = table_remove(&by_child, event->window); - if (client->name != NULL) - free(client->name); + /* If this was the fullscreen client, we need to unset it */ + if (client->fullscreen) + client->workspace->fullscreen_client = NULL; + /* Clients without a container are either floating or dock windows */ if (client->container != NULL) { Container *con = client->container; - /* If this was the fullscreen client, we need to unset it */ - if (client->fullscreen) - con->workspace->fullscreen_client = NULL; - /* Remove the client from the list of clients */ - remove_client_from_container(conn, client, con); - - /* Remove from the focus stack */ - LOG("Removing from focus stack\n"); - SLIST_REMOVE(&(con->workspace->focus_stack), client, Client, focus_clients); + client_remove_from_container(conn, client, con, true); /* Set focus to the last focused client in this container */ - con->currently_focused = NULL; - Client *focus_client; - SLIST_FOREACH(focus_client, &(con->workspace->focus_stack), focus_clients) - if (focus_client->container == con) { - con->currently_focused = focus_client; - set_focus(conn, focus_client); - break; - } + con->currently_focused = get_last_focused_client(conn, con, NULL); + + /* Only if this is the active container, we need to really change focus */ + if ((con->currently_focused != NULL) && ((con == CUR_CELL) || client->fullscreen)) + set_focus(conn, con->currently_focused, true); + } else if (client_is_floating(client)) { + LOG("Removing from floating clients\n"); + TAILQ_REMOVE(&(client->workspace->floating_clients), client, floating_clients); + SLIST_REMOVE(&(client->workspace->focus_stack), client, Client, focus_clients); } if (client->dock) { LOG("Removing from dock clients\n"); - SLIST_REMOVE(&(client->workspace->dock_clients), client, Client, dock_clients); + SLIST_REMOVE(&(client->workspace->screen->dock_clients), client, Client, dock_clients); } LOG("child of 0x%08x.\n", client->frame); xcb_reparent_window(conn, client->child, root, 0, 0); xcb_destroy_window(conn, client->frame); xcb_flush(conn); - table_remove(byParent, client->frame); + table_remove(&by_parent, client->frame); - if (client->container != NULL) - cleanup_table(conn, client->container->workspace); + if (client->container != NULL) { + Workspace *workspace = client->container->workspace; + cleanup_table(conn, workspace); + fix_colrowspan(conn, workspace); + } + /* Let’s see how many clients there are left on the workspace to delete it if it’s empty */ + bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack)); + bool workspace_active = false; + Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL); + + /* If this workspace is currently active, we don’t delete it */ + i3Screen *screen; + TAILQ_FOREACH(screen, virtual_screens, screens) + if (screen->current_workspace == client->workspace->num) { + workspace_active = true; + workspace_empty = false; + break; + } + + if (workspace_empty) { + LOG("setting ws to NULL for workspace %d (%p)\n", client->workspace->num, + client->workspace); + client->workspace->screen = NULL; + } + + FREE(client->window_class); + FREE(client->name); free(client); render_layout(conn); + /* Ensure the focus is set to the next client in the focus stack */ + if (workspace_active && to_focus != NULL) + set_focus(conn, to_focus, true); + return 1; } @@ -567,11 +745,11 @@ int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_noti 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) { LOG("window's name changed.\n"); - if (prop == NULL) { - LOG("prop == NULL\n"); + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + LOG("_NET_WM_NAME not specified, not changing\n"); return 1; } - Client *client = table_get(byChild, window); + Client *client = table_get(&by_child, window); if (client == NULL) return 1; @@ -599,6 +777,74 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, char *old_name = client->name; client->name = ucs2_name; client->name_len = new_len; + client->uses_net_wm_name = true; + + FREE(old_name); + + /* If the client is a dock window, we don’t need to render anything */ + if (client->dock) + return 1; + + if (client->container != NULL && client->container->mode == MODE_STACK) + render_container(conn, client->container); + else decorate_window(conn, client, client->frame, client->titlegc, 0); + xcb_flush(conn); + + return 1; +} + +/* + * We handle legacy window names (titles) which are in COMPOUND_TEXT encoding. However, we + * just pass them along, so when containing non-ASCII characters, those will be rendering + * incorrectly. In order to correctly render unicode window titles in i3, an application + * has to set _NET_WM_NAME, which is in UTF-8 encoding. + * + * On every update, a message is put out to the user, so he may improve the situation and + * update applications which display filenames in their title to correctly use + * _NET_WM_NAME and therefore support unicode. + * + */ +int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state, + xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { + LOG("window's name changed (legacy).\n"); + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + LOG("prop == NULL\n"); + return 1; + } + Client *client = table_get(&by_child, window); + if (client == NULL) + return 1; + + if (client->uses_net_wm_name) { + LOG("This client is capable of _NET_WM_NAME, ignoring legacy name\n"); + return 1; + } + + /* Save the old pointer to make the update atomic */ + char *new_name; + if (asprintf(&new_name, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) { + perror("Could not get old name"); + LOG("Could not get old name\n"); + return 1; + } + /* Convert it to UCS-2 here for not having to convert it later every time we want to pass it to X */ + LOG("Name should change to \"%s\"\n", new_name); + + /* Check if they are the same and don’t update if so. */ + if (client->name != NULL && + strlen(new_name) == strlen(client->name) && + strcmp(client->name, new_name) == 0) { + LOG("Name did not change, not updating\n"); + free(new_name); + return 1; + } + + LOG("Using legacy window title. Note that in order to get Unicode window titles in i3," + "the application has to set _NET_WM_NAME which is in UTF-8 encoding.\n"); + + char *old_name = client->name; + client->name = new_name; + client->name_len = -1; if (old_name != NULL) free(old_name); @@ -607,7 +853,7 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, if (client->dock) return 1; - if (client->container->mode == MODE_STACK) + if (client->container != NULL && client->container->mode == MODE_STACK) render_container(conn, client->container); else decorate_window(conn, client, client->frame, client->titlegc, 0); xcb_flush(conn); @@ -615,6 +861,46 @@ int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state, return 1; } +/* + * Updates the client’s WM_CLASS property + * + */ +int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state, + xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) { + LOG("window class changed\n"); + if (prop == NULL || xcb_get_property_value_length(prop) == 0) { + LOG("prop == NULL\n"); + return 1; + } + Client *client = table_get(&by_child, window); + if (client == NULL) + return 1; + char *new_class; + if (asprintf(&new_class, "%.*s", xcb_get_property_value_length(prop), (char*)xcb_get_property_value(prop)) == -1) { + perror("Could not get window class"); + LOG("Could not get window class\n"); + return 1; + } + + LOG("changed to %s\n", new_class); + char *old_class = client->window_class; + client->window_class = new_class; + FREE(old_class); + + if (!client->initialized) { + LOG("Client is not yet initialized, not putting it to floating\n"); + return 1; + } + + if (strcmp(new_class, "tools") == 0 || strcmp(new_class, "Dialog") == 0) { + LOG("tool/dialog window, should we put it floating?\n"); + if (client->floating == FLOATING_AUTO_OFF) + toggle_floating_mode(conn, client, true); + } + + return 1; +} + /* * Expose event means we should redraw our windows (= title bar) * @@ -626,7 +912,7 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * return 1; LOG("window = %08x\n", event->window); - Client *client = table_get(byParent, event->window); + Client *client = table_get(&by_parent, event->window); if (client == NULL) { /* There was no client in the table, so this is probably an expose event for one of our stack_windows. */ @@ -639,11 +925,9 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * /* …or one of the bars? */ i3Screen *screen; - TAILQ_FOREACH(screen, virtual_screens, screens) { - if (screen->bar == event->window) { + TAILQ_FOREACH(screen, virtual_screens, screens) + if (screen->bar == event->window) render_layout(conn); - } - } return 1; } @@ -653,15 +937,15 @@ int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t * return 1; } - if (client->container->mode != MODE_STACK) + if (client->container == NULL || client->container->mode != MODE_STACK) decorate_window(conn, client, client->frame, client->titlegc, 0); else { uint32_t background_color; /* Distinguish if the window is currently focused… */ if (CUR_CELL->currently_focused == client) - background_color = get_colorpixel(conn, "#285577"); + background_color = config.client.focused.background; /* …or if it is the focused window in a not focused container */ - else background_color = get_colorpixel(conn, "#555555"); + else background_color = config.client.focused_inactive.background; /* Set foreground color to current focused color, line width to 2 */ uint32_t values[] = {background_color, 2}; @@ -696,7 +980,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message LOG("fullscreen\n"); - Client *client = table_get(byChild, event->window); + Client *client = table_get(&by_child, event->window); if (client == NULL) return 0; @@ -707,7 +991,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message (!client->fullscreen && (event->data.data32[0] == _NET_WM_STATE_ADD || event->data.data32[0] == _NET_WM_STATE_TOGGLE))) - toggle_fullscreen(conn, client); + client_toggle_fullscreen(conn, client); } else { LOG("unhandled clientmessage\n"); return 0; @@ -716,7 +1000,7 @@ int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message return 1; } -int window_type_handler(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, +int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property) { /* TODO: Implement this one. To do this, implement a little test program which sleep(1)s before changing this property. */ @@ -734,12 +1018,13 @@ int window_type_handler(void *data, xcb_connection_t *conn, uint8_t state, xcb_w int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, xcb_atom_t name, xcb_get_property_reply_t *reply) { LOG("handle_normal_hints\n"); - Client *client = table_get(byChild, window); + Client *client = table_get(&by_child, window); if (client == NULL) { LOG("No such client\n"); return 1; } xcb_size_hints_t size_hints; + LOG("client is %08x / child %08x\n", client->frame, client->child); /* If the hints were already in this event, use them, if not, request them */ if (reply != NULL) @@ -747,6 +1032,11 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w else xcb_get_wm_normal_hints_reply(conn, xcb_get_wm_normal_hints_unchecked(conn, client->child), &size_hints, NULL); + if ((size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE)) { + LOG("min size set\n"); + LOG("gots min_width = %d, min_height = %d\n", size_hints.min_width, size_hints.min_height); + } + /* If no aspect ratio was set or if it was invalid, we ignore the hints */ if (!(size_hints.flags & XCB_SIZE_HINT_P_ASPECT) || (size_hints.min_aspect_num <= 0) || @@ -757,8 +1047,7 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w LOG("window is %08x / %s\n", client->child, client->name); - int base_width = 0, base_height = 0, - min_width = 0, min_height = 0; + int base_width = 0, base_height = 0; /* base_width/height are the desired size of the window. We check if either the program-specified size or the program-specified @@ -771,14 +1060,6 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w base_height = size_hints.min_height; } - if (size_hints.flags & XCB_SIZE_HINT_P_MIN_SIZE) { - min_width = size_hints.min_width; - min_height = size_hints.min_height; - } else if (size_hints.flags & XCB_SIZE_HINT_P_SIZE) { - min_width = size_hints.base_width; - min_height = size_hints.base_height; - } - double width = client->rect.width - base_width; double height = client->rect.height - base_height; /* Convert numerator/denominator to a double */ @@ -810,3 +1091,70 @@ int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_w return 1; } + +/* + * Handles the transient for hints set by a window, signalizing that this window is a popup window + * for some other window. + * + * See ICCCM 4.1.2.6 for more details + * + */ +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) { + LOG("Transient hint!\n"); + Client *client = table_get(&by_child, window); + if (client == NULL) { + LOG("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)) { + LOG("Not transient for any window\n"); + return 1; + } + } else { + if (!xcb_get_wm_transient_for_reply(conn, xcb_get_wm_transient_for_unchecked(conn, window), + &transient_for, NULL)) { + LOG("Not transient for any window\n"); + return 1; + } + } + + if (client->floating == FLOATING_AUTO_OFF) { + LOG("This is a popup window, putting into floating\n"); + toggle_floating_mode(conn, client, true); + } + + 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) { + LOG("client leader changed\n"); + if (prop == NULL) { + prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, + false, window, WM_CLIENT_LEADER, WINDOW, 0, 32), NULL); + } + + Client *client = table_get(&by_child, window); + if (client == NULL) + return 1; + + xcb_window_t *leader = xcb_get_property_value(prop); + if (leader == NULL) + return 1; + + LOG("changed to %08x\n", *leader); + + client->leader = *leader; + + return 1; +}