FocusOut's are NOT ignored. thats bullshit. fixing comment.
[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     client = g_hash_table_lookup(client_map, &window);
153
154     /* grab the lasttime and hack up the state */
155     switch (e->type) {
156     case ButtonPress:
157     case ButtonRelease:
158         event_lasttime = e->xbutton.time;
159         e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask);
160         /* kill off the Button1Mask etc, only want the modifiers */
161         e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask |
162                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
163         break;
164     case KeyPress:
165         event_lasttime = e->xkey.time;
166         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
167         /* kill off the Button1Mask etc, only want the modifiers */
168         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
169                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
170         /* add to the state the mask of the modifier being pressed, if it is
171            a modifier key being pressed (this is a little ugly..) */
172 /* I'm commenting this out cuz i don't want "C-Control_L" being returned. */
173 /*      kp = modmap->modifiermap;*/
174 /*      for (i = 0; i < mask_table_size; ++i) {*/
175 /*          for (k = 0; k < modmap->max_keypermod; ++k) {*/
176 /*              if (*kp == e->xkey.keycode) {*/ /* found the keycode */
177                     /* add the mask for it */
178 /*                  e->xkey.state |= mask_table[i];*/
179                     /* cause the first loop to break; */
180 /*                  i = mask_table_size;*/
181 /*                  break;*/ /* get outta here! */
182 /*              }*/
183 /*              ++kp;*/
184 /*          }*/
185 /*      }*/
186
187         break;
188     case KeyRelease:
189         event_lasttime = e->xkey.time;
190         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
191         /* kill off the Button1Mask etc, only want the modifiers */
192         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
193                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
194         /* remove from the state the mask of the modifier being released, if
195            it is a modifier key being released (this is a little ugly..) */
196         kp = modmap->modifiermap;
197         for (i = 0; i < mask_table_size; ++i) {
198             for (k = 0; k < modmap->max_keypermod; ++k) {
199                 if (*kp == e->xkey.keycode) { /* found the keycode */
200                     /* remove the mask for it */
201                     e->xkey.state &= ~mask_table[i];
202                     /* cause the first loop to break; */
203                     i = mask_table_size;
204                     break; /* get outta here! */
205                 }
206                 ++kp;
207             }
208         }
209         break;
210     case MotionNotify:
211         event_lasttime = e->xmotion.time;
212         e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask);
213         /* kill off the Button1Mask etc, only want the modifiers */
214         e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask |
215                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
216         /* compress events */
217         while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) {
218             e->xmotion.x_root = ce.xmotion.x_root;
219             e->xmotion.y_root = ce.xmotion.y_root;
220         }
221         break;
222     case PropertyNotify:
223         event_lasttime = e->xproperty.time;
224         break;
225     case FocusIn:
226         g_message("FocusIn on %lx mode %d detail %d", window,
227                   e->xfocus.mode, e->xfocus.detail);
228         if (client == NULL) {
229             /* says a client was not found for the event!
230                this is important whether the event is a valid type for us or
231                not! this makes the evil known as mozilla not DESTROY my
232                precious wm!! YES ITS FIVE AM AND I AM NOT SANE RIGHT NOW. FOCUS
233                EVENTS WILL DRIVE YOU MAD.
234              */
235             e->xfocus.window = None;
236         }
237
238         /* NotifyAncestor is not ignored in FocusIn like it is in FocusOut
239            because of RevertToPointerRoot. If the focus ends up reverting to
240            pointer root on a workspace change, then the FocusIn event that we
241            want will be of type NotifyAncestor. This situation does not occur
242            for FocusOut, so it is safely ignored there.
243         */
244         if (e->xfocus.detail == NotifyInferior ||
245             e->xfocus.detail > NotifyNonlinearVirtual) return;
246             g_message("FocusIn on %lx", window);
247         break;
248     case FocusOut:
249         g_message("FocusOut on %lx mode %d detail %d", window,
250                   e->xfocus.mode, e->xfocus.detail);
251         if (e->xfocus.mode == NotifyGrab ||
252             e->xfocus.detail == NotifyInferior ||
253             e->xfocus.detail == NotifyAncestor ||
254             e->xfocus.detail > NotifyNonlinearVirtual) return;
255
256         g_message("FocusOut on %lx", window);
257         /* Try process a FocusIn first, and if a legit one isn't found, then
258            do the fallback shiznit. */
259         {
260             XEvent fi;
261             if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
262                 event_process(&fi);
263
264                 /* secret magic way of event_process telling us that no client
265                    was found for the FocusIn event. ^_^ */
266                 if (fi.xfocus.window == None)
267                     focus_fallback(FALSE);
268                 if (fi.xfocus.window == e->xfocus.window)
269                     return;
270             } else
271                 focus_fallback(FALSE);
272         }
273         break;
274     case EnterNotify:
275     case LeaveNotify:
276         event_lasttime = e->xcrossing.time;
277         /* NotifyUngrab occurs when a mouse button is released and the event is
278            caused, like when lowering a window */
279         if (e->xcrossing.mode == NotifyGrab) return;
280         break;
281     default:
282         event_lasttime = CurrentTime;
283         break;
284     }
285
286     /* deal with it in the kernel */
287     if (client)
288         event_handle_client(client, e);
289     else if (window == ob_root)
290         event_handle_root(e);
291     else if (e->type == MapRequest)
292         client_manage(window);
293     else if (e->type == ConfigureRequest) {
294         /* unhandled configure requests must be used to configure the
295            window directly */
296         XWindowChanges xwc;
297                
298         xwc.x = e->xconfigurerequest.x;
299         xwc.y = e->xconfigurerequest.y;
300         xwc.width = e->xconfigurerequest.width;
301         xwc.height = e->xconfigurerequest.height;
302         xwc.border_width = e->xconfigurerequest.border_width;
303         xwc.sibling = e->xconfigurerequest.above;
304         xwc.stack_mode = e->xconfigurerequest.detail;
305        
306         /* we are not to be held responsible if someone sends us an
307            invalid request! */
308         xerror_set_ignore(TRUE);
309         XConfigureWindow(ob_display, window,
310                          e->xconfigurerequest.value_mask, &xwc);
311         xerror_set_ignore(FALSE);
312     }
313
314     /* dispatch the event to registered handlers */
315     dispatch_x(e, client);
316 }
317
318 static void event_handle_root(XEvent *e)
319 {
320     Atom msgtype;
321      
322     switch(e->type) {
323     case ClientMessage:
324         if (e->xclient.format != 32) break;
325
326         msgtype = e->xclient.message_type;
327         if (msgtype == prop_atoms.net_current_desktop) {
328             unsigned int d = e->xclient.data.l[0];
329             if (d < screen_num_desktops)
330                 screen_set_desktop(d);
331         } else if (msgtype == prop_atoms.net_number_of_desktops) {
332             unsigned int d = e->xclient.data.l[0];
333             if (d > 0)
334                 screen_set_num_desktops(d);
335         } else if (msgtype == prop_atoms.net_showing_desktop) {
336             screen_show_desktop(e->xclient.data.l[0] != 0);
337         }
338         break;
339     case PropertyNotify:
340         if (e->xproperty.atom == prop_atoms.net_desktop_names)
341             screen_update_desktop_names();
342         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
343             screen_update_layout();
344         break;
345     }
346 }
347
348 static void event_handle_client(Client *client, XEvent *e)
349 {
350     XEvent ce;
351     Atom msgtype;
352     int i=0;
353     ConfigValue focus_follow;
354      
355     switch (e->type) {
356     case FocusIn:
357         focus_set_client(client);
358     case FocusOut:
359         g_message("Focus%s on client for %lx", (e->type==FocusIn?"In":"Out"),
360                   client->window);
361         /* focus state can affect the stacking layer */
362         client_calc_layer(client);
363         engine_frame_adjust_focus(client->frame);
364         break;
365     case EnterNotify:
366         if (client_normal(client)) {
367             if (ob_state == State_Starting) {
368                 /* move it to the top of the focus order */
369                 guint desktop = client->desktop;
370                 if (desktop == DESKTOP_ALL) desktop = screen_desktop;
371                 focus_order[desktop] = g_list_remove(focus_order[desktop],
372                                                      client);
373                 focus_order[desktop] = g_list_prepend(focus_order[desktop],
374                                                       client);
375             } else {
376                 if (!config_get("focusFollowsMouse",Config_Bool,&focus_follow))
377                     g_assert_not_reached();
378                 if (focus_follow.bool)
379                     client_focus(client);
380             }
381         }
382         break;
383     case ConfigureRequest:
384         /* compress these */
385         while (XCheckTypedWindowEvent(ob_display, client->window,
386                                       ConfigureRequest, &ce)) {
387             ++i;
388             /* XXX if this causes bad things.. we can compress config req's
389                with the same mask. */
390             e->xconfigurerequest.value_mask |=
391                 ce.xconfigurerequest.value_mask;
392             if (ce.xconfigurerequest.value_mask & CWX)
393                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
394             if (ce.xconfigurerequest.value_mask & CWY)
395                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
396             if (ce.xconfigurerequest.value_mask & CWWidth)
397                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
398             if (ce.xconfigurerequest.value_mask & CWHeight)
399                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
400             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
401                 e->xconfigurerequest.border_width =
402                     ce.xconfigurerequest.border_width;
403             if (ce.xconfigurerequest.value_mask & CWStackMode)
404                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
405         }
406         if (i) g_message("Compressed %d Configures", i);
407
408         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
409         if (client->iconic || client->shaded) return;
410
411         if (e->xconfigurerequest.value_mask & CWBorderWidth)
412             client->border_width = e->xconfigurerequest.border_width;
413
414         /* resize, then move, as specified in the EWMH section 7.7 */
415         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
416                                                CWX | CWY)) {
417             int x, y, w, h;
418             Corner corner;
419                
420             x = (e->xconfigurerequest.value_mask & CWX) ?
421                 e->xconfigurerequest.x : client->area.x;
422             y = (e->xconfigurerequest.value_mask & CWY) ?
423                 e->xconfigurerequest.y : client->area.y;
424             w = (e->xconfigurerequest.value_mask & CWWidth) ?
425                 e->xconfigurerequest.width : client->area.width;
426             h = (e->xconfigurerequest.value_mask & CWHeight) ?
427                 e->xconfigurerequest.height : client->area.height;
428                
429             switch (client->gravity) {
430             case NorthEastGravity:
431             case EastGravity:
432                 corner = Corner_TopRight;
433                 break;
434             case SouthWestGravity:
435             case SouthGravity:
436                 corner = Corner_BottomLeft;
437                 break;
438             case SouthEastGravity:
439                 corner = Corner_BottomRight;
440                 break;
441             default:     /* NorthWest, Static, etc */
442                 corner = Corner_TopLeft;
443             }
444
445             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
446         }
447
448         if (e->xconfigurerequest.value_mask & CWStackMode) {
449             switch (e->xconfigurerequest.detail) {
450             case Below:
451             case BottomIf:
452                 stacking_lower(client);
453                 break;
454
455             case Above:
456             case TopIf:
457             default:
458                 stacking_raise(client);
459                 break;
460             }
461         }
462         break;
463     case UnmapNotify:
464         if (client->ignore_unmaps) {
465             client->ignore_unmaps--;
466             break;
467         }
468         client_unmanage(client);
469         break;
470     case DestroyNotify:
471         client_unmanage(client);
472         break;
473     case ReparentNotify:
474         /* this is when the client is first taken captive in the frame */
475         if (e->xreparent.parent == client->frame->plate) break;
476
477         /*
478           This event is quite rare and is usually handled in unmapHandler.
479           However, if the window is unmapped when the reparent event occurs,
480           the window manager never sees it because an unmap event is not sent
481           to an already unmapped window.
482         */
483
484         /* we don't want the reparent event, put it back on the stack for the
485            X server to deal with after we unmanage the window */
486         XPutBackEvent(ob_display, e);
487      
488         client_unmanage(client);
489         break;
490     case MapRequest:
491         if (!client->iconic) break; /* this normally doesn't happen, but if it
492                                        does, we don't want it! */
493         if (screen_showing_desktop)
494             screen_show_desktop(FALSE);
495         client_iconify(client, FALSE, TRUE);
496         if (!client->frame->visible)
497             /* if its not visible still, then don't mess with it */
498             break;
499         if (client->shaded)
500             client_shade(client, FALSE);
501         client_focus(client);
502         stacking_raise(client);
503         break;
504     case ClientMessage:
505         /* validate cuz we query stuff off the client here */
506         if (!client_validate(client)) break;
507   
508         if (e->xclient.format != 32) return;
509
510         msgtype = e->xclient.message_type;
511         if (msgtype == prop_atoms.wm_change_state) {
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             client_set_wm_state(client, e->xclient.data.l[0]);
525         } else if (msgtype == prop_atoms.net_wm_desktop) {
526             /* compress changes into a single change */
527             while (XCheckTypedWindowEvent(ob_display, e->type,
528                                           client->window, &ce)) {
529                 /* XXX: it would be nice to compress ALL messages of a
530                    type, not just messages in a row without other
531                    message types between. */
532                 if (ce.xclient.message_type != msgtype) {
533                     XPutBackEvent(ob_display, &ce);
534                     break;
535                 }
536                 e->xclient = ce.xclient;
537             }
538             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops ||
539                 (unsigned)e->xclient.data.l[0] == DESKTOP_ALL)
540                 client_set_desktop(client, (unsigned)e->xclient.data.l[0],
541                                    FALSE);
542         } else if (msgtype == prop_atoms.net_wm_state) {
543             /* can't compress these */
544             g_message("net_wm_state %s %ld %ld for 0x%lx",
545                       (e->xclient.data.l[0] == 0 ? "Remove" :
546                        e->xclient.data.l[0] == 1 ? "Add" :
547                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
548                       e->xclient.data.l[1], e->xclient.data.l[2],
549                       client->window);
550             client_set_state(client, e->xclient.data.l[0],
551                              e->xclient.data.l[1], e->xclient.data.l[2]);
552         } else if (msgtype == prop_atoms.net_close_window) {
553             g_message("net_close_window for 0x%lx", client->window);
554             client_close(client);
555         } else if (msgtype == prop_atoms.net_active_window) {
556             g_message("net_active_window for 0x%lx", client->window);
557             if (screen_showing_desktop)
558                 screen_show_desktop(FALSE);
559             if (client->iconic)
560                 client_iconify(client, FALSE, TRUE);
561             else if (!client->frame->visible)
562                 /* if its not visible for other reasons, then don't mess
563                    with it */
564                 break;
565             if (client->shaded)
566                 client_shade(client, FALSE);
567             client_focus(client);
568             stacking_raise(client);
569         }
570         break;
571     case PropertyNotify:
572         /* validate cuz we query stuff off the client here */
573         if (!client_validate(client)) break;
574   
575         /* compress changes to a single property into a single change */
576         while (XCheckTypedWindowEvent(ob_display, e->type,
577                                       client->window, &ce)) {
578             /* XXX: it would be nice to compress ALL changes to a property,
579                not just changes in a row without other props between. */
580             if (ce.xproperty.atom != e->xproperty.atom) {
581                 XPutBackEvent(ob_display, &ce);
582                 break;
583             }
584         }
585
586         msgtype = e->xproperty.atom;
587         if (msgtype == XA_WM_NORMAL_HINTS) {
588             client_update_normal_hints(client);
589             /* normal hints can make a window non-resizable */
590             client_setup_decor_and_functions(client);
591         }
592         else if (msgtype == XA_WM_HINTS)
593             client_update_wmhints(client);
594         else if (msgtype == XA_WM_TRANSIENT_FOR) {
595             client_update_transient_for(client);
596             client_get_type(client);
597             /* type may have changed, so update the layer */
598             client_calc_layer(client);
599             client_setup_decor_and_functions(client);
600         }
601         else if (msgtype == prop_atoms.net_wm_name ||
602                  msgtype == prop_atoms.wm_name)
603             client_update_title(client);
604         else if (msgtype == prop_atoms.net_wm_icon_name ||
605                  msgtype == prop_atoms.wm_icon_name)
606             client_update_icon_title(client);
607         else if (msgtype == prop_atoms.wm_class)
608             client_update_class(client);
609         else if (msgtype == prop_atoms.wm_protocols) {
610             client_update_protocols(client);
611             client_setup_decor_and_functions(client);
612         }
613         else if (msgtype == prop_atoms.net_wm_strut)
614             client_update_strut(client);
615         else if (msgtype == prop_atoms.net_wm_icon)
616             client_update_icons(client);
617         else if (msgtype == prop_atoms.kwm_win_icon)
618             client_update_kwm_icon(client);
619     default:
620         ;
621 #ifdef SHAPE
622         if (extensions_shape && e->type == extensions_shape_event_basep) {
623             client->shaped = ((XShapeEvent*)e)->shaped;
624             engine_frame_adjust_shape(client->frame);
625         }
626 #endif
627     }
628 }