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