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