X-Git-Url: https://git.sur5r.net/?a=blobdiff_plain;f=src%2Fhandlers.c;h=d7d48a1cfb5847c9cfcf5c1ce728717d495925d1;hb=94a09b3cac98a3bc1ed580fce16aa9869f83bbaa;hp=ba9b12117ad53a1727f164783b8ba8df3552985d;hpb=4c06e7a573e450329212b28a3b8f4a5e89b326d4;p=i3%2Fi3 diff --git a/src/handlers.c b/src/handlers.c index ba9b1211..d7d48a1c 100644 --- a/src/handlers.c +++ b/src/handlers.c @@ -4,7 +4,7 @@ * vim:ts=4:sw=4:expandtab * * i3 - an improved dynamic tiling window manager - * © 2009-2012 Michael Stapelberg and contributors (see also: LICENSE) + * © 2009 Michael Stapelberg and contributors (see also: LICENSE) * * handlers.c: Small handlers for various events (keypresses, focus changes, * …). @@ -21,6 +21,8 @@ #include int randr_base = -1; +int xkb_base = -1; +int xkb_current_group; /* 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 @@ -104,7 +106,7 @@ static void check_crossing_screen_boundary(uint32_t x, uint32_t y) { return; } - /* Focus the output on which the user moved his cursor */ + /* Focus the output on which the user moved their cursor */ Con *old_focused = focused; Con *next = con_descend_focused(output_get_content(output->con)); /* Since we are switching outputs, this *must* be a different workspace, so @@ -147,7 +149,7 @@ static void handle_enter_notify(xcb_enter_notify_event_t *event) { enter_child = true; } - /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */ + /* If not, then the user moved their cursor to the root window. In that case, we adjust c_ws */ if (con == NULL) { DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y); check_crossing_screen_boundary(event->root_x, event->root_y); @@ -213,7 +215,7 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) { /* Skip events where the pointer was over a child window, we are only * interested in events on the root window. */ - if (event->child != 0) + if (event->child != XCB_NONE) return; Con *con; @@ -226,7 +228,7 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) { if (config.disable_focus_follows_mouse) return; - if (con->layout != L_DEFAULT) + if (con->layout != L_DEFAULT && con->layout != L_SPLITV && con->layout != L_SPLITH) return; /* see over which rect the user is */ @@ -243,8 +245,6 @@ static void handle_motion_notify(xcb_motion_notify_event_t *event) { x_push_changes(croot); return; } - - return; } /* @@ -282,7 +282,6 @@ static void handle_map_request(xcb_map_request_event_t *event) { add_ignore_event(event->sequence, -1); manage_window(event->window, cookie, false); - x_push_changes(croot); return; } @@ -487,7 +486,6 @@ static void handle_unmap_notify_event(xcb_unmap_notify_event_t *event) { tree_close(con, DONT_KILL_WINDOW, false, false); tree_render(); - x_push_changes(croot); ignore_end: /* If the client (as opposed to i3) destroyed or unmapped a window, an @@ -651,6 +649,19 @@ static void handle_expose_event(xcb_expose_event_t *event) { return; } +#define _NET_WM_MOVERESIZE_SIZE_TOPLEFT 0 +#define _NET_WM_MOVERESIZE_SIZE_TOP 1 +#define _NET_WM_MOVERESIZE_SIZE_TOPRIGHT 2 +#define _NET_WM_MOVERESIZE_SIZE_RIGHT 3 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMRIGHT 4 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOM 5 +#define _NET_WM_MOVERESIZE_SIZE_BOTTOMLEFT 6 +#define _NET_WM_MOVERESIZE_SIZE_LEFT 7 +#define _NET_WM_MOVERESIZE_MOVE 8 /* movement only */ +#define _NET_WM_MOVERESIZE_SIZE_KEYBOARD 9 /* size via keyboard */ +#define _NET_WM_MOVERESIZE_MOVE_KEYBOARD 10 /* move via keyboard */ +#define _NET_WM_MOVERESIZE_CANCEL 11 /* cancel operation */ + /* * Handle client messages (EWMH) * @@ -730,16 +741,17 @@ static void handle_client_message(xcb_client_message_event_t *event) { workspace_show(ws); con_focus(con); } else { - /* If the request is from an application, only focus if the - * workspace is visible. Otherwise set the urgency hint. */ - if (workspace_is_visible(ws)) { - DLOG("Request to focus con on a visible workspace. Focusing con = %p\n", con); + /* Request is from an application. */ + + if (config.focus_on_window_activation == FOWA_FOCUS || (config.focus_on_window_activation == FOWA_SMART && workspace_is_visible(ws))) { + DLOG("Focusing con = %p\n", con); workspace_show(ws); con_focus(con); - } else { - DLOG("Request to focus con on a hidden workspace. Setting urgent con = %p\n", con); + } else if (config.focus_on_window_activation == FOWA_URGENT || (config.focus_on_window_activation == FOWA_SMART && !workspace_is_visible(ws))) { + DLOG("Marking con = %p urgent\n", con); con_set_urgency(con, true); - } + } else + DLOG("Ignoring request for con = %p.\n", con); } tree_render(); @@ -791,21 +803,102 @@ static void handle_client_message(xcb_client_message_event_t *event) { XCB_ATOM_CARDINAL, 32, 4, &r); xcb_flush(conn); + } else if (event->type == A__NET_CURRENT_DESKTOP) { + /* This request is used by pagers and bars to change the current + * desktop likely as a result of some user action. We interpret this as + * a request to focus the given workspace. See + * http://standards.freedesktop.org/wm-spec/latest/ar01s03.html#idm140251368135008 + * */ + Con *output; + uint32_t idx = 0; + DLOG("Request to change current desktop to index %d\n", event->data.data32[0]); + + TAILQ_FOREACH(output, &(croot->nodes_head), nodes) { + Con *ws; + TAILQ_FOREACH(ws, &(output_get_content(output)->nodes_head), nodes) { + if (STARTS_WITH(ws->name, "__")) + continue; + + if (idx == event->data.data32[0]) { + /* data32[1] is a timestamp used to prevent focus race conditions */ + if (event->data.data32[1]) + last_timestamp = event->data.data32[1]; + + DLOG("Handling request to focus workspace %s\n", ws->name); + + workspace_show(ws); + tree_render(); + + return; + } + + ++idx; + } + } + } else if (event->type == A__NET_CLOSE_WINDOW) { + /* + * Pagers wanting to close a window MUST send a _NET_CLOSE_WINDOW + * client message request to the root window. + * http://standards.freedesktop.org/wm-spec/wm-spec-latest.html#idm140200472668896 + */ + Con *con = con_by_window_id(event->window); + if (con) { + DLOG("Handling _NET_CLOSE_WINDOW request (con = %p)\n", con); + + if (event->data.data32[0]) + last_timestamp = event->data.data32[0]; + + tree_close(con, KILL_WINDOW, false, false); + tree_render(); + } else { + DLOG("Couldn't find con for _NET_CLOSE_WINDOW request. (window = %d)\n", event->window); + } + } else if (event->type == A__NET_WM_MOVERESIZE) { + /* + * Client-side decorated Gtk3 windows emit this signal when being + * dragged by their GtkHeaderBar + */ + Con *con = con_by_window_id(event->window); + if (!con || !con_is_floating(con)) { + DLOG("Couldn't find con for _NET_WM_MOVERESIZE request, or con not floating (window = %d)\n", event->window); + return; + } + DLOG("Handling _NET_WM_MOVERESIZE request (con = %p)\n", con); + uint32_t direction = event->data.data32[2]; + uint32_t x_root = event->data.data32[0]; + uint32_t y_root = event->data.data32[1]; + /* construct fake xcb_button_press_event_t */ + xcb_button_press_event_t fake = { + .root_x = x_root, + .root_y = y_root, + .event_x = x_root - (con->rect.x), + .event_y = y_root - (con->rect.y)}; + switch (direction) { + case _NET_WM_MOVERESIZE_MOVE: + floating_drag_window(con->parent, &fake); + break; + case _NET_WM_MOVERESIZE_SIZE_TOPLEFT... _NET_WM_MOVERESIZE_SIZE_LEFT: + floating_resize_window(con->parent, false, &fake); + break; + default: + DLOG("_NET_WM_MOVERESIZE direction %d not implemented\n", direction); + break; + } } else { DLOG("unhandled clientmessage\n"); return; } } -#if 0 -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. */ - ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n"); - return 0; +bool 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 *reply) { + Con *con; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) + return false; + + window_update_type(con->window, reply); + return true; } -#endif /* * Handles the size hints set by a window, but currently only the part necessary for displaying @@ -1045,6 +1138,111 @@ static void handle_focus_in(xcb_focus_in_event_t *event) { return; } +/* + * Handles the WM_CLASS property for assignments and criteria selection. + * + */ +static bool handle_class_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 false; + + if (prop == NULL) { + prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn, + false, window, XCB_ATOM_WM_CLASS, XCB_ATOM_STRING, 0, 32), + NULL); + + if (prop == NULL) + return false; + } + + window_update_class(con->window, prop, false); + + return true; +} + +/* + * Handles the _NET_WM_STRUT_PARTIAL property for allocating space for dock clients. + * + */ +static bool handle_strut_partial_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window, + xcb_atom_t name, xcb_get_property_reply_t *prop) { + DLOG("strut partial change for window 0x%08x\n", window); + + Con *con; + if ((con = con_by_window_id(window)) == NULL || con->window == NULL) { + return false; + } + + if (prop == NULL) { + xcb_generic_error_t *err = NULL; + xcb_get_property_cookie_t strut_cookie = xcb_get_property(conn, false, window, A__NET_WM_STRUT_PARTIAL, + XCB_GET_PROPERTY_TYPE_ANY, 0, UINT32_MAX); + prop = xcb_get_property_reply(conn, strut_cookie, &err); + + if (err != NULL) { + DLOG("got error when getting strut partial property: %d\n", err->error_code); + free(err); + return false; + } + + if (prop == NULL) { + return false; + } + } + + DLOG("That is con %p / %s\n", con, con->name); + + window_update_strut_partial(con->window, prop); + + /* we only handle this change for dock clients */ + if (con->parent == NULL || con->parent->type != CT_DOCKAREA) { + return true; + } + + Con *search_at = croot; + Con *output = con_get_output(con); + if (output != NULL) { + DLOG("Starting search at output %s\n", output->name); + search_at = output; + } + + /* find out the desired position of this dock window */ + if (con->window->reserved.top > 0 && con->window->reserved.bottom == 0) { + DLOG("Top dock client\n"); + con->window->dock = W_DOCK_TOP; + } else if (con->window->reserved.top == 0 && con->window->reserved.bottom > 0) { + DLOG("Bottom dock client\n"); + con->window->dock = W_DOCK_BOTTOM; + } else { + DLOG("Ignoring invalid reserved edges (_NET_WM_STRUT_PARTIAL), using position as fallback:\n"); + if (con->geometry.y < (search_at->rect.height / 2)) { + DLOG("geom->y = %d < rect.height / 2 = %d, it is a top dock client\n", + con->geometry.y, (search_at->rect.height / 2)); + con->window->dock = W_DOCK_TOP; + } else { + DLOG("geom->y = %d >= rect.height / 2 = %d, it is a bottom dock client\n", + con->geometry.y, (search_at->rect.height / 2)); + con->window->dock = W_DOCK_BOTTOM; + } + } + + /* find the dockarea */ + Con *dockarea = con_for_window(search_at, con->window, NULL); + assert(dockarea != NULL); + + /* attach the dock to the dock area */ + con_detach(con); + con->parent = dockarea; + TAILQ_INSERT_HEAD(&(dockarea->focus_head), con, focused); + TAILQ_INSERT_HEAD(&(dockarea->nodes_head), con, nodes); + + tree_render(); + + return true; +} + /* Returns false if the event could not be processed (e.g. the window could not * be found), true otherwise */ typedef bool (*cb_property_handler_t)(void *data, xcb_connection_t *c, uint8_t state, xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *property); @@ -1062,7 +1260,10 @@ static struct property_handler_t property_handlers[] = { {0, UINT_MAX, handle_normal_hints}, {0, UINT_MAX, handle_clientleader_change}, {0, UINT_MAX, handle_transient_for}, - {0, 128, handle_windowrole_change}}; + {0, 128, handle_windowrole_change}, + {0, 128, handle_class_change}, + {0, UINT_MAX, handle_strut_partial_change}, + {0, UINT_MAX, handle_window_type}}; #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t)) /* @@ -1080,6 +1281,9 @@ void property_handlers_init(void) { property_handlers[4].atom = A_WM_CLIENT_LEADER; property_handlers[5].atom = XCB_ATOM_WM_TRANSIENT_FOR; property_handlers[6].atom = A_WM_WINDOW_ROLE; + property_handlers[7].atom = XCB_ATOM_WM_CLASS; + property_handlers[8].atom = A__NET_WM_STRUT_PARTIAL; + property_handlers[9].atom = A__NET_WM_WINDOW_TYPE; } static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) { @@ -1115,12 +1319,59 @@ static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) * */ void handle_event(int type, xcb_generic_event_t *event) { + DLOG("event type %d, xkb_base %d\n", type, xkb_base); if (randr_base > -1 && type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) { handle_screen_change(event); return; } + if (xkb_base > -1 && type == xkb_base) { + DLOG("xkb event, need to handle it.\n"); + + xcb_xkb_state_notify_event_t *state = (xcb_xkb_state_notify_event_t *)event; + if (state->xkbType == XCB_XKB_NEW_KEYBOARD_NOTIFY) { + DLOG("xkb new keyboard notify, sequence %d, time %d\n", state->sequence, state->time); + xcb_key_symbols_free(keysyms); + keysyms = xcb_key_symbols_alloc(conn); + ungrab_all_keys(conn); + translate_keysyms(); + grab_all_keys(conn, false); + } else if (state->xkbType == XCB_XKB_MAP_NOTIFY) { + if (event_is_ignored(event->sequence, type)) { + DLOG("Ignoring map notify event for sequence %d.\n", state->sequence); + } else { + DLOG("xkb map notify, sequence %d, time %d\n", state->sequence, state->time); + add_ignore_event(event->sequence, type); + xcb_key_symbols_free(keysyms); + keysyms = xcb_key_symbols_alloc(conn); + ungrab_all_keys(conn); + translate_keysyms(); + grab_all_keys(conn, false); + } + } else if (state->xkbType == XCB_XKB_STATE_NOTIFY) { + DLOG("xkb state group = %d\n", state->group); + + /* See The XKB Extension: Library Specification, section 14.1 */ + /* We check if the current group (each group contains + * two levels) has been changed. Mode_switch activates + * group XCB_XKB_GROUP_2 */ + if (xkb_current_group == state->group) + return; + xkb_current_group = state->group; + if (state->group == XCB_XKB_GROUP_1) { + DLOG("Mode_switch disabled\n"); + ungrab_all_keys(conn); + grab_all_keys(conn, false); + } else { + DLOG("Mode_switch enabled\n"); + grab_all_keys(conn, true); + } + } + + return; + } + switch (type) { case XCB_KEY_PRESS: case XCB_KEY_RELEASE: @@ -1128,6 +1379,7 @@ void handle_event(int type, xcb_generic_event_t *event) { break; case XCB_BUTTON_PRESS: + case XCB_BUTTON_RELEASE: handle_button_press((xcb_button_press_event_t *)event); break; @@ -1151,7 +1403,7 @@ void handle_event(int type, xcb_generic_event_t *event) { handle_motion_notify((xcb_motion_notify_event_t *)event); break; - /* Enter window = user moved his mouse over the window */ + /* Enter window = user moved their mouse over the window */ case XCB_ENTER_NOTIFY: handle_enter_notify((xcb_enter_notify_event_t *)event); break;