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