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