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