comment why NotifyAncestor is not ignored
[dana/openbox.git] / openbox / event.c
1 #include "openbox.h"
2 #include "client.h"
3 #include "config.h"
4 #include "xerror.h"
5 #include "prop.h"
6 #include "screen.h"
7 #include "frame.h"
8 #include "engine.h"
9 #include "focus.h"
10 #include "stacking.h"
11 #include "extensions.h"
12 #include "timer.h"
13 #include "engine.h"
14 #include "dispatch.h"
15
16 #include <X11/Xlib.h>
17 #include <X11/keysym.h>
18 #include <X11/Xatom.h>
19 #ifdef HAVE_SYS_SELECT_H
20 #  include <sys/select.h>
21 #endif
22
23 static void event_process(XEvent *e);
24 static void event_handle_root(XEvent *e);
25 static void event_handle_client(Client *c, XEvent *e);
26
27 Time event_lasttime = 0;
28
29 /*! The value of the mask for the NumLock modifier */
30 unsigned int NumLockMask;
31 /*! The value of the mask for the ScrollLock modifier */
32 unsigned int ScrollLockMask;
33 /*! The key codes for the modifier keys */
34 static XModifierKeymap *modmap;
35 /*! Table of the constant modifier masks */
36 static const int mask_table[] = {
37     ShiftMask, LockMask, ControlMask, Mod1Mask,
38     Mod2Mask, Mod3Mask, Mod4Mask, Mod5Mask
39 };
40 static int mask_table_size;
41
42 void event_startup()
43 {
44     mask_table_size = sizeof(mask_table) / sizeof(mask_table[0]);
45      
46     /* get lock masks that are defined by the display (not constant) */
47     modmap = XGetModifierMapping(ob_display);
48     g_assert(modmap);
49     if (modmap && modmap->max_keypermod > 0) {
50         size_t cnt;
51         const size_t size = mask_table_size * modmap->max_keypermod;
52         /* get the values of the keyboard lock modifiers
53            Note: Caps lock is not retrieved the same way as Scroll and Num
54            lock since it doesn't need to be. */
55         const KeyCode num_lock = XKeysymToKeycode(ob_display, XK_Num_Lock);
56         const KeyCode scroll_lock = XKeysymToKeycode(ob_display,
57                                                      XK_Scroll_Lock);
58           
59         for (cnt = 0; cnt < size; ++cnt) {
60             if (! modmap->modifiermap[cnt]) continue;
61                
62             if (num_lock == modmap->modifiermap[cnt])
63                 NumLockMask = mask_table[cnt / modmap->max_keypermod];
64             if (scroll_lock == modmap->modifiermap[cnt])
65                 ScrollLockMask = mask_table[cnt / modmap->max_keypermod];
66         }
67     }
68 }
69
70 void event_shutdown()
71 {
72     XFreeModifiermap(modmap);
73 }
74
75 void event_loop()
76 {
77     fd_set selset;
78     XEvent e;
79     int x_fd;
80     struct timeval *wait;
81
82     while (TRUE) {
83         /*
84           There are slightly different event retrieval semantics here for
85           local (or high bandwidth) versus remote (or low bandwidth)
86           connections to the display/Xserver.
87         */
88         if (ob_remote) {
89             if (!XPending(ob_display))
90                 break;
91         } else {
92             /*
93               This XSync allows for far more compression of events, which
94               makes things like Motion events perform far far better. Since
95               it also means network traffic for every event instead of every
96               X events (where X is the number retrieved at a time), it
97               probably should not be used for setups where Openbox is
98               running on a remote/low bandwidth display/Xserver.
99             */
100             XSync(ob_display, FALSE);
101             if (!XEventsQueued(ob_display, QueuedAlready))
102                 break;
103         }
104         XNextEvent(ob_display, &e);
105
106         event_process(&e);
107     }
108      
109     timer_dispatch((GTimeVal**)&wait);
110     x_fd = ConnectionNumber(ob_display);
111     FD_ZERO(&selset);
112     FD_SET(x_fd, &selset);
113     select(x_fd + 1, &selset, NULL, NULL, wait);
114 }
115
116 void event_process(XEvent *e)
117 {
118     XEvent ce;
119     KeyCode *kp;
120     Window window;
121     int i, k;
122     Client *client;
123
124     /* pick a window */
125     switch (e->type) {
126     case MapRequest:
127         window = e->xmap.window;
128         break;
129     case UnmapNotify:
130         window = e->xunmap.window;
131         break;
132     case DestroyNotify:
133         window = e->xdestroywindow.window;
134         break;
135     case ConfigureRequest:
136         window = e->xconfigurerequest.window;
137         break;
138     default:
139 #ifdef XKB
140         if (extensions_xkb && e->type == extensions_xkb_event_basep) {
141             switch (((XkbAnyEvent*)&e)->xkb_type) {
142             case XkbBellNotify:
143                 window = ((XkbBellNotifyEvent*)&e)->window;
144             default:
145                 window = None;
146             }
147         } else
148 #endif
149             window = e->xany.window;
150     }
151      
152     /* grab the lasttime and hack up the state */
153     switch (e->type) {
154     case ButtonPress:
155     case ButtonRelease:
156         event_lasttime = e->xbutton.time;
157         e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask);
158         /* kill off the Button1Mask etc, only want the modifiers */
159         e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask |
160                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
161         break;
162     case KeyPress:
163         event_lasttime = e->xkey.time;
164         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
165         /* kill off the Button1Mask etc, only want the modifiers */
166         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
167                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
168         /* add to the state the mask of the modifier being pressed, if it is
169            a modifier key being pressed (this is a little ugly..) */
170 /* I'm commenting this out cuz i don't want "C-Control_L" being returned. */
171 /*      kp = modmap->modifiermap;*/
172 /*      for (i = 0; i < mask_table_size; ++i) {*/
173 /*          for (k = 0; k < modmap->max_keypermod; ++k) {*/
174 /*              if (*kp == e->xkey.keycode) {*/ /* found the keycode */
175                     /* add the mask for it */
176 /*                  e->xkey.state |= mask_table[i];*/
177                     /* cause the first loop to break; */
178 /*                  i = mask_table_size;*/
179 /*                  break;*/ /* get outta here! */
180 /*              }*/
181 /*              ++kp;*/
182 /*          }*/
183 /*      }*/
184
185         break;
186     case KeyRelease:
187         event_lasttime = e->xkey.time;
188         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
189         /* kill off the Button1Mask etc, only want the modifiers */
190         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
191                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
192         /* remove from the state the mask of the modifier being released, if
193            it is a modifier key being released (this is a little ugly..) */
194         kp = modmap->modifiermap;
195         for (i = 0; i < mask_table_size; ++i) {
196             for (k = 0; k < modmap->max_keypermod; ++k) {
197                 if (*kp == e->xkey.keycode) { /* found the keycode */
198                     /* remove the mask for it */
199                     e->xkey.state &= ~mask_table[i];
200                     /* cause the first loop to break; */
201                     i = mask_table_size;
202                     break; /* get outta here! */
203                 }
204                 ++kp;
205             }
206         }
207         break;
208     case MotionNotify:
209         event_lasttime = e->xmotion.time;
210         e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask);
211         /* kill off the Button1Mask etc, only want the modifiers */
212         e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask |
213                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
214         /* compress events */
215         while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) {
216             e->xmotion.x_root = ce.xmotion.x_root;
217             e->xmotion.y_root = ce.xmotion.y_root;
218         }
219         break;
220     case PropertyNotify:
221         event_lasttime = e->xproperty.time;
222         break;
223     case FocusIn:
224         g_message("FocusIn on %lx mode %d detail %d", window,
225                   e->xfocus.mode, e->xfocus.detail);
226         /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
227            because of RevertToPointerRoot. If the focus ends up reverting to
228            pointer root on a workspace change, then the FocusIn event that we
229            want will be of type NotifyAncestor. This situation does not occur
230            for FocusOut, so it is safely ignored there.
231         */
232         if (e->xfocus.detail == NotifyInferior ||
233             e->xfocus.detail > NotifyNonlinearVirtual) return;
234             g_message("FocusIn on %lx", window);
235         break;
236     case FocusOut:
237         g_message("FocusOut on %lx mode %d detail %d", window,
238                   e->xfocus.mode, e->xfocus.detail);
239         if (e->xfocus.mode == NotifyGrab ||
240             e->xfocus.detail == NotifyInferior ||
241             e->xfocus.detail == NotifyAncestor ||
242             e->xfocus.detail > NotifyNonlinearVirtual) return;
243
244         g_message("FocusOut on %lx", window);
245         /* FocusOut events just make us look for FocusIn events. They
246            are mostly ignored otherwise. */
247         {
248             XEvent fi;
249             if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
250                 event_process(&fi);
251
252                 /* secret magic way of event_process telling us that no client
253                    was found for the FocusIn event.
254                    
255                    it should be noted!! that focus events of invalud types
256                    (the ones that cause a return in the FocusIn case above)
257                    will not cause this focus_fallback to be called. it will
258                    be assumed that focus is going someplace sane still, or
259                    there are more focus events coming to fix up the situation.
260                    this may not be perfect.. but its working! and focus events
261                    are too much headache to take that for granted. ktnx. ^_^
262                 */
263                 if (fi.xfocus.window == None)
264                     focus_fallback(FALSE);
265                 if (fi.xfocus.window == e->xfocus.window)
266                     return;
267             } else
268                 focus_fallback(FALSE);
269         }
270         break;
271     case EnterNotify:
272     case LeaveNotify:
273         event_lasttime = e->xcrossing.time;
274         /* NotifyUngrab occurs when a mouse button is released and the event is
275            caused, like when lowering a window */
276         if (e->xcrossing.mode == NotifyGrab) return;
277         break;
278     default:
279         event_lasttime = CurrentTime;
280         break;
281     }
282
283     client = g_hash_table_lookup(client_map, &window);
284
285     /* deal with it in the kernel */
286     if (client)
287         event_handle_client(client, e);
288     else if (window == ob_root)
289         event_handle_root(e);
290     else if (e->type == MapRequest)
291         client_manage(window);
292     else if (e->type == FocusIn)
293         e->xfocus.window = None; /* says a client was found for the event! */
294     else if (e->type == ConfigureRequest) {
295         /* unhandled configure requests must be used to configure the
296            window directly */
297         XWindowChanges xwc;
298                
299         xwc.x = e->xconfigurerequest.x;
300         xwc.y = e->xconfigurerequest.y;
301         xwc.width = e->xconfigurerequest.width;
302         xwc.height = e->xconfigurerequest.height;
303         xwc.border_width = e->xconfigurerequest.border_width;
304         xwc.sibling = e->xconfigurerequest.above;
305         xwc.stack_mode = e->xconfigurerequest.detail;
306        
307         /* we are not to be held responsible if someone sends us an
308            invalid request! */
309         xerror_set_ignore(TRUE);
310         XConfigureWindow(ob_display, window,
311                          e->xconfigurerequest.value_mask, &xwc);
312         xerror_set_ignore(FALSE);
313     }
314
315     /* dispatch the event to registered handlers */
316     dispatch_x(e, client);
317 }
318
319 static void event_handle_root(XEvent *e)
320 {
321     Atom msgtype;
322      
323     switch(e->type) {
324     case ClientMessage:
325         if (e->xclient.format != 32) break;
326
327         msgtype = e->xclient.message_type;
328         if (msgtype == prop_atoms.net_current_desktop) {
329             unsigned int d = e->xclient.data.l[0];
330             if (d < screen_num_desktops)
331                 screen_set_desktop(d);
332         } else if (msgtype == prop_atoms.net_number_of_desktops) {
333             unsigned int d = e->xclient.data.l[0];
334             if (d > 0)
335                 screen_set_num_desktops(d);
336         } else if (msgtype == prop_atoms.net_showing_desktop) {
337             screen_show_desktop(e->xclient.data.l[0] != 0);
338         }
339         break;
340     case PropertyNotify:
341         if (e->xproperty.atom == prop_atoms.net_desktop_names)
342             screen_update_desktop_names();
343         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
344             screen_update_layout();
345         break;
346     }
347 }
348
349 static void event_handle_client(Client *client, XEvent *e)
350 {
351     XEvent ce;
352     Atom msgtype;
353     int i=0;
354     ConfigValue focus_follow;
355      
356     switch (e->type) {
357     case FocusIn:
358         focus_set_client(client);
359     case FocusOut:
360         g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
361                   client->window);
362         /* focus state can affect the stacking layer */
363         client_calc_layer(client);
364         engine_frame_adjust_focus(client->frame);
365         break;
366     case EnterNotify:
367         if (client_normal(client)) {
368             if (ob_state == State_Starting) {
369                 /* move it to the top of the focus order */
370                 guint desktop = client->desktop;
371                 if (desktop == DESKTOP_ALL) desktop = screen_desktop;
372                 focus_order[desktop] = g_list_remove(focus_order[desktop],
373                                                      client);
374                 focus_order[desktop] = g_list_prepend(focus_order[desktop],
375                                                       client);
376             } else {
377                 if (!config_get("focusFollowsMouse",Config_Bool,&focus_follow))
378                     g_assert_not_reached();
379                 if (focus_follow.bool)
380                     client_focus(client);
381             }
382         }
383         break;
384     case ConfigureRequest:
385         /* compress these */
386         while (XCheckTypedWindowEvent(ob_display, client->window,
387                                       ConfigureRequest, &ce)) {
388             ++i;
389             /* XXX if this causes bad things.. we can compress config req's
390                with the same mask. */
391             e->xconfigurerequest.value_mask |=
392                 ce.xconfigurerequest.value_mask;
393             if (ce.xconfigurerequest.value_mask & CWX)
394                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
395             if (ce.xconfigurerequest.value_mask & CWY)
396                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
397             if (ce.xconfigurerequest.value_mask & CWWidth)
398                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
399             if (ce.xconfigurerequest.value_mask & CWHeight)
400                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
401             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
402                 e->xconfigurerequest.border_width =
403                     ce.xconfigurerequest.border_width;
404             if (ce.xconfigurerequest.value_mask & CWStackMode)
405                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
406         }
407         if (i) g_message("Compressed %d Configures", i);
408
409         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
410         if (client->iconic || client->shaded) return;
411
412         if (e->xconfigurerequest.value_mask & CWBorderWidth)
413             client->border_width = e->xconfigurerequest.border_width;
414
415         /* resize, then move, as specified in the EWMH section 7.7 */
416         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
417                                                CWX | CWY)) {
418             int x, y, w, h;
419             Corner corner;
420                
421             x = (e->xconfigurerequest.value_mask & CWX) ?
422                 e->xconfigurerequest.x : client->area.x;
423             y = (e->xconfigurerequest.value_mask & CWY) ?
424                 e->xconfigurerequest.y : client->area.y;
425             w = (e->xconfigurerequest.value_mask & CWWidth) ?
426                 e->xconfigurerequest.width : client->area.width;
427             h = (e->xconfigurerequest.value_mask & CWHeight) ?
428                 e->xconfigurerequest.height : client->area.height;
429                
430             switch (client->gravity) {
431             case NorthEastGravity:
432             case EastGravity:
433                 corner = Corner_TopRight;
434                 break;
435             case SouthWestGravity:
436             case SouthGravity:
437                 corner = Corner_BottomLeft;
438                 break;
439             case SouthEastGravity:
440                 corner = Corner_BottomRight;
441                 break;
442             default:     /* NorthWest, Static, etc */
443                 corner = Corner_TopLeft;
444             }
445
446             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
447         }
448
449         if (e->xconfigurerequest.value_mask & CWStackMode) {
450             switch (e->xconfigurerequest.detail) {
451             case Below:
452             case BottomIf:
453                 stacking_lower(client);
454                 break;
455
456             case Above:
457             case TopIf:
458             default:
459                 stacking_raise(client);
460                 break;
461             }
462         }
463         break;
464     case UnmapNotify:
465         if (client->ignore_unmaps) {
466             client->ignore_unmaps--;
467             break;
468         }
469         client_unmanage(client);
470         break;
471     case DestroyNotify:
472         client_unmanage(client);
473         break;
474     case ReparentNotify:
475         /* this is when the client is first taken captive in the frame */
476         if (e->xreparent.parent == client->frame->plate) break;
477
478         /*
479           This event is quite rare and is usually handled in unmapHandler.
480           However, if the window is unmapped when the reparent event occurs,
481           the window manager never sees it because an unmap event is not sent
482           to an already unmapped window.
483         */
484
485         /* we don't want the reparent event, put it back on the stack for the
486            X server to deal with after we unmanage the window */
487         XPutBackEvent(ob_display, e);
488      
489         client_unmanage(client);
490         break;
491     case MapRequest:
492         if (!client->iconic) break; /* this normally doesn't happen, but if it
493                                        does, we don't want it! */
494         if (screen_showing_desktop)
495             screen_show_desktop(FALSE);
496         client_iconify(client, FALSE, TRUE);
497         if (!client->frame->visible)
498             /* if its not visible still, then don't mess with it */
499             break;
500         if (client->shaded)
501             client_shade(client, FALSE);
502         client_focus(client);
503         stacking_raise(client);
504         break;
505     case ClientMessage:
506         /* validate cuz we query stuff off the client here */
507         if (!client_validate(client)) break;
508   
509         if (e->xclient.format != 32) return;
510
511         msgtype = e->xclient.message_type;
512         if (msgtype == prop_atoms.wm_change_state) {
513             /* compress changes into a single change */
514             while (XCheckTypedWindowEvent(ob_display, e->type,
515                                           client->window, &ce)) {
516                 /* XXX: it would be nice to compress ALL messages of a
517                    type, not just messages in a row without other
518                    message types between. */
519                 if (ce.xclient.message_type != msgtype) {
520                     XPutBackEvent(ob_display, &ce);
521                     break;
522                 }
523                 e->xclient = ce.xclient;
524             }
525             client_set_wm_state(client, e->xclient.data.l[0]);
526         } else if (msgtype == prop_atoms.net_wm_desktop) {
527             /* compress changes into a single change */
528             while (XCheckTypedWindowEvent(ob_display, e->type,
529                                           client->window, &ce)) {
530                 /* XXX: it would be nice to compress ALL messages of a
531                    type, not just messages in a row without other
532                    message types between. */
533                 if (ce.xclient.message_type != msgtype) {
534                     XPutBackEvent(ob_display, &ce);
535                     break;
536                 }
537                 e->xclient = ce.xclient;
538             }
539             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
540                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
541                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
542                                    FALSE);
543         } else if (msgtype == prop_atoms.net_wm_state) {
544             /* can't compress these */
545             g_message("net_wm_state %s %ld %ld for 0x%lx",
546                       (e->xclient.data.l[0] == 0 ? "Remove" :
547                        e->xclient.data.l[0] == 1 ? "Add" :
548                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
549                       e->xclient.data.l[1], e->xclient.data.l[2],
550                       client->window);
551             client_set_state(client, e->xclient.data.l[0],
552                              e->xclient.data.l[1], e->xclient.data.l[2]);
553         } else if (msgtype == prop_atoms.net_close_window) {
554             g_message("net_close_window for 0x%lx", client->window);
555             client_close(client);
556         } else if (msgtype == prop_atoms.net_active_window) {
557             g_message("net_active_window for 0x%lx", client->window);
558             if (screen_showing_desktop)
559                 screen_show_desktop(FALSE);
560             if (client->iconic)
561                 client_iconify(client, FALSE, TRUE);
562             else if (!client->frame->visible)
563                 /* if its not visible for other reasons, then don't mess
564                    with it */
565                 break;
566             if (client->shaded)
567                 client_shade(client, FALSE);
568             client_focus(client);
569             stacking_raise(client);
570         }
571         break;
572     case PropertyNotify:
573         /* validate cuz we query stuff off the client here */
574         if (!client_validate(client)) break;
575   
576         /* compress changes to a single property into a single change */
577         while (XCheckTypedWindowEvent(ob_display, e->type,
578                                       client->window, &ce)) {
579             /* XXX: it would be nice to compress ALL changes to a property,
580                not just changes in a row without other props between. */
581             if (ce.xproperty.atom != e->xproperty.atom) {
582                 XPutBackEvent(ob_display, &ce);
583                 break;
584             }
585         }
586
587         msgtype = e->xproperty.atom;
588         if (msgtype == XA_WM_NORMAL_HINTS) {
589             client_update_normal_hints(client);
590             /* normal hints can make a window non-resizable */
591             client_setup_decor_and_functions(client);
592         }
593         else if (msgtype == XA_WM_HINTS)
594             client_update_wmhints(client);
595         else if (msgtype == XA_WM_TRANSIENT_FOR) {
596             client_update_transient_for(client);
597             client_get_type(client);
598             /* type may have changed, so update the layer */
599             client_calc_layer(client);
600             client_setup_decor_and_functions(client);
601         }
602         else if (msgtype == prop_atoms.net_wm_name ||
603                  msgtype == prop_atoms.wm_name)
604             client_update_title(client);
605         else if (msgtype == prop_atoms.net_wm_icon_name ||
606                  msgtype == prop_atoms.wm_icon_name)
607             client_update_icon_title(client);
608         else if (msgtype == prop_atoms.wm_class)
609             client_update_class(client);
610         else if (msgtype == prop_atoms.wm_protocols) {
611             client_update_protocols(client);
612             client_setup_decor_and_functions(client);
613         }
614         else if (msgtype == prop_atoms.net_wm_strut)
615             client_update_strut(client);
616         else if (msgtype == prop_atoms.net_wm_icon)
617             client_update_icons(client);
618         else if (msgtype == prop_atoms.kwm_win_icon)
619             client_update_kwm_icon(client);
620     default:
621         ;
622 #ifdef SHAPE
623         if (extensions_shape && e->type == extensions_shape_event_basep) {
624             client->shaped = ((XShapeEvent*)e)->shaped;
625             engine_frame_adjust_shape(client->frame);
626         }
627 #endif
628     }
629 }