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