dont focus !normal windows on enter events
[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         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                 if (fi.xfocus.window == e->xfocus.window)
247                     return;
248                 /* secret magic way of event_process telling us that no client
249                    was found for the FocusIn event */
250                 if (fi.xfocus.window == None)
251                     focus_fallback(FALSE);
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 == FocusIn)
278         e->xfocus.window = None; /* says no client was found for the event */
279     else if (e->type == ConfigureRequest) {
280         /* unhandled configure requests must be used to configure the
281            window directly */
282         XWindowChanges xwc;
283                
284         xwc.x = e->xconfigurerequest.x;
285         xwc.y = e->xconfigurerequest.y;
286         xwc.width = e->xconfigurerequest.width;
287         xwc.height = e->xconfigurerequest.height;
288         xwc.border_width = e->xconfigurerequest.border_width;
289         xwc.sibling = e->xconfigurerequest.above;
290         xwc.stack_mode = e->xconfigurerequest.detail;
291        
292         /* we are not to be held responsible if someone sends us an
293            invalid request! */
294         xerror_set_ignore(TRUE);
295         XConfigureWindow(ob_display, window,
296                          e->xconfigurerequest.value_mask, &xwc);
297         xerror_set_ignore(FALSE);
298     }
299
300     /* dispatch the event to registered handlers */
301     dispatch_x(e, client);
302 }
303
304 static void event_handle_root(XEvent *e)
305 {
306     Atom msgtype;
307      
308     switch(e->type) {
309     case ClientMessage:
310         if (e->xclient.format != 32) break;
311
312         msgtype = e->xclient.message_type;
313         if (msgtype == prop_atoms.net_current_desktop) {
314             unsigned int d = e->xclient.data.l[0];
315             if (d < screen_num_desktops)
316                 screen_set_desktop(d);
317         } else if (msgtype == prop_atoms.net_number_of_desktops) {
318             unsigned int d = e->xclient.data.l[0];
319             if (d > 0)
320                 screen_set_num_desktops(d);
321         } else if (msgtype == prop_atoms.net_showing_desktop) {
322             screen_show_desktop(e->xclient.data.l[0] != 0);
323         }
324         break;
325     case PropertyNotify:
326         if (e->xproperty.atom == prop_atoms.net_desktop_names)
327             screen_update_desktop_names();
328         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
329             screen_update_layout();
330         break;
331     }
332 }
333
334 static void event_handle_client(Client *client, XEvent *e)
335 {
336     XEvent ce;
337     Atom msgtype;
338     int i=0;
339     ConfigValue focus_follow;
340      
341     switch (e->type) {
342     case FocusIn:
343         focus_set_client(client);
344     case FocusOut:
345         g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
346                   client->window);
347         /* focus state can affect the stacking layer */
348         client_calc_layer(client);
349         engine_frame_adjust_focus(client->frame);
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         } else if (msgtype == prop_atoms.net_wm_state) {
528             /* can't compress these */
529             g_message("net_wm_state %s %ld %ld for 0x%lx",
530                       (e->xclient.data.l[0] == 0 ? "Remove" :
531                        e->xclient.data.l[0] == 1 ? "Add" :
532                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
533                       e->xclient.data.l[1], e->xclient.data.l[2],
534                       client->window);
535             client_set_state(client, e->xclient.data.l[0],
536                              e->xclient.data.l[1], e->xclient.data.l[2]);
537         } else if (msgtype == prop_atoms.net_close_window) {
538             g_message("net_close_window for 0x%lx", client->window);
539             client_close(client);
540         } else if (msgtype == prop_atoms.net_active_window) {
541             g_message("net_active_window for 0x%lx", client->window);
542             if (screen_showing_desktop)
543                 screen_show_desktop(FALSE);
544             if (client->iconic)
545                 client_iconify(client, FALSE, TRUE);
546             else if (!client->frame->visible)
547                 /* if its not visible for other reasons, then don't mess
548                    with it */
549                 break;
550             if (client->shaded)
551                 client_shade(client, FALSE);
552             client_focus(client);
553             stacking_raise(client);
554         }
555         break;
556     case PropertyNotify:
557         /* validate cuz we query stuff off the client here */
558         if (!client_validate(client)) break;
559   
560         /* compress changes to a single property into a single change */
561         while (XCheckTypedWindowEvent(ob_display, e->type,
562                                       client->window, &ce)) {
563             /* XXX: it would be nice to compress ALL changes to a property,
564                not just changes in a row without other props between. */
565             if (ce.xproperty.atom != e->xproperty.atom) {
566                 XPutBackEvent(ob_display, &ce);
567                 break;
568             }
569         }
570
571         msgtype = e->xproperty.atom;
572         if (msgtype == XA_WM_NORMAL_HINTS) {
573             client_update_normal_hints(client);
574             /* normal hints can make a window non-resizable */
575             client_setup_decor_and_functions(client);
576         }
577         else if (msgtype == XA_WM_HINTS)
578             client_update_wmhints(client);
579         else if (msgtype == XA_WM_TRANSIENT_FOR) {
580             client_update_transient_for(client);
581             client_get_type(client);
582             /* type may have changed, so update the layer */
583             client_calc_layer(client);
584             client_setup_decor_and_functions(client);
585         }
586         else if (msgtype == prop_atoms.net_wm_name ||
587                  msgtype == prop_atoms.wm_name)
588             client_update_title(client);
589         else if (msgtype == prop_atoms.net_wm_icon_name ||
590                  msgtype == prop_atoms.wm_icon_name)
591             client_update_icon_title(client);
592         else if (msgtype == prop_atoms.wm_class)
593             client_update_class(client);
594         else if (msgtype == prop_atoms.wm_protocols) {
595             client_update_protocols(client);
596             client_setup_decor_and_functions(client);
597         }
598         else if (msgtype == prop_atoms.net_wm_strut)
599             client_update_strut(client);
600         else if (msgtype == prop_atoms.net_wm_icon)
601             client_update_icons(client);
602         else if (msgtype == prop_atoms.kwm_win_icon)
603             client_update_kwm_icon(client);
604     default:
605         ;
606 #ifdef SHAPE
607         if (extensions_shape && e->type == extensions_shape_event_basep) {
608             client->shaped = ((XShapeEvent*)e)->shaped;
609             engine_frame_adjust_shape(client->frame);
610         }
611 #endif
612     }
613 }