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