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