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