more focus cleanups. ignored focusin's were not working right recursively.
[mikachu/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         if (e->xfocus.detail == NotifyInferior ||
227             e->xfocus.detail == NotifyAncestor ||
228             e->xfocus.detail > NotifyNonlinearVirtual) return;
229             g_message("FocusIn on %lx", window);
230         break;
231     case FocusOut:
232         g_message("FocusOut on %lx mode %d detail %d", window,
233                   e->xfocus.mode, e->xfocus.detail);
234         if (e->xfocus.detail == NotifyInferior ||
235             e->xfocus.detail == NotifyAncestor ||
236             e->xfocus.detail > NotifyNonlinearVirtual) return;
237
238         g_message("FocusOut on %lx", window);
239         /* FocusOut events just make us look for FocusIn events. They
240            are mostly ignored otherwise. */
241         {
242             XEvent fi;
243             if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
244                 event_process(&fi);
245
246                 /* secret magic way of event_process telling us that no client
247                    was found for the FocusIn event */
248                 if (fi.xfocus.window != None)
249                     focus_fallback(FALSE);
250                 if (fi.xfocus.window == e->xfocus.window)
251                     return;
252             } else
253                 focus_fallback(FALSE);
254         }
255         break;
256     case EnterNotify:
257     case LeaveNotify:
258         event_lasttime = e->xcrossing.time;
259         /* NotifyUngrab occurs when a mouse button is released and the event is
260            caused, like when lowering a window */
261         if (e->xcrossing.mode == NotifyGrab) return;
262         break;
263     default:
264         event_lasttime = CurrentTime;
265         break;
266     }
267
268     client = g_hash_table_lookup(client_map, &window);
269
270     /* deal with it in the kernel */
271     if (client)
272         event_handle_client(client, e);
273     else if (window == ob_root)
274         event_handle_root(e);
275     else if (e->type == MapRequest)
276         client_manage(window);
277     else if (e->type == ConfigureRequest) {
278         /* unhandled configure requests must be used to configure the
279            window directly */
280         XWindowChanges xwc;
281                
282         xwc.x = e->xconfigurerequest.x;
283         xwc.y = e->xconfigurerequest.y;
284         xwc.width = e->xconfigurerequest.width;
285         xwc.height = e->xconfigurerequest.height;
286         xwc.border_width = e->xconfigurerequest.border_width;
287         xwc.sibling = e->xconfigurerequest.above;
288         xwc.stack_mode = e->xconfigurerequest.detail;
289        
290         /* we are not to be held responsible if someone sends us an
291            invalid request! */
292         xerror_set_ignore(TRUE);
293         XConfigureWindow(ob_display, window,
294                          e->xconfigurerequest.value_mask, &xwc);
295         xerror_set_ignore(FALSE);
296     }
297
298     /* dispatch the event to registered handlers */
299     dispatch_x(e, client);
300 }
301
302 static void event_handle_root(XEvent *e)
303 {
304     Atom msgtype;
305      
306     switch(e->type) {
307     case ClientMessage:
308         if (e->xclient.format != 32) break;
309
310         msgtype = e->xclient.message_type;
311         if (msgtype == prop_atoms.net_current_desktop) {
312             unsigned int d = e->xclient.data.l[0];
313             if (d < screen_num_desktops)
314                 screen_set_desktop(d);
315         } else if (msgtype == prop_atoms.net_number_of_desktops) {
316             unsigned int d = e->xclient.data.l[0];
317             if (d > 0)
318                 screen_set_num_desktops(d);
319         } else if (msgtype == prop_atoms.net_showing_desktop) {
320             screen_show_desktop(e->xclient.data.l[0] != 0);
321         }
322         break;
323     case PropertyNotify:
324         if (e->xproperty.atom == prop_atoms.net_desktop_names)
325             screen_update_desktop_names();
326         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
327             screen_update_layout();
328         break;
329     }
330 }
331
332 static void event_handle_client(Client *client, XEvent *e)
333 {
334     XEvent ce;
335     Atom msgtype;
336     int i=0;
337     ConfigValue focus_follow;
338      
339     switch (e->type) {
340     case FocusIn:
341         focus_set_client(client);
342     case FocusOut:
343         g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
344                   client->window);
345         /* focus state can affect the stacking layer */
346         client_calc_layer(client);
347         engine_frame_adjust_focus(client->frame);
348
349         e->xfocus.window = None; /* says a client was found for the event! */
350         break;
351     case EnterNotify:
352         if (client_normal(client)) {
353             if (ob_state == State_Starting) {
354                 /* move it to the top of the focus order */
355                 guint desktop = client->desktop;
356                 if (desktop == DESKTOP_ALL) desktop = screen_desktop;
357                 focus_order[desktop] = g_list_remove(focus_order[desktop],
358                                                      client);
359                 focus_order[desktop] = g_list_prepend(focus_order[desktop],
360                                                       client);
361             } else {
362                 if (!config_get("focusFollowsMouse",Config_Bool,&focus_follow))
363                     g_assert_not_reached();
364                 if (focus_follow.bool)
365                     client_focus(client);
366             }
367         }
368         break;
369     case ConfigureRequest:
370         /* compress these */
371         while (XCheckTypedWindowEvent(ob_display, client->window,
372                                       ConfigureRequest, &ce)) {
373             ++i;
374             /* XXX if this causes bad things.. we can compress config req's
375                with the same mask. */
376             e->xconfigurerequest.value_mask |=
377                 ce.xconfigurerequest.value_mask;
378             if (ce.xconfigurerequest.value_mask & CWX)
379                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
380             if (ce.xconfigurerequest.value_mask & CWY)
381                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
382             if (ce.xconfigurerequest.value_mask & CWWidth)
383                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
384             if (ce.xconfigurerequest.value_mask & CWHeight)
385                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
386             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
387                 e->xconfigurerequest.border_width =
388                     ce.xconfigurerequest.border_width;
389             if (ce.xconfigurerequest.value_mask & CWStackMode)
390                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
391         }
392         if (i) g_message("Compressed %d Configures", i);
393
394         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
395         if (client->iconic || client->shaded) return;
396
397         if (e->xconfigurerequest.value_mask & CWBorderWidth)
398             client->border_width = e->xconfigurerequest.border_width;
399
400         /* resize, then move, as specified in the EWMH section 7.7 */
401         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
402                                                CWX | CWY)) {
403             int x, y, w, h;
404             Corner corner;
405                
406             x = (e->xconfigurerequest.value_mask & CWX) ?
407                 e->xconfigurerequest.x : client->area.x;
408             y = (e->xconfigurerequest.value_mask & CWY) ?
409                 e->xconfigurerequest.y : client->area.y;
410             w = (e->xconfigurerequest.value_mask & CWWidth) ?
411                 e->xconfigurerequest.width : client->area.width;
412             h = (e->xconfigurerequest.value_mask & CWHeight) ?
413                 e->xconfigurerequest.height : client->area.height;
414                
415             switch (client->gravity) {
416             case NorthEastGravity:
417             case EastGravity:
418                 corner = Corner_TopRight;
419                 break;
420             case SouthWestGravity:
421             case SouthGravity:
422                 corner = Corner_BottomLeft;
423                 break;
424             case SouthEastGravity:
425                 corner = Corner_BottomRight;
426                 break;
427             default:     /* NorthWest, Static, etc */
428                 corner = Corner_TopLeft;
429             }
430
431             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
432         }
433
434         if (e->xconfigurerequest.value_mask & CWStackMode) {
435             switch (e->xconfigurerequest.detail) {
436             case Below:
437             case BottomIf:
438                 stacking_lower(client);
439                 break;
440
441             case Above:
442             case TopIf:
443             default:
444                 stacking_raise(client);
445                 break;
446             }
447         }
448         break;
449     case UnmapNotify:
450         if (client->ignore_unmaps) {
451             client->ignore_unmaps--;
452             break;
453         }
454         client_unmanage(client);
455         break;
456     case DestroyNotify:
457         client_unmanage(client);
458         break;
459     case ReparentNotify:
460         /* this is when the client is first taken captive in the frame */
461         if (e->xreparent.parent == client->frame->plate) break;
462
463         /*
464           This event is quite rare and is usually handled in unmapHandler.
465           However, if the window is unmapped when the reparent event occurs,
466           the window manager never sees it because an unmap event is not sent
467           to an already unmapped window.
468         */
469
470         /* we don't want the reparent event, put it back on the stack for the
471            X server to deal with after we unmanage the window */
472         XPutBackEvent(ob_display, e);
473      
474         client_unmanage(client);
475         break;
476     case MapRequest:
477         if (!client->iconic) break; /* this normally doesn't happen, but if it
478                                        does, we don't want it! */
479         if (screen_showing_desktop)
480             screen_show_desktop(FALSE);
481         client_iconify(client, FALSE, TRUE);
482         if (!client->frame->visible)
483             /* if its not visible still, then don't mess with it */
484             break;
485         if (client->shaded)
486             client_shade(client, FALSE);
487         client_focus(client);
488         stacking_raise(client);
489         break;
490     case ClientMessage:
491         /* validate cuz we query stuff off the client here */
492         if (!client_validate(client)) break;
493   
494         if (e->xclient.format != 32) return;
495
496         msgtype = e->xclient.message_type;
497         if (msgtype == prop_atoms.wm_change_state) {
498             /* compress changes into a single change */
499             while (XCheckTypedWindowEvent(ob_display, e->type,
500                                           client->window, &ce)) {
501                 /* XXX: it would be nice to compress ALL messages of a
502                    type, not just messages in a row without other
503                    message types between. */
504                 if (ce.xclient.message_type != msgtype) {
505                     XPutBackEvent(ob_display, &ce);
506                     break;
507                 }
508                 e->xclient = ce.xclient;
509             }
510             client_set_wm_state(client, e->xclient.data.l[0]);
511         } else if (msgtype == prop_atoms.net_wm_desktop) {
512             /* compress changes into a single change */
513             while (XCheckTypedWindowEvent(ob_display, e->type,
514                                           client->window, &ce)) {
515                 /* XXX: it would be nice to compress ALL messages of a
516                    type, not just messages in a row without other
517                    message types between. */
518                 if (ce.xclient.message_type != msgtype) {
519                     XPutBackEvent(ob_display, &ce);
520                     break;
521                 }
522                 e->xclient = ce.xclient;
523             }
524             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
525                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
526                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
527                                    FALSE);
528         } else if (msgtype == prop_atoms.net_wm_state) {
529             /* can't compress these */
530             g_message("net_wm_state %s %ld %ld for 0x%lx",
531                       (e->xclient.data.l[0] == 0 ? "Remove" :
532                        e->xclient.data.l[0] == 1 ? "Add" :
533                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
534                       e->xclient.data.l[1], e->xclient.data.l[2],
535                       client->window);
536             client_set_state(client, e->xclient.data.l[0],
537                              e->xclient.data.l[1], e->xclient.data.l[2]);
538         } else if (msgtype == prop_atoms.net_close_window) {
539             g_message("net_close_window for 0x%lx", client->window);
540             client_close(client);
541         } else if (msgtype == prop_atoms.net_active_window) {
542             g_message("net_active_window for 0x%lx", client->window);
543             if (screen_showing_desktop)
544                 screen_show_desktop(FALSE);
545             if (client->iconic)
546                 client_iconify(client, FALSE, TRUE);
547             else if (!client->frame->visible)
548                 /* if its not visible for other reasons, then don't mess
549                    with it */
550                 break;
551             if (client->shaded)
552                 client_shade(client, FALSE);
553             client_focus(client);
554             stacking_raise(client);
555         }
556         break;
557     case PropertyNotify:
558         /* validate cuz we query stuff off the client here */
559         if (!client_validate(client)) break;
560   
561         /* compress changes to a single property into a single change */
562         while (XCheckTypedWindowEvent(ob_display, e->type,
563                                       client->window, &ce)) {
564             /* XXX: it would be nice to compress ALL changes to a property,
565                not just changes in a row without other props between. */
566             if (ce.xproperty.atom != e->xproperty.atom) {
567                 XPutBackEvent(ob_display, &ce);
568                 break;
569             }
570         }
571
572         msgtype = e->xproperty.atom;
573         if (msgtype == XA_WM_NORMAL_HINTS) {
574             client_update_normal_hints(client);
575             /* normal hints can make a window non-resizable */
576             client_setup_decor_and_functions(client);
577         }
578         else if (msgtype == XA_WM_HINTS)
579             client_update_wmhints(client);
580         else if (msgtype == XA_WM_TRANSIENT_FOR) {
581             client_update_transient_for(client);
582             client_get_type(client);
583             /* type may have changed, so update the layer */
584             client_calc_layer(client);
585             client_setup_decor_and_functions(client);
586         }
587         else if (msgtype == prop_atoms.net_wm_name ||
588                  msgtype == prop_atoms.wm_name)
589             client_update_title(client);
590         else if (msgtype == prop_atoms.net_wm_icon_name ||
591                  msgtype == prop_atoms.wm_icon_name)
592             client_update_icon_title(client);
593         else if (msgtype == prop_atoms.wm_class)
594             client_update_class(client);
595         else if (msgtype == prop_atoms.wm_protocols) {
596             client_update_protocols(client);
597             client_setup_decor_and_functions(client);
598         }
599         else if (msgtype == prop_atoms.net_wm_strut)
600             client_update_strut(client);
601         else if (msgtype == prop_atoms.net_wm_icon)
602             client_update_icons(client);
603         else if (msgtype == prop_atoms.kwm_win_icon)
604             client_update_kwm_icon(client);
605     default:
606         ;
607 #ifdef SHAPE
608         if (extensions_shape && e->type == extensions_shape_event_basep) {
609             client->shaped = ((XShapeEvent*)e)->shaped;
610             engine_frame_adjust_shape(client->frame);
611         }
612 #endif
613     }
614 }