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