]> git.sur5r.net Git - i3/i3/blob - src/handlers.c
Handle FocusIn events generated by clients and update decoration accordingly (Thanks...
[i3/i3] / src / handlers.c
1 /*
2  * vim:ts=4:sw=4:expandtab
3  *
4  * i3 - an improved dynamic tiling window manager
5  * © 2009-2010 Michael Stapelberg and contributors (see also: LICENSE)
6  *
7  */
8 #include <time.h>
9 #include <limits.h>
10
11 #include <xcb/randr.h>
12
13 #include <X11/XKBlib.h>
14
15 #include "all.h"
16
17 int randr_base = -1;
18
19 /* forward declaration for property_notify */
20 static int property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom);
21
22 /* After mapping/unmapping windows, a notify event is generated. However, we don’t want it,
23    since it’d trigger an infinite loop of switching between the different windows when
24    changing workspaces */
25 static SLIST_HEAD(ignore_head, Ignore_Event) ignore_events;
26
27 void add_ignore_event(const int sequence) {
28     struct Ignore_Event *event = smalloc(sizeof(struct Ignore_Event));
29
30     event->sequence = sequence;
31     event->added = time(NULL);
32
33     SLIST_INSERT_HEAD(&ignore_events, event, ignore_events);
34 }
35
36 /*
37  * Checks if the given sequence is ignored and returns true if so.
38  *
39  */
40 static bool event_is_ignored(const int sequence) {
41     struct Ignore_Event *event;
42     time_t now = time(NULL);
43     for (event = SLIST_FIRST(&ignore_events); event != SLIST_END(&ignore_events);) {
44         if ((now - event->added) > 5) {
45             struct Ignore_Event *save = event;
46             event = SLIST_NEXT(event, ignore_events);
47             SLIST_REMOVE(&ignore_events, save, Ignore_Event, ignore_events);
48             free(save);
49         } else event = SLIST_NEXT(event, ignore_events);
50     }
51
52     SLIST_FOREACH(event, &ignore_events, ignore_events) {
53         if (event->sequence != sequence)
54             continue;
55
56         /* instead of removing a sequence number we better wait until it gets
57          * garbage collected. it may generate multiple events (there are multiple
58          * enter_notifies for one configure_request, for example). */
59         //SLIST_REMOVE(&ignore_events, event, Ignore_Event, ignore_events);
60         //free(event);
61         return true;
62     }
63
64     return false;
65 }
66
67 /*
68  * Takes an xcb_generic_event_t and calls the appropriate handler, based on the
69  * event type.
70  *
71  */
72 void handle_event(int type, xcb_generic_event_t *event) {
73     /* XXX: remove the NULL and conn parameters as soon as this version of libxcb is required */
74
75     if (randr_base > -1 &&
76         type == randr_base + XCB_RANDR_SCREEN_CHANGE_NOTIFY) {
77         handle_screen_change(NULL, conn, event);
78         return;
79     }
80
81     switch (type) {
82         case XCB_KEY_PRESS:
83             handle_key_press(NULL, conn, (xcb_key_press_event_t*)event);
84             break;
85
86         case XCB_BUTTON_PRESS:
87             handle_button_press(NULL, conn, (xcb_button_press_event_t*)event);
88             break;
89
90         case XCB_MAP_REQUEST:
91             handle_map_request(NULL, conn, (xcb_map_request_event_t*)event);
92             break;
93
94         case XCB_UNMAP_NOTIFY:
95             handle_unmap_notify_event(NULL, conn, (xcb_unmap_notify_event_t*)event);
96             break;
97
98         case XCB_DESTROY_NOTIFY:
99             handle_destroy_notify_event(NULL, conn, (xcb_destroy_notify_event_t*)event);
100             break;
101
102         case XCB_EXPOSE:
103             handle_expose_event(NULL, conn, (xcb_expose_event_t*)event);
104             break;
105
106         case XCB_MOTION_NOTIFY:
107             handle_motion_notify(NULL, conn, (xcb_motion_notify_event_t*)event);
108             break;
109
110         /* Enter window = user moved his mouse over the window */
111         case XCB_ENTER_NOTIFY:
112             handle_enter_notify(NULL, conn, (xcb_enter_notify_event_t*)event);
113             break;
114
115         /* Client message are sent to the root window. The only interesting
116          * client message for us is _NET_WM_STATE, we honour
117          * _NET_WM_STATE_FULLSCREEN */
118         case XCB_CLIENT_MESSAGE:
119             handle_client_message(NULL, conn, (xcb_client_message_event_t*)event);
120             break;
121
122         /* Configure request = window tried to change size on its own */
123         case XCB_CONFIGURE_REQUEST:
124             handle_configure_request(NULL, conn, (xcb_configure_request_event_t*)event);
125             break;
126
127         /* Mapping notify = keyboard mapping changed (Xmodmap), re-grab bindings */
128         case XCB_MAPPING_NOTIFY:
129             handle_mapping_notify(NULL, conn, (xcb_mapping_notify_event_t*)event);
130             break;
131
132         case XCB_FOCUS_IN:
133             handle_focus_in(NULL, conn, (xcb_focus_in_event_t*)event);
134             break;
135
136         case XCB_PROPERTY_NOTIFY:
137             DLOG("Property notify\n");
138             xcb_property_notify_event_t *e = (xcb_property_notify_event_t*)event;
139             property_notify(e->state, e->window, e->atom);
140             break;
141
142         default:
143             DLOG("Unhandled event of type %d\n", type);
144             break;
145     }
146 }
147
148 typedef int (*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);
149
150 struct property_handler_t {
151     xcb_atom_t atom;
152     uint32_t long_len;
153     cb_property_handler_t cb;
154 };
155
156 static struct property_handler_t property_handlers[] = {
157     { 0, 128, handle_windowname_change },
158     { 0, UINT_MAX, handle_hints },
159     { 0, 128, handle_windowname_change_legacy },
160     { 0, UINT_MAX, handle_normal_hints },
161     { 0, UINT_MAX, handle_clientleader_change },
162     { 0, UINT_MAX, handle_transient_for }
163 };
164 #define NUM_HANDLERS (sizeof(property_handlers) / sizeof(struct property_handler_t))
165
166 /*
167  * Sets the appropriate atoms for the property handlers after the atoms were
168  * received from X11
169  *
170  */
171 void property_handlers_init() {
172     property_handlers[0].atom = A__NET_WM_NAME;
173     property_handlers[1].atom = A_WM_HINTS;
174     property_handlers[2].atom = A_WM_NAME;
175     property_handlers[3].atom = A_WM_NORMAL_HINTS;
176     property_handlers[4].atom = A_WM_CLIENT_LEADER;
177     property_handlers[5].atom = A_WM_TRANSIENT_FOR;
178 }
179
180 static int property_notify(uint8_t state, xcb_window_t window, xcb_atom_t atom) {
181     struct property_handler_t *handler = NULL;
182     xcb_get_property_reply_t *propr = NULL;
183     int ret;
184
185     for (int c = 0; c < sizeof(property_handlers) / sizeof(struct property_handler_t); c++) {
186         if (property_handlers[c].atom != atom)
187             continue;
188
189         handler = &property_handlers[c];
190         break;
191     }
192
193     if (handler == NULL) {
194         DLOG("Unhandled property notify for atom %d (0x%08x)\n", atom, atom);
195         return 0;
196     }
197
198     if (state != XCB_PROPERTY_DELETE) {
199         xcb_get_property_cookie_t cookie = xcb_get_property(conn, 0, window, atom, XCB_GET_PROPERTY_TYPE_ANY, 0, handler->long_len);
200         propr = xcb_get_property_reply(conn, cookie, 0);
201     }
202
203     ret = handler->cb(NULL, conn, state, window, atom, propr);
204     FREE(propr);
205     return ret;
206 }
207
208 /*
209  * There was a key press. We compare this key code with our bindings table and pass
210  * the bound action to parse_command().
211  *
212  */
213 int handle_key_press(void *ignored, xcb_connection_t *conn, xcb_key_press_event_t *event) {
214     DLOG("Keypress %d, state raw = %d\n", event->detail, event->state);
215
216     /* Remove the numlock bit, all other bits are modifiers we can bind to */
217     uint16_t state_filtered = event->state & ~(xcb_numlock_mask | XCB_MOD_MASK_LOCK);
218     DLOG("(removed numlock, state = %d)\n", state_filtered);
219     /* Only use the lower 8 bits of the state (modifier masks) so that mouse
220      * button masks are filtered out */
221     state_filtered &= 0xFF;
222     DLOG("(removed upper 8 bits, state = %d)\n", state_filtered);
223
224     if (xkb_current_group == XkbGroup2Index)
225         state_filtered |= BIND_MODE_SWITCH;
226
227     DLOG("(checked mode_switch, state %d)\n", state_filtered);
228
229     /* Find the binding */
230     Binding *bind = get_binding(state_filtered, event->detail);
231
232     /* No match? Then the user has Mode_switch enabled but does not have a
233      * specific keybinding. Fall back to the default keybindings (without
234      * Mode_switch). Makes it much more convenient for users of a hybrid
235      * layout (like us, ru). */
236     if (bind == NULL) {
237         state_filtered &= ~(BIND_MODE_SWITCH);
238         DLOG("no match, new state_filtered = %d\n", state_filtered);
239         if ((bind = get_binding(state_filtered, event->detail)) == NULL) {
240             ELOG("Could not lookup key binding (modifiers %d, keycode %d)\n",
241                  state_filtered, event->detail);
242             return 1;
243         }
244     }
245
246     parse_cmd(bind->command);
247     tree_render();
248     return 1;
249 }
250
251 /*
252  * Called with coordinates of an enter_notify event or motion_notify event
253  * to check if the user crossed virtual screen boundaries and adjust the
254  * current workspace, if so.
255  *
256  */
257 static void check_crossing_screen_boundary(uint32_t x, uint32_t y) {
258     Output *output;
259
260     /* If the user disable focus follows mouse, we have nothing to do here */
261     if (config.disable_focus_follows_mouse)
262         return;
263
264     if ((output = get_output_containing(x, y)) == NULL) {
265         ELOG("ERROR: No such screen\n");
266         return;
267     }
268
269     if (output->con == NULL) {
270         ELOG("ERROR: The screen is not recognized by i3 (no container associated)\n");
271         return;
272     }
273
274     /* Focus the output on which the user moved his cursor */
275     Con *old_focused = focused;
276     con_focus(con_descend_focused(output_get_content(output->con)));
277
278     /* If the focus changed, we re-render to get updated decorations */
279     if (old_focused != focused)
280         tree_render();
281 }
282
283 /*
284  * When the user moves the mouse pointer onto a window, this callback gets called.
285  *
286  */
287 int handle_enter_notify(void *ignored, xcb_connection_t *conn,
288                         xcb_enter_notify_event_t *event) {
289     Con *con;
290
291     DLOG("enter_notify for %08x, mode = %d, detail %d, serial %d\n",
292          event->event, event->mode, event->detail, event->sequence);
293     DLOG("coordinates %d, %d\n", event->event_x, event->event_y);
294     if (event->mode != XCB_NOTIFY_MODE_NORMAL) {
295         DLOG("This was not a normal notify, ignoring\n");
296         return 1;
297     }
298     /* Some events are not interesting, because they were not generated
299      * actively by the user, but by reconfiguration of windows */
300     if (event_is_ignored(event->sequence))
301         return 1;
302
303     bool enter_child = false;
304     /* Get container by frame or by child window */
305     if ((con = con_by_frame_id(event->event)) == NULL) {
306         con = con_by_window_id(event->event);
307         enter_child = true;
308     }
309
310     /* If not, then the user moved his cursor to the root window. In that case, we adjust c_ws */
311     if (con == NULL) {
312         DLOG("Getting screen at %d x %d\n", event->root_x, event->root_y);
313         check_crossing_screen_boundary(event->root_x, event->root_y);
314         return 1;
315     }
316
317     if (con->parent->type == CT_DOCKAREA) {
318         DLOG("Ignoring, this is a dock client\n");
319         return 1;
320     }
321
322     /* see if the user entered the window on a certain window decoration */
323     int layout = (enter_child ? con->parent->layout : con->layout);
324     Con *child;
325     TAILQ_FOREACH(child, &(con->nodes_head), nodes)
326         if (rect_contains(child->deco_rect, event->event_x, event->event_y)) {
327             LOG("using child %p / %s instead!\n", child, child->name);
328             con = child;
329             break;
330         }
331
332     /* for stacked/tabbed layout we do not want to change focus when the user
333      * enters the window at the decoration of any child window. */
334     if (layout == L_STACKED || layout == L_TABBED) {
335         con = TAILQ_FIRST(&(con->parent->focus_head));
336         LOG("using focused %p / %s instead\n", con, con->name);
337     }
338
339 #if 0
340     if (client->workspace != c_ws && client->workspace->output == c_ws->output) {
341             /* This can happen when a client gets assigned to a different workspace than
342              * the current one (see src/mainx.c:reparent_window). Shortly after it was created,
343              * an enter_notify will follow. */
344             DLOG("enter_notify for a client on a different workspace but the same screen, ignoring\n");
345             return 1;
346     }
347 #endif
348
349     if (config.disable_focus_follows_mouse)
350         return 1;
351
352     con_focus(con_descend_focused(con));
353     tree_render();
354
355     return 1;
356 }
357
358 /*
359  * When the user moves the mouse but does not change the active window
360  * (e.g. when having no windows opened but moving mouse on the root screen
361  * and crossing virtual screen boundaries), this callback gets called.
362  *
363  */
364 int handle_motion_notify(void *ignored, xcb_connection_t *conn, xcb_motion_notify_event_t *event) {
365     /* Skip events where the pointer was over a child window, we are only
366      * interested in events on the root window. */
367     if (event->child != 0)
368         return 1;
369
370     Con *con;
371     if ((con = con_by_frame_id(event->event)) == NULL) {
372         check_crossing_screen_boundary(event->root_x, event->root_y);
373         return 1;
374     }
375
376     if (config.disable_focus_follows_mouse)
377         return 1;
378
379     if (con->layout != L_DEFAULT)
380         return 1;
381
382     /* see over which rect the user is */
383     Con *current;
384     TAILQ_FOREACH(current, &(con->nodes_head), nodes) {
385         if (!rect_contains(current->deco_rect, event->event_x, event->event_y))
386             continue;
387
388         /* We found the rect, let’s see if this window is focused */
389         if (TAILQ_FIRST(&(con->focus_head)) == current)
390             return 1;
391
392         con_focus(current);
393         x_push_changes(croot);
394         return 1;
395     }
396
397     return 1;
398 }
399
400 /*
401  * Called when the keyboard mapping changes (for example by using Xmodmap),
402  * we need to update our key bindings then (re-translate symbols).
403  *
404  */
405 int handle_mapping_notify(void *ignored, xcb_connection_t *conn, xcb_mapping_notify_event_t *event) {
406     if (event->request != XCB_MAPPING_KEYBOARD &&
407         event->request != XCB_MAPPING_MODIFIER)
408         return 0;
409
410     DLOG("Received mapping_notify for keyboard or modifier mapping, re-grabbing keys\n");
411     xcb_refresh_keyboard_mapping(keysyms, event);
412
413     xcb_get_numlock_mask(conn);
414
415     ungrab_all_keys(conn);
416     translate_keysyms();
417     grab_all_keys(conn, false);
418
419     return 0;
420 }
421
422 /*
423  * A new window appeared on the screen (=was mapped), so let’s manage it.
424  *
425  */
426 int handle_map_request(void *prophs, xcb_connection_t *conn, xcb_map_request_event_t *event) {
427     xcb_get_window_attributes_cookie_t cookie;
428
429     cookie = xcb_get_window_attributes_unchecked(conn, event->window);
430
431     DLOG("window = 0x%08x, serial is %d.\n", event->window, event->sequence);
432     add_ignore_event(event->sequence);
433
434     manage_window(event->window, cookie, false);
435     x_push_changes(croot);
436     return 1;
437 }
438
439 /*
440  * Configure requests are received when the application wants to resize windows on their own.
441  *
442  * We generate a synthethic configure notify event to signalize the client its "new" position.
443  *
444  */
445 int handle_configure_request(void *prophs, xcb_connection_t *conn, xcb_configure_request_event_t *event) {
446     Con *con;
447
448     DLOG("window 0x%08x wants to be at %dx%d with %dx%d\n",
449         event->window, event->x, event->y, event->width, event->height);
450
451     /* For unmanaged windows, we just execute the configure request. As soon as
452      * it gets mapped, we will take over anyways. */
453     if ((con = con_by_window_id(event->window)) == NULL) {
454         DLOG("Configure request for unmanaged window, can do that.\n");
455
456         uint32_t mask = 0;
457         uint32_t values[7];
458         int c = 0;
459 #define COPY_MASK_MEMBER(mask_member, event_member) do { \
460         if (event->value_mask & mask_member) { \
461             mask |= mask_member; \
462             values[c++] = event->event_member; \
463         } \
464 } while (0)
465
466         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_X, x);
467         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_Y, y);
468         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_WIDTH, width);
469         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_HEIGHT, height);
470         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_BORDER_WIDTH, border_width);
471         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_SIBLING, sibling);
472         COPY_MASK_MEMBER(XCB_CONFIG_WINDOW_STACK_MODE, stack_mode);
473
474         xcb_configure_window(conn, event->window, mask, values);
475         xcb_flush(conn);
476
477         return 1;
478     }
479
480     DLOG("Configure request!\n");
481     if (con_is_floating(con) && con_is_leaf(con)) {
482         /* find the height for the decorations */
483         int deco_height = config.font.height + 5;
484         /* we actually need to apply the size/position changes to the *parent*
485          * container */
486         Rect bsr = con_border_style_rect(con);
487         if (con->border_style == BS_NORMAL) {
488             bsr.y += deco_height;
489             bsr.height -= deco_height;
490         }
491         con = con->parent;
492         DLOG("Container is a floating leaf node, will do that.\n");
493         if (event->value_mask & XCB_CONFIG_WINDOW_X) {
494             con->rect.x = event->x + (-1) * bsr.x;
495             DLOG("proposed x = %d, new x is %d\n", event->x, con->rect.x);
496         }
497         if (event->value_mask & XCB_CONFIG_WINDOW_Y) {
498             con->rect.y = event->y + (-1) * bsr.y;
499             DLOG("proposed y = %d, new y is %d\n", event->y, con->rect.y);
500         }
501         if (event->value_mask & XCB_CONFIG_WINDOW_WIDTH) {
502             con->rect.width = event->width + (-1) * bsr.width;
503             DLOG("proposed width = %d, new width is %d\n", event->width, con->rect.width);
504         }
505         if (event->value_mask & XCB_CONFIG_WINDOW_HEIGHT) {
506             con->rect.height = event->height + (-1) * bsr.height;
507             DLOG("proposed height = %d, new height is %d\n", event->height, con->rect.height);
508         }
509         tree_render();
510     }
511
512     fake_absolute_configure_notify(con);
513
514     return 1;
515 #if 0
516         /* Dock clients can be reconfigured in their height */
517         if (client->dock) {
518                 DLOG("Reconfiguring height of this dock client\n");
519
520                 if (!(event->value_mask & XCB_CONFIG_WINDOW_HEIGHT)) {
521                         DLOG("Ignoring configure request, no height given\n");
522                         return 1;
523                 }
524
525                 client->desired_height = event->height;
526                 render_workspace(conn, c_ws->output, c_ws);
527                 xcb_flush(conn);
528
529                 return 1;
530         }
531
532         if (client->fullscreen) {
533                 DLOG("Client is in fullscreen mode\n");
534
535                 Rect child_rect = client->container->workspace->rect;
536                 child_rect.x = child_rect.y = 0;
537                 fake_configure_notify(conn, child_rect, client->child);
538
539                 return 1;
540         }
541
542         fake_absolute_configure_notify(conn, client);
543
544         return 1;
545 #endif
546 }
547 #if 0
548
549 /*
550  * Configuration notifies are only handled because we need to set up ignore for
551  * the following enter notify events.
552  *
553  */
554 int handle_configure_event(void *prophs, xcb_connection_t *conn, xcb_configure_notify_event_t *event) {
555     DLOG("configure_event, sequence %d\n", event->sequence);
556         /* We ignore this sequence twice because events for child and frame should be ignored */
557         add_ignore_event(event->sequence);
558         add_ignore_event(event->sequence);
559
560         return 1;
561 }
562 #endif
563
564 /*
565  * Gets triggered upon a RandR screen change event, that is when the user
566  * changes the screen configuration in any way (mode, position, …)
567  *
568  */
569 int handle_screen_change(void *prophs, xcb_connection_t *conn,
570                          xcb_generic_event_t *e) {
571     DLOG("RandR screen change\n");
572
573     randr_query_outputs();
574
575     ipc_send_event("output", I3_IPC_EVENT_OUTPUT, "{\"change\":\"unspecified\"}");
576
577     return 1;
578 }
579
580 /*
581  * Our window decorations were unmapped. That means, the window will be killed
582  * now, so we better clean up before.
583  *
584  */
585 int handle_unmap_notify_event(void *data, xcb_connection_t *conn, xcb_unmap_notify_event_t *event) {
586
587     /* FIXME: we cannot ignore this sequence because more UnmapNotifys with the same sequence
588      * numbers but different window IDs may follow */
589     /* we need to ignore EnterNotify events which will be generated because a
590      * different window is visible now */
591     //add_ignore_event(event->sequence);
592
593     DLOG("UnmapNotify for 0x%08x (received from 0x%08x), serial %d\n", event->window, event->event, event->sequence);
594     Con *con = con_by_window_id(event->window);
595     if (con == NULL) {
596         /* This could also be an UnmapNotify for the frame. We need to
597          * decrement the ignore_unmap counter. */
598         con = con_by_frame_id(event->window);
599         if (con == NULL) {
600             LOG("Not a managed window, ignoring UnmapNotify event\n");
601             return 1;
602         }
603         if (con->ignore_unmap > 0)
604             con->ignore_unmap--;
605         DLOG("ignore_unmap = %d for frame of container %p\n", con->ignore_unmap, con);
606         return 1;
607     }
608
609     if (con->ignore_unmap > 0) {
610         DLOG("ignore_unmap = %d, dec\n", con->ignore_unmap);
611         con->ignore_unmap--;
612         return 1;
613     }
614
615     tree_close(con, false, false);
616     tree_render();
617     x_push_changes(croot);
618     return 1;
619
620 #if 0
621         if (client == NULL) {
622                 DLOG("not a managed window. Ignoring.\n");
623
624                 /* This was most likely the destroyed frame of a client which is
625                  * currently being unmapped, so we add this sequence (again!) to
626                  * the ignore list (enter_notify events will get sent for both,
627                  * the child and its frame). */
628                 add_ignore_event(event->sequence);
629
630                 return 0;
631         }
632 #endif
633
634
635 #if 0
636         /* Let’s see how many clients there are left on the workspace to delete it if it’s empty */
637         bool workspace_empty = SLIST_EMPTY(&(client->workspace->focus_stack));
638         bool workspace_focused = (c_ws == client->workspace);
639         Client *to_focus = (!workspace_empty ? SLIST_FIRST(&(client->workspace->focus_stack)) : NULL);
640
641         /* If this workspace is currently visible, we don’t delete it */
642         if (workspace_is_visible(client->workspace))
643                 workspace_empty = false;
644
645         if (workspace_empty) {
646                 client->workspace->output = NULL;
647                 ipc_send_event("workspace", I3_IPC_EVENT_WORKSPACE, "{\"change\":\"empty\"}");
648         }
649
650         /* Remove the urgency flag if set */
651         client->urgent = false;
652         workspace_update_urgent_flag(client->workspace);
653
654         render_layout(conn);
655 #endif
656
657         return 1;
658 }
659
660 /*
661  * A destroy notify event is sent when the window is not unmapped, but
662  * immediately destroyed (for example when starting a window and immediately
663  * killing the program which started it).
664  *
665  * We just pass on the event to the unmap notify handler (by copying the
666  * important fields in the event data structure).
667  *
668  */
669 int handle_destroy_notify_event(void *data, xcb_connection_t *conn, xcb_destroy_notify_event_t *event) {
670     DLOG("destroy notify for 0x%08x, 0x%08x\n", event->event, event->window);
671
672     xcb_unmap_notify_event_t unmap;
673     unmap.sequence = event->sequence;
674     unmap.event = event->event;
675     unmap.window = event->window;
676
677     return handle_unmap_notify_event(NULL, conn, &unmap);
678 }
679
680 /*
681  * Called when a window changes its title
682  *
683  */
684 int handle_windowname_change(void *data, xcb_connection_t *conn, uint8_t state,
685                                 xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
686     Con *con;
687     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
688         return 1;
689
690     window_update_name(con->window, prop);
691
692     x_push_changes(croot);
693
694     return 1;
695 }
696
697 /*
698  * Handles legacy window name updates (WM_NAME), see also src/window.c,
699  * window_update_name_legacy().
700  *
701  */
702 int handle_windowname_change_legacy(void *data, xcb_connection_t *conn, uint8_t state,
703                                 xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
704     Con *con;
705     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
706         return 1;
707
708     window_update_name_legacy(con->window, prop);
709
710     x_push_changes(croot);
711
712     return 1;
713 }
714
715 /*
716  * Updates the client’s WM_CLASS property
717  *
718  */
719 int handle_windowclass_change(void *data, xcb_connection_t *conn, uint8_t state,
720                              xcb_window_t window, xcb_atom_t atom, xcb_get_property_reply_t *prop) {
721     Con *con;
722     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
723         return 1;
724
725     window_update_class(con->window, prop);
726
727     return 0;
728 }
729
730 /*
731  * Expose event means we should redraw our windows (= title bar)
732  *
733  */
734 int handle_expose_event(void *data, xcb_connection_t *conn, xcb_expose_event_t *event) {
735     Con *parent, *con;
736
737     /* event->count is the number of minimum remaining expose events for this
738      * window, so we skip all events but the last one */
739     if (event->count != 0)
740         return 1;
741
742     DLOG("window = %08x\n", event->window);
743
744     if ((parent = con_by_frame_id(event->window)) == NULL) {
745         LOG("expose event for unknown window, ignoring\n");
746         return 1;
747     }
748
749     if (parent->window)
750         x_draw_decoration(parent);
751
752     TAILQ_FOREACH(con, &(parent->nodes_head), nodes) {
753         DLOG("expose for con %p / %s\n", con, con->name);
754         if (con->window)
755             x_draw_decoration(con);
756     }
757
758     /* We also need to render the decorations of other Cons nearby the Con
759      * itself to not get overlapping decorations */
760     TAILQ_FOREACH(con, &(parent->parent->nodes_head), nodes) {
761         DLOG("expose for con %p / %s\n", con, con->name);
762         if (con->window)
763             x_draw_decoration(con);
764     }
765     xcb_flush(conn);
766
767     return 1;
768 }
769
770 /*
771  * Handle client messages (EWMH)
772  *
773  */
774 int handle_client_message(void *data, xcb_connection_t *conn, xcb_client_message_event_t *event) {
775     LOG("ClientMessage for window 0x%08x\n", event->window);
776     if (event->type == A__NET_WM_STATE) {
777         if (event->format != 32 || event->data.data32[1] != A__NET_WM_STATE_FULLSCREEN) {
778             DLOG("atom in clientmessage is %d, fullscreen is %d\n",
779                     event->data.data32[1], A__NET_WM_STATE_FULLSCREEN);
780             DLOG("not about fullscreen atom\n");
781             return 0;
782         }
783
784         Con *con = con_by_window_id(event->window);
785         if (con == NULL) {
786             DLOG("Could not get window for client message\n");
787             return 0;
788         }
789
790         /* Check if the fullscreen state should be toggled */
791         if ((con->fullscreen_mode != CF_NONE &&
792              (event->data.data32[0] == _NET_WM_STATE_REMOVE ||
793               event->data.data32[0] == _NET_WM_STATE_TOGGLE)) ||
794             (con->fullscreen_mode == CF_NONE &&
795              (event->data.data32[0] == _NET_WM_STATE_ADD ||
796               event->data.data32[0] == _NET_WM_STATE_TOGGLE))) {
797             DLOG("toggling fullscreen\n");
798             con_toggle_fullscreen(con);
799         }
800
801         tree_render();
802         x_push_changes(croot);
803     } else {
804         ELOG("unhandled clientmessage\n");
805         return 0;
806     }
807
808     return 1;
809 }
810
811 #if 0
812 int handle_window_type(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
813                         xcb_atom_t atom, xcb_get_property_reply_t *property) {
814         /* TODO: Implement this one. To do this, implement a little test program which sleep(1)s
815          before changing this property. */
816         ELOG("_NET_WM_WINDOW_TYPE changed, this is not yet implemented.\n");
817         return 0;
818 }
819 #endif
820
821 /*
822  * Handles the size hints set by a window, but currently only the part necessary for displaying
823  * clients proportionally inside their frames (mplayer for example)
824  *
825  * See ICCCM 4.1.2.3 for more details
826  *
827  */
828 int handle_normal_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
829                         xcb_atom_t name, xcb_get_property_reply_t *reply) {
830     Con *con = con_by_window_id(window);
831     if (con == NULL) {
832         DLOG("Received WM_NORMAL_HINTS for unknown client\n");
833         return 1;
834     }
835
836     xcb_size_hints_t size_hints;
837
838         //CLIENT_LOG(client);
839
840     /* If the hints were already in this event, use them, if not, request them */
841     if (reply != NULL)
842         xcb_icccm_get_wm_size_hints_from_reply(&size_hints, reply);
843     else
844         xcb_icccm_get_wm_normal_hints_reply(conn, xcb_icccm_get_wm_normal_hints_unchecked(conn, con->window->id), &size_hints, NULL);
845
846     if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE)) {
847         // TODO: Minimum size is not yet implemented
848         DLOG("Minimum size: %d (width) x %d (height)\n", size_hints.min_width, size_hints.min_height);
849     }
850
851     bool changed = false;
852     if ((size_hints.flags & XCB_ICCCM_SIZE_HINT_P_RESIZE_INC)) {
853         if (size_hints.width_inc > 0 && size_hints.width_inc < 0xFFFF)
854             if (con->width_increment != size_hints.width_inc) {
855                 con->width_increment = size_hints.width_inc;
856                 changed = true;
857             }
858         if (size_hints.height_inc > 0 && size_hints.height_inc < 0xFFFF)
859             if (con->height_increment != size_hints.height_inc) {
860                 con->height_increment = size_hints.height_inc;
861                 changed = true;
862             }
863
864         if (changed)
865             DLOG("resize increments changed\n");
866     }
867
868     int base_width = 0, base_height = 0;
869
870     /* base_width/height are the desired size of the window.
871        We check if either the program-specified size or the program-specified
872        min-size is available */
873     if (size_hints.flags & XCB_ICCCM_SIZE_HINT_BASE_SIZE) {
874         base_width = size_hints.base_width;
875         base_height = size_hints.base_height;
876     } else if (size_hints.flags & XCB_ICCCM_SIZE_HINT_P_MIN_SIZE) {
877         /* TODO: is this right? icccm says not */
878         base_width = size_hints.min_width;
879         base_height = size_hints.min_height;
880     }
881
882     if (base_width != con->base_width ||
883         base_height != con->base_height) {
884         con->base_width = base_width;
885         con->base_height = base_height;
886         DLOG("client's base_height changed to %d\n", base_height);
887         DLOG("client's base_width changed to %d\n", base_width);
888     }
889
890     /* If no aspect ratio was set or if it was invalid, we ignore the hints */
891     if (!(size_hints.flags & XCB_ICCCM_SIZE_HINT_P_ASPECT) ||
892         (size_hints.min_aspect_num <= 0) ||
893         (size_hints.min_aspect_den <= 0)) {
894         goto render_and_return;
895     }
896
897     /* XXX: do we really use rect here, not window_rect? */
898     double width = con->rect.width - base_width;
899     double height = con->rect.height - base_height;
900     /* Convert numerator/denominator to a double */
901     double min_aspect = (double)size_hints.min_aspect_num / size_hints.min_aspect_den;
902     double max_aspect = (double)size_hints.max_aspect_num / size_hints.min_aspect_den;
903
904     DLOG("Aspect ratio set: minimum %f, maximum %f\n", min_aspect, max_aspect);
905     DLOG("width = %f, height = %f\n", width, height);
906
907     /* Sanity checks, this is user-input, in a way */
908     if (max_aspect <= 0 || min_aspect <= 0 || height == 0 || (width / height) <= 0)
909         goto render_and_return;
910
911     /* Check if we need to set proportional_* variables using the correct ratio */
912     if ((width / height) < min_aspect) {
913         con->proportional_width = width;
914         con->proportional_height = width / min_aspect;
915     } else if ((width / height) > max_aspect) {
916         con->proportional_width = width;
917         con->proportional_height = width / max_aspect;
918     } else goto render_and_return;
919
920 render_and_return:
921     tree_render();
922     return 1;
923 }
924
925 /*
926  * Handles the WM_HINTS property for extracting the urgency state of the window.
927  *
928  */
929 int handle_hints(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
930                   xcb_atom_t name, xcb_get_property_reply_t *reply) {
931     Con *con = con_by_window_id(window);
932     if (con == NULL) {
933         DLOG("Received WM_HINTS for unknown client\n");
934         return 1;
935     }
936
937     xcb_icccm_wm_hints_t hints;
938
939     if (reply != NULL) {
940         if (!xcb_icccm_get_wm_hints_from_reply(&hints, reply))
941             return 1;
942     } else {
943         if (!xcb_icccm_get_wm_hints_reply(conn, xcb_icccm_get_wm_hints_unchecked(conn, con->window->id), &hints, NULL))
944             return 1;
945     }
946
947     if (!con->urgent && focused == con) {
948         DLOG("Ignoring urgency flag for current client\n");
949         return 1;
950     }
951
952     /* Update the flag on the client directly */
953     con->urgent = (xcb_icccm_wm_hints_get_urgency(&hints) != 0);
954     //CLIENT_LOG(con);
955     LOG("Urgency flag changed to %d\n", con->urgent);
956
957     workspace_update_urgent_flag(con_get_workspace(con));
958
959 #if 0
960     /* If the workspace this client is on is not visible, we need to redraw
961      * the workspace bar */
962     if (!workspace_is_visible(client->workspace)) {
963             Output *output = client->workspace->output;
964             render_workspace(conn, output, output->current_workspace);
965             xcb_flush(conn);
966     }
967 #endif
968
969     return 1;
970 }
971
972 /*
973  * Handles the transient for hints set by a window, signalizing that this window is a popup window
974  * for some other window.
975  *
976  * See ICCCM 4.1.2.6 for more details
977  *
978  */
979 int handle_transient_for(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
980                          xcb_atom_t name, xcb_get_property_reply_t *prop) {
981     Con *con;
982
983     if ((con = con_by_window_id(window)) == NULL || con->window == NULL) {
984         DLOG("No such window\n");
985         return 1;
986     }
987
988     if (prop == NULL) {
989         prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
990                                 false, window, A_WM_TRANSIENT_FOR, A_WINDOW, 0, 32), NULL);
991         if (prop == NULL)
992             return 1;
993     }
994
995     window_update_transient_for(con->window, prop);
996
997     // TODO: put window in floating mode if con->window->transient_for != XCB_NONE:
998 #if 0
999     if (client->floating == FLOATING_AUTO_OFF) {
1000         DLOG("This is a popup window, putting into floating\n");
1001         toggle_floating_mode(conn, client, true);
1002     }
1003 #endif
1004
1005     return 1;
1006 }
1007
1008 /*
1009  * Handles changes of the WM_CLIENT_LEADER atom which specifies if this is a
1010  * toolwindow (or similar) and to which window it belongs (logical parent).
1011  *
1012  */
1013 int handle_clientleader_change(void *data, xcb_connection_t *conn, uint8_t state, xcb_window_t window,
1014                         xcb_atom_t name, xcb_get_property_reply_t *prop) {
1015     Con *con;
1016     if ((con = con_by_window_id(window)) == NULL || con->window == NULL)
1017         return 1;
1018
1019     if (prop == NULL) {
1020         prop = xcb_get_property_reply(conn, xcb_get_property_unchecked(conn,
1021                                 false, window, A_WM_CLIENT_LEADER, A_WINDOW, 0, 32), NULL);
1022         if (prop == NULL)
1023             return 1;
1024     }
1025
1026     window_update_leader(con->window, prop);
1027
1028     return 1;
1029 }
1030
1031 /*
1032  * Handles FocusIn events which are generated by clients (i3’s focus changes
1033  * don’t generate FocusIn events due to a different EventMask) and updates the
1034  * decorations accordingly.
1035  *
1036  */
1037 int handle_focus_in(void *data, xcb_connection_t *conn, xcb_focus_in_event_t *event) {
1038     DLOG("focus change in, for window 0x%08x\n", event->event);
1039     Con *con;
1040     if ((con = con_by_window_id(event->event)) == NULL || con->window == NULL)
1041         return 1;
1042     DLOG("That is con %p / %s\n", con, con->name);
1043
1044     if (event->detail == XCB_NOTIFY_DETAIL_POINTER) {
1045         DLOG("notify detail is pointer, ignoring this event\n");
1046         return 1;
1047     }
1048
1049     if (focused_id == event->event) {
1050         DLOG("focus matches the currently focused window, not doing anything\n");
1051         return 1;
1052     }
1053
1054     DLOG("focus is different, updating decorations\n");
1055     con_focus(con);
1056     /* We update focused_id because we don’t need to set focus again */
1057     focused_id = event->event;
1058     x_push_changes(croot);
1059     return 1;
1060 }