i3
src/handlers.c
Go to the documentation of this file.
00001 /*
00002  * vim:ts=4:sw=4:expandtab
00003  *
00004  * i3 - an improved dynamic tiling window manager
00005  * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
00006  *
00007  */
00008 #include <time.h>
00009 #include <limits.h>
00010 
00011 #include <xcb/randr.h>
00012 
00013 #include <X11/XKBlib.h>
00014 
00015 #include "all.h"
00016 
00017 int randr_base = -1;
00018 
00019 /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it,
00020    since it’d trigger an infinite loop of switching between the different windows when
00021    changing workspaces */
00022 static SLIST_HEAD(ignore_head, Ignore_Event) ignore_events;
00023 
00024 /*
00025  * Adds the given sequence to the list of events which are ignored.
00026  * If this ignore should only affect a specific response_type, pass
00027  * response_type, otherwise, pass -1.
00028  *
00029  * Every ignored sequence number gets garbage collected after 5 seconds.
00030  *
00031  */
00032 void add_ignore_event(const int sequence, const int response_type) {
00033     struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event));
00034 
00035     event->sequence = sequence;
00036     event->response_type = response_type;
00037     event->added = time(NULL);
00038 
00039     SLIST_INSERT_HEAD(&ignore_events, event, ignore_events);
00040 }
00041 
00042 /*
00043  * Checks if the given sequence is ignored and returns true if so.
00044  *
00045  */
00046 bool event_is_ignored(const int sequence, const int response_type) {
00047     struct Ignore_Event *event;
00048     time_t now = time(NULL);
00049     for (event = SLIST_FIRST(&ignore_events); event != SLIST_END(&ignore_events);) {
00050         if ((now - event->added) > 5) {
00051             struct Ignore_Event *save = event;
00052             event = SLIST_NEXT(event, ignore_events);
00053             SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events);
00054             free(save);
00055         } else event = SLIST_NEXT(event, ignore_events);
00056     }
00057 
00058     SLIST_FOREACH(event, &ignore_events, ignore_events) {
00059         if (event->sequence != sequence)
00060             continue;
00061 
00062         if (event->response_type != -1 &&
00063             event->response_type != response_type)
00064             continue;
00065 
00066         /* instead of removing a sequence number we better wait until it gets
00067          * garbage collected. it may generate multiple events (there are multiple
00068          * enter_notifies for one configure_request, for example). */
00069         //SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events);
00070         //free(event);
00071         return true;
00072     }
00073 
00074     return false;
00075 }
00076 
00077 
00078 /*
00079  * There was a key press. We compare this key code with our bindings table and pass
00080  * the bound action to parse_command().
00081  *
00082  */
00083 static int handle_key_press(xcb_key_press_event_t *event) {
00084     DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
00085 
00086     /* Remove the numlock bit, all other bits are modifiers we can bind to */
00087     uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
00088     DLOG("(removed numlock, state = %d)\n", state_filtered);
00089     /* Only use the lower 8 bits of the state (modifier masks) so that mouse
00090      * button masks are filtered out */
00091     state_filtered &= 0xFF;
00092     DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
00093 
00094     if (xkb_current_group == XkbGroup2Index)
00095         state_filtered |= BIND_MODE_SWITCH;
00096 
00097     DLOG("(checked mode_switch, state %d)\n", state_filtered);
00098 
00099     /* Find the binding */
00100     Binding *bind = get_binding(state_filtered, event->detail);
00101 
00102     /* No match? Then the user has Mode_switch enabled but does not have a
00103      * specific keybinding. Fall back to the default keybindings (without
00104      * Mode_switch). Makes it much more convenient for users of a hybrid
00105      * layout (like us, ru). */
00106     if (bind == NULL) {
00107         state_filtered &= ~(BIND_MODE_SWITCH);
00108         DLOG("no match, new state_filtered = %d\n", state_filtered);
00109         if ((bind = get_binding(state_filtered, event->detail)) == NULL) {
00110             ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
00111                  state_filtered, event->detail);
00112             return 1;
00113         }
00114     }
00115 
00116     parse_cmd(bind->command);
00117     return 1;
00118 }
00119 
00120 /*
00121  * Called with coordinates of an enter_notify event or motion_notify event
00122  * to check if the user crossed virtual screen boundaries and adjust the
00123  * current workspace, if so.
00124  *
00125  */
00126 static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
00127     Output *output;
00128 
00129     /* If the user disable focus follows mouse, we have nothing to do here */
00130     if (config.disable_focus_follows_mouse)
00131         return;
00132 
00133     if ((output = get_output_containing(x, y)) == NULL) {
00134         ELOG("ERROR: No such screen\n");
00135         return;
00136     }
00137 
00138     if (output->con == NULL) {
00139         ELOG("ERROR: The screen is not recognized by i3 (no container associated)\n");
00140         return;
00141     }
00142 
00143     /* Focus the output on which the user moved his cursor */
00144     Con *old_focused = focused;
00145     con_focus(con_descend_focused(output_get_content(output->con)));
00146 
00147     /* If the focus changed, we re-render to get updated decorations */
00148     if (old_focused != focused)
00149         tree_render();
00150 }
00151 
00152 /*
00153  * When the user moves the mouse pointer onto a window, this callback gets called.
00154  *
00155  */
00156 static int handle_enter_notify(xcb_enter_notify_event_t *event) {
00157     Con *con;
00158 
00159     DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n",
00160          event->event, event->mode, event->detail, event->sequence);
00161     DLOG("coordinates %d, %d\n", event->event_x, event->event_y);
00162     if (event->mode != XCB_NOTIFY_MODE_NORMAL) {
00163         DLOG("This was not a normal notify, ignoring\n");
00164         return 1;
00165     }
00166     /* Some events are not interesting, because they were not generated
00167      * actively by the user, but by reconfiguration of windows */
00168     if (event_is_ignored(event->sequence, XCB_ENTER_NOTIFY)) {
00169         DLOG("Event ignored\n");
00170         return 1;
00171     }
00172 
00173     bool enter_child = false;
00174     /* Get container by frame or by child window */
00175     if ((con = con_by_frame_id(event->event)) == NULL) {
00176         con = con_by_window_id(event->event);
00177         enter_child = true;
00178     }
00179 
00180     /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */
00181     if (con == NULL) {
00182         DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
00183         check_crossing_screen_boundary(event->root_x, event->root_y);
00184         return 1;
00185     }
00186 
00187     if (con->parent->type == CT_DOCKAREA) {
00188         DLOG("Ignoring, this is a dock client\n");
00189         return 1;
00190     }
00191 
00192     /* see if the user entered the window on a certain window decoration */
00193     int layout = (enter_child ? con->parent->layout : con->layout);
00194     if (layout == L_DEFAULT) {
00195         Con *child;
00196         TAILQ_FOREACH(child, &(con->nodes_head), nodes)
00197             if (rect_contains(child->deco_rect, event->event_x, event->event_y)) {
00198                 LOG("using child %p / %s instead!\n", child, child->name);
00199                 con = child;
00200                 break;
00201             }
00202     }
00203 
00204 #if 0
00205     if (client->workspace != c_ws && client->workspace->output == c_ws->output) {
00206             /* This can happen when a client gets assigned to a different workspace than
00207              * the current one (see src/mainx.c:reparent_window). Shortly after it was created,
00208              * an enter_notify will follow. */
00209             DLOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
00210             return 1;
00211     }
00212 #endif
00213 
00214     if (config.disable_focus_follows_mouse)
00215         return 1;
00216 
00217     con_focus(con_descend_focused(con));
00218     tree_render();
00219 
00220     return 1;
00221 }
00222 
00223 /*
00224  * When the user moves the mouse but does not change the active window
00225  * (e.g. when having no windows opened but moving mouse on the root screen
00226  * and crossing virtual screen boundaries), this callback gets called.
00227  *
00228  */
00229 static int handle_motion_notify(xcb_motion_notify_event_t *event) {
00230     /* Skip events where the pointer was over a child window, we are only
00231      * interested in events on the root window. */
00232     if (event->child != 0)
00233         return 1;
00234 
00235     Con *con;
00236     if ((con = con_by_frame_id(event->event)) == NULL) {
00237         check_crossing_screen_boundary(event->root_x, event->root_y);
00238         return 1;
00239     }
00240 
00241     if (config.disable_focus_follows_mouse)
00242         return 1;
00243 
00244     if (con->layout != L_DEFAULT)
00245         return 1;
00246 
00247     /* see over which rect the user is */
00248     Con *current;
00249     TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
00250         if (!rect_contains(current->deco_rect, event->event_x, event->event_y))
00251             continue;
00252 
00253         /* We found the rect, let’s see if this window is focused */
00254         if (TAILQ_FIRST(&(con->focus_head)) == current)
00255             return 1;
00256 
00257         con_focus(current);
00258         x_push_changes(croot);
00259         return 1;
00260     }
00261 
00262     return 1;
00263 }
00264 
00265 /*
00266  * Called when the keyboard mapping changes (for example by using Xmodmap),
00267  * we need to update our key bindings then (re-translate symbols).
00268  *
00269  */
00270 static int handle_mapping_notify(xcb_mapping_notify_event_t *event) {
00271     if (event->request != XCB_MAPPING_KEYBOARD &&
00272         event->request != XCB_MAPPING_MODIFIER)
00273         return 0;
00274 
00275     DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
00276     xcb_refresh_keyboard_mapping(keysyms, event);
00277 
00278     xcb_get_numlock_mask(conn);
00279 
00280     ungrab_all_keys(conn);
00281     translate_keysyms();
00282     grab_all_keys(conn, false);
00283 
00284     return 0;
00285 }
00286 
00287 /*
00288  * A new window appeared on the screen (=was mapped), so let’s manage it.
00289  *
00290  */
00291 static int handle_map_request(xcb_map_request_event_t *event) {
00292     xcb_get_window_attributes_cookie_t cookie;
00293 
00294     cookie = xcb_get_window_attributes_unchecked(conn, event->window);
00295 
00296     DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
00297     add_ignore_event(event->sequence, -1);
00298 
00299     manage_window(event->window, cookie, false);
00300     x_push_changes(croot);
00301     return 1;
00302 }
00303 
00304 /*
00305  * Configure requests are received when the application wants to resize windows on their own.
00306  *
00307  * We generate a synthethic configure notify event to signalize the client its "new" position.
00308  *
00309  */
00310 static int handle_configure_request(xcb_configure_request_event_t *event) {
00311     Con *con;
00312 
00313     DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
00314         event->window, event->x, event->y, event->width, event->height);
00315 
00316     /* For unmanaged windows, we just execute the configure request. As soon as
00317      * it gets mapped, we will take over anyways. */
00318     if ((con = con_by_window_id(event->window)) == NULL) {
00319         DLOG("Configure request for unmanaged window, can do that.\n");
00320 
00321         uint32_t mask = 0;
00322         uint32_t values[7];
00323         int c = 0;
00324 #define COPY_MASK_MEMBER(mask_member, event_member) do { \
00325         if (event->value_mask & mask_member) { \
00326             mask |= mask_member; \
00327             values[c++] = event->event_member; \
00328         } \
00329 } while (0)
00330 
00331         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x);
00332         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y);
00333         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width);
00334         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height);
00335         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width);
00336         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling);
00337         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode);
00338 
00339         xcb_configure_window(conn, event->window, mask, values);
00340         xcb_flush(conn);
00341 
00342         return 1;
00343     }
00344 
00345     DLOG("Configure request!\n");
00346     if (con_is_floating(con) && con_is_leaf(con)) {
00347         /* find the height for the decorations */
00348         int deco_height = config.font.height + 5;
00349         /* we actually need to apply the size/position changes to the *parent*
00350          * container */
00351         Rect bsr = con_border_style_rect(con);
00352         if (con->border_style == BS_NORMAL) {
00353             bsr.y += deco_height;
00354             bsr.height -= deco_height;
00355         }
00356         con = con->parent;
00357         DLOG("Container is a floating leaf node, will do that.\n");
00358         if (event->value_mask & XCB_CONFIG_WINDOW_X) {
00359             con->rect.x = event->x + (-1) * bsr.x;
00360             DLOG("proposed x = %d, new x is %d\n", event->x, con->rect.x);
00361         }
00362         if (event->value_mask & XCB_CONFIG_WINDOW_Y) {
00363             con->rect.y = event->y + (-1) * bsr.y;
00364             DLOG("proposed y = %d, new y is %d\n", event->y, con->rect.y);
00365         }
00366         if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) {
00367             con->rect.width = event->width + (-1) * bsr.width;
00368             DLOG("proposed width = %d, new width is %d\n", event->width, con->rect.width);
00369         }
00370         if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
00371             con->rect.height = event->height + (-1) * bsr.height;
00372             DLOG("proposed height = %d, new height is %d\n", event->height, con->rect.height);
00373         }
00374         tree_render();
00375     }
00376 
00377     fake_absolute_configure_notify(con);
00378 
00379     return 1;
00380 #if 0
00381         /* Dock clients can be reconfigured in their height */
00382         if (client->dock) {
00383                 DLOG("Reconfiguring height of this dock client\n");
00384 
00385                 if (!(event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) {
00386                         DLOG("Ignoring configure request, no height given\n");
00387                         return 1;
00388                 }
00389 
00390                 client->desired_height = event->height;
00391                 render_workspace(conn, c_ws->output, c_ws);
00392                 xcb_flush(conn);
00393 
00394                 return 1;
00395         }
00396 
00397         if (client->fullscreen) {
00398                 DLOG("Client is in fullscreen mode\n");
00399 
00400                 Rect child_rect = client->container->workspace->rect;
00401                 child_rect.x = child_rect.y = 0;
00402                 fake_configure_notify(conn, child_rect, client->child);
00403 
00404                 return 1;
00405         }
00406 
00407         fake_absolute_configure_notify(conn, client);
00408 
00409         return 1;
00410 #endif
00411 }
00412 #if 0
00413 
00414 /*
00415  * Configuration notifies are only handled because we need to set up ignore for
00416  * the following enter notify events.
00417  *
00418  */
00419 int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) {
00420     DLOG("configure_event, sequence %d\n", event->sequence);
00421         /* We ignore this sequence twice because events for child and frame should be ignored */
00422         add_ignore_event(event->sequence);
00423         add_ignore_event(event->sequence);
00424 
00425         return 1;
00426 }
00427 #endif
00428 
00429 /*
00430  * Gets triggered upon a RandR screen change event, that is when the user
00431  * changes the screen configuration in any way (mode, position, …)
00432  *
00433  */
00434 static int handle_screen_change(xcb_generic_event_t *e) {
00435     DLOG("RandR screen change\n");
00436 
00437     randr_query_outputs();
00438 
00439     ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
00440 
00441     return 1;
00442 }
00443 
00444 /*
00445  * Our window decorations were unmapped. That means, the window will be killed
00446  * now, so we better clean up before.
00447  *
00448  */
00449 static int handle_unmap_notify_event(xcb_unmap_notify_event_t *event) {
00450     // XXX: this is commented out because in src/x.c we disable EnterNotify events
00451     /* we need to ignore EnterNotify events which will be generated because a
00452      * different window is visible now */
00453     //add_ignore_event(event->sequence, XCB_ENTER_NOTIFY);
00454 
00455     DLOG("UnmapNotify for 0x%08x (received from 0x%08x), serial %d\n", event->window, event->event, event->sequence);
00456     Con *con = con_by_window_id(event->window);
00457     if (con == NULL) {
00458         /* This could also be an UnmapNotify for the frame. We need to
00459          * decrement the ignore_unmap counter. */
00460         con = con_by_frame_id(event->window);
00461         if (con == NULL) {
00462             LOG("Not a managed window, ignoring UnmapNotify event\n");
00463             return 1;
00464         }
00465         if (con->ignore_unmap > 0)
00466             con->ignore_unmap--;
00467         DLOG("ignore_unmap = %d for frame of container %p\n", con->ignore_unmap, con);
00468         return 1;
00469     }
00470 
00471     if (con->ignore_unmap > 0) {
00472         DLOG("ignore_unmap = %d, dec\n", con->ignore_unmap);
00473         con->ignore_unmap--;
00474         return 1;
00475     }
00476 
00477     tree_close(con, DONT_KILL_WINDOW, false);
00478     tree_render();
00479     x_push_changes(croot);
00480     return 1;
00481 
00482 #if 0
00483         if (client == NULL) {
00484                 DLOG("not a managed window. Ignoring.\n");
00485 
00486                 /* This was most likely the destroyed frame of a client which is
00487                  * currently being unmapped, so we add this sequence (again!) to
00488                  * the ignore list (enter_notify events will get sent for both,
00489                  * the child and its frame). */
00490                 add_ignore_event(event->sequence);
00491 
00492                 return 0;
00493         }
00494 #endif
00495 
00496 
00497 #if 0
00498         /* Let’s see how many clients there are left on the workspace to delete it if it’s empty */
00499         bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack));
00500         bool workspace_focused = (c_ws == client->workspace);
00501         Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL);
00502 
00503         /* If this workspace is currently visible, we don’t delete it */
00504         if (workspace_is_visible(client->workspace))
00505                 workspace_empty = false;
00506 
00507         if (workspace_empty) {
00508                 client->workspace->output = NULL;
00509                 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
00510         }
00511 
00512         /* Remove the urgency flag if set */
00513         client->urgent = false;
00514         workspace_update_urgent_flag(client->workspace);
00515 
00516         render_layout(conn);
00517 #endif
00518 
00519         return 1;
00520 }
00521 
00522 /*
00523  * A destroy notify event is sent when the window is not unmapped, but
00524  * immediately destroyed (for example when starting a window and immediately
00525  * killing the program which started it).
00526  *
00527  * We just pass on the event to the unmap notify handler (by copying the
00528  * important fields in the event data structure).
00529  *
00530  */
00531 static int handle_destroy_notify_event(xcb_destroy_notify_event_t *event) {
00532     DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window);
00533 
00534     xcb_unmap_notify_event_t unmap;
00535     unmap.sequence = event->sequence;
00536     unmap.event = event->event;
00537     unmap.window = event->window;
00538 
00539     return handle_unmap_notify_event(&unmap);
00540 }
00541 
00542 /*
00543  * Called when a window changes its title
00544  *
00545  */
00546 static bool handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
00547                                 xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
00548     Con *con;
00549     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
00550         return false;
00551 
00552     window_update_name(con->window, prop, false);
00553 
00554     x_push_changes(croot);
00555 
00556     return true;
00557 }
00558 
00559 /*
00560  * Handles legacy window name updates (WM_NAME), see also src/window.c,
00561  * window_update_name_legacy().
00562  *
00563  */
00564 static bool handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state,
00565                                 xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
00566     Con *con;
00567     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
00568         return false;
00569 
00570     window_update_name_legacy(con->window, prop, false);
00571 
00572     x_push_changes(croot);
00573 
00574     return true;
00575 }
00576 
00577 #if 0
00578 /*
00579  * Updates the client’s WM_CLASS property
00580  *
00581  */
00582 static int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
00583                              xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
00584     Con *con;
00585     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
00586         return 1;
00587 
00588     window_update_class(con->window, prop, false);
00589 
00590     return 0;
00591 }
00592 #endif
00593 
00594 /*
00595  * Expose event means we should redraw our windows (= title bar)
00596  *
00597  */
00598 static int handle_expose_event(xcb_expose_event_t *event) {
00599     Con *parent;
00600 
00601     /* event->count is the number of minimum remaining expose events for this
00602      * window, so we skip all events but the last one */
00603     if (event->count != 0)
00604         return 1;
00605 
00606     DLOG("window = %08x\n", event->window);
00607 
00608     if ((parent = con_by_frame_id(event->window)) == NULL) {
00609         LOG("expose event for unknown window, ignoring\n");
00610         return 1;
00611     }
00612 
00613     /* re-render the parent (recursively, if it’s a split con) */
00614     x_deco_recurse(parent);
00615     xcb_flush(conn);
00616 
00617     return 1;
00618 }
00619 
00620 /*
00621  * Handle client messages (EWMH)
00622  *
00623  */
00624 static int handle_client_message(xcb_client_message_event_t *event) {
00625     LOG("ClientMessage for window 0x%08x\n", event->window);
00626     if (event->type == A__NET_WM_STATE) {
00627         if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) {
00628             DLOG("atom in clientmessage is %d, fullscreen is %d\n",
00629                     event->data.data32[1], A__NET_WM_STATE_FULLSCREEN);
00630             DLOG("not about fullscreen atom\n");
00631             return 0;
00632         }
00633 
00634         Con *con = con_by_window_id(event->window);
00635         if (con == NULL) {
00636             DLOG("Could not get window for client message\n");
00637             return 0;
00638         }
00639 
00640         /* Check if the fullscreen state should be toggled */
00641         if ((con->fullscreen_mode != CF_NONE &&
00642              (event->data.data32[0] == _NET_WM_STATE_REMOVE ||
00643               event->data.data32[0] == _NET_WM_STATE_TOGGLE)) ||
00644             (con->fullscreen_mode == CF_NONE &&
00645              (event->data.data32[0] == _NET_WM_STATE_ADD ||
00646               event->data.data32[0] == _NET_WM_STATE_TOGGLE))) {
00647             DLOG("toggling fullscreen\n");
00648             con_toggle_fullscreen(con, CF_OUTPUT);
00649         }
00650 
00651         tree_render();
00652         x_push_changes(croot);
00653     } else {
00654         ELOG("unhandled clientmessage\n");
00655         return 0;
00656     }
00657 
00658     return 1;
00659 }
00660 
00661 #if 0
00662 int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
00663                         xcb_atom_t atom, xcb_get_property_reply_t *property) {
00664         /* TODO: Implement this one. To do this, implement a little test program which sleep(1)s
00665          before changing this property. */
00666         ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
00667         return 0;
00668 }
00669 #endif
00670 
00671 /*
00672  * Handles the size hints set by a window, but currently only the part necessary for displaying
00673  * clients proportionally inside their frames (mplayer for example)
00674  *
00675  * See ICCCM 4.1.2.3 for more details
00676  *
00677  */
00678 static bool handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
00679                         xcb_atom_t name, xcb_get_property_reply_t *reply) {
00680     Con *con = con_by_window_id(window);
00681     if (con == NULL) {
00682         DLOG("Received WM_NORMAL_HINTS for unknown client\n");
00683         return false;
00684     }
00685 
00686     xcb_size_hints_t size_hints;
00687 
00688         //CLIENT_LOG(client);
00689 
00690     /* If the hints were already in this event, use them, if not, request them */
00691     if (reply != NULL)
00692         xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
00693     else
00694         xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL);
00695 
00696     if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) {
00697         // TODO: Minimum size is not yet implemented
00698         DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
00699     }
00700 
00701     bool changed = false;
00702     if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
00703         if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
00704             if (con->width_increment != size_hints.width_inc) {
00705                 con->width_increment = size_hints.width_inc;
00706                 changed = true;
00707             }
00708         if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
00709             if (con->height_increment != size_hints.height_inc) {
00710                 con->height_increment = size_hints.height_inc;
00711                 changed = true;
00712             }
00713 
00714         if (changed)
00715             DLOG("resize increments changed\n");
00716     }
00717 
00718     int base_width = 0, base_height = 0;
00719 
00720     /* base_width/height are the desired size of the window.
00721        We check if either the program-specified size or the program-specified
00722        min-size is available */
00723     if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) {
00724         base_width = size_hints.base_width;
00725         base_height = size_hints.base_height;
00726     } else if (size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
00727         /* TODO: is this right? icccm says not */
00728         base_width = size_hints.min_width;
00729         base_height = size_hints.min_height;
00730     }
00731 
00732     if (base_width != con->base_width ||
00733         base_height != con->base_height) {
00734         con->base_width = base_width;
00735         con->base_height = base_height;
00736         DLOG("client's base_height changed to %d\n", base_height);
00737         DLOG("client's base_width changed to %d\n", base_width);
00738         changed = true;
00739     }
00740 
00741     /* If no aspect ratio was set or if it was invalid, we ignore the hints */
00742     if (!(size_hints.flags & XCB_ICCCM_SIZE_HINT_P_ASPECT) ||
00743         (size_hints.min_aspect_num <= 0) ||
00744         (size_hints.min_aspect_den <= 0)) {
00745         goto render_and_return;
00746     }
00747 
00748     /* XXX: do we really use rect here, not window_rect? */
00749     double width = con->rect.width - base_width;
00750     double height = con->rect.height - base_height;
00751     /* Convert numerator/denominator to a double */
00752     double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
00753     double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
00754 
00755     DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
00756     DLOG("width = %f, height = %f\n", width, height);
00757 
00758     /* Sanity checks, this is user-input, in a way */
00759     if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0)
00760         goto render_and_return;
00761 
00762     /* Check if we need to set proportional_* variables using the correct ratio */
00763     if ((width / height) < min_aspect) {
00764         if (con->proportional_width != width ||
00765             con->proportional_height != (width / min_aspect)) {
00766             con->proportional_width = width;
00767             con->proportional_height = width / min_aspect;
00768             changed = true;
00769         }
00770     } else if ((width / height) > max_aspect) {
00771         if (con->proportional_width != width ||
00772             con->proportional_height != (width / max_aspect)) {
00773             con->proportional_width = width;
00774             con->proportional_height = width / max_aspect;
00775             changed = true;
00776         }
00777     } else goto render_and_return;
00778 
00779 render_and_return:
00780     if (changed)
00781         tree_render();
00782     FREE(reply);
00783     return true;
00784 }
00785 
00786 /*
00787  * Handles the WM_HINTS property for extracting the urgency state of the window.
00788  *
00789  */
00790 static bool handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
00791                   xcb_atom_t name, xcb_get_property_reply_t *reply) {
00792     Con *con = con_by_window_id(window);
00793     if (con == NULL) {
00794         DLOG("Received WM_HINTS for unknown client\n");
00795         return false;
00796     }
00797 
00798     xcb_icccm_wm_hints_t hints;
00799 
00800     if (reply != NULL) {
00801         if (!xcb_icccm_get_wm_hints_from_reply(&hints, reply))
00802             return false;
00803     } else {
00804         if (!xcb_icccm_get_wm_hints_reply(conn, xcb_icccm_get_wm_hints_unchecked(conn, con->window->id), &hints, NULL))
00805             return false;
00806     }
00807 
00808     if (!con->urgent && focused == con) {
00809         DLOG("Ignoring urgency flag for current client\n");
00810         FREE(reply);
00811         return true;
00812     }
00813 
00814     /* Update the flag on the client directly */
00815     con->urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
00816     //CLIENT_LOG(con);
00817     LOG("Urgency flag changed to %d\n", con->urgent);
00818 
00819     Con *ws;
00820     /* Set the urgency flag on the workspace, if a workspace could be found
00821      * (for dock clients, that is not the case). */
00822     if ((ws = con_get_workspace(con)) != NULL)
00823         workspace_update_urgent_flag(ws);
00824 
00825     tree_render();
00826 
00827 #if 0
00828     /* If the workspace this client is on is not visible, we need to redraw
00829      * the workspace bar */
00830     if (!workspace_is_visible(client->workspace)) {
00831             Output *output = client->workspace->output;
00832             render_workspace(conn, output, output->current_workspace);
00833             xcb_flush(conn);
00834     }
00835 #endif
00836 
00837     FREE(reply);
00838     return true;
00839 }
00840 
00841 /*
00842  * Handles the transient for hints set by a window, signalizing that this window is a popup window
00843  * for some other window.
00844  *
00845  * See ICCCM 4.1.2.6 for more details
00846  *
00847  */
00848 static bool handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
00849                          xcb_atom_t name, xcb_get_property_reply_t *prop) {
00850     Con *con;
00851 
00852     if ((con = con_by_window_id(window)) == NULL || con->window == NULL) {
00853         DLOG("No such window\n");
00854         return false;
00855     }
00856 
00857     if (prop == NULL) {
00858         prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
00859                                 false, window, A_WM_TRANSIENT_FOR, A_WINDOW, 0, 32), NULL);
00860         if (prop == NULL)
00861             return false;
00862     }
00863 
00864     window_update_transient_for(con->window, prop);
00865 
00866     // TODO: put window in floating mode if con->window->transient_for != XCB_NONE:
00867 #if 0
00868     if (client->floating == FLOATING_AUTO_OFF) {
00869         DLOG("This is a popup window, putting into floating\n");
00870         toggle_floating_mode(conn, client, true);
00871     }
00872 #endif
00873 
00874     return true;
00875 }
00876 
00877 /*
00878  * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a
00879  * toolwindow (or similar) and to which window it belongs (logical parent).
00880  *
00881  */
00882 static bool handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
00883                         xcb_atom_t name, xcb_get_property_reply_t *prop) {
00884     Con *con;
00885     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
00886         return false;
00887 
00888     if (prop == NULL) {
00889         prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
00890                                 false, window, A_WM_CLIENT_LEADER, A_WINDOW, 0, 32), NULL);
00891         if (prop == NULL)
00892             return false;
00893     }
00894 
00895     window_update_leader(con->window, prop);
00896 
00897     return true;
00898 }
00899 
00900 /*
00901  * Handles FocusIn events which are generated by clients (i3’s focus changes
00902  * don’t generate FocusIn events due to a different EventMask) and updates the
00903  * decorations accordingly.
00904  *
00905  */
00906 static int handle_focus_in(xcb_focus_in_event_t *event) {
00907     DLOG("focus change in, for window 0x%08x\n", event->event);
00908     Con *con;
00909     if ((con = con_by_window_id(event->event)) == NULL || con->window == NULL)
00910         return 1;
00911     DLOG("That is con %p / %s\n", con, con->name);
00912 
00913     if (event->mode == XCB_NOTIFY_MODE_GRAB ||
00914         event->mode == XCB_NOTIFY_MODE_UNGRAB) {
00915         DLOG("FocusIn event for grab/ungrab, ignoring\n");
00916         return 1;
00917     }
00918 
00919     if (event->detail == XCB_NOTIFY_DETAIL_POINTER) {
00920         DLOG("notify detail is pointer, ignoring this event\n");
00921         return 1;
00922     }
00923 
00924     if (focused_id == event->event) {
00925         DLOG("focus matches the currently focused window, not doing anything\n");
00926         return 1;
00927     }
00928 
00929     DLOG("focus is different, updating decorations\n");
00930     con_focus(con);
00931     /* We update focused_id because we don’t need to set focus again */
00932     focused_id = event->event;
00933     x_push_changes(croot);
00934     return 1;
00935 }
00936 
00937 /* Returns false if the event could not be processed (e.g. the window could not
00938  * be found), true otherwise */
00939 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);
00940 
00941 struct property_handler_t {
00942     xcb_atom_t atom;
00943     uint32_t long_len;
00944     cb_property_handler_t cb;
00945 };
00946 
00947 static struct property_handler_t property_handlers[] = {
00948     { 0, 128, handle_windowname_change },
00949     { 0, UINT_MAX, handle_hints },
00950     { 0, 128, handle_windowname_change_legacy },
00951     { 0, UINT_MAX, handle_normal_hints },
00952     { 0, UINT_MAX, handle_clientleader_change },
00953     { 0, UINT_MAX, handle_transient_for }
00954 };
00955 #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
00956 
00957 /*
00958  * Sets the appropriate atoms for the property handlers after the atoms were
00959  * received from X11
00960  *
00961  */
00962 void property_handlers_init() {
00963     property_handlers[0].atom = A__NET_WM_NAME;
00964     property_handlers[1].atom = A_WM_HINTS;
00965     property_handlers[2].atom = A_WM_NAME;
00966     property_handlers[3].atom = A_WM_NORMAL_HINTS;
00967     property_handlers[4].atom = A_WM_CLIENT_LEADER;
00968     property_handlers[5].atom = A_WM_TRANSIENT_FOR;
00969 }
00970 
00971 static void property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
00972     struct property_handler_t *handler = NULL;
00973     xcb_get_property_reply_t *propr = NULL;
00974 
00975     for (int c = 0; c < sizeof(property_handlers) / sizeof(struct property_handler_t); c++) {
00976         if (property_handlers[c].atom != atom)
00977             continue;
00978 
00979         handler = &property_handlers[c];
00980         break;
00981     }
00982 
00983     if (handler == NULL) {
00984         DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom);
00985         return;
00986     }
00987 
00988     if (state != XCB_PROPERTY_DELETE) {
00989         xcb_get_property_cookie_t cookie = xcb_get_property(conn, 0, window, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, handler->long_len);
00990         propr = xcb_get_property_reply(conn, cookie, 0);
00991     }
00992 
00993     /* the handler will free() the reply unless it returns false */
00994     if (!handler->cb(NULL, conn, state, window, atom, propr))
00995         FREE(propr);
00996 }
00997 
00998 /*
00999  * Takes an xcb_generic_event_t and calls the appropriate handler, based on the
01000  * event type.
01001  *
01002  */
01003 void handle_event(int type, xcb_generic_event_t *event) {
01004     if (randr_base > -1 &&
01005         type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
01006         handle_screen_change(event);
01007         return;
01008     }
01009 
01010     switch (type) {
01011         case XCB_KEY_PRESS:
01012             handle_key_press((xcb_key_press_event_t*)event);
01013             break;
01014 
01015         case XCB_BUTTON_PRESS:
01016             handle_button_press((xcb_button_press_event_t*)event);
01017             break;
01018 
01019         case XCB_MAP_REQUEST:
01020             handle_map_request((xcb_map_request_event_t*)event);
01021             break;
01022 
01023         case XCB_UNMAP_NOTIFY:
01024             handle_unmap_notify_event((xcb_unmap_notify_event_t*)event);
01025             break;
01026 
01027         case XCB_DESTROY_NOTIFY:
01028             handle_destroy_notify_event((xcb_destroy_notify_event_t*)event);
01029             break;
01030 
01031         case XCB_EXPOSE:
01032             handle_expose_event((xcb_expose_event_t*)event);
01033             break;
01034 
01035         case XCB_MOTION_NOTIFY:
01036             handle_motion_notify((xcb_motion_notify_event_t*)event);
01037             break;
01038 
01039         /* Enter window = user moved his mouse over the window */
01040         case XCB_ENTER_NOTIFY:
01041             handle_enter_notify((xcb_enter_notify_event_t*)event);
01042             break;
01043 
01044         /* Client message are sent to the root window. The only interesting
01045          * client message for us is _NET_WM_STATE, we honour
01046          * _NET_WM_STATE_FULLSCREEN */
01047         case XCB_CLIENT_MESSAGE:
01048             handle_client_message((xcb_client_message_event_t*)event);
01049             break;
01050 
01051         /* Configure request = window tried to change size on its own */
01052         case XCB_CONFIGURE_REQUEST:
01053             handle_configure_request((xcb_configure_request_event_t*)event);
01054             break;
01055 
01056         /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */
01057         case XCB_MAPPING_NOTIFY:
01058             handle_mapping_notify((xcb_mapping_notify_event_t*)event);
01059             break;
01060 
01061         case XCB_FOCUS_IN:
01062             handle_focus_in((xcb_focus_in_event_t*)event);
01063             break;
01064 
01065         case XCB_PROPERTY_NOTIFY:
01066             DLOG("Property notify\n");
01067             xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event;
01068             property_notify(e->state, e->window, e->atom);
01069             break;
01070 
01071         default:
01072             DLOG("Unhandled event of type %d\n", type);
01073             break;
01074     }
01075 }