]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/event.c
use ungrab enter events
[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     /* grab the lasttime and hack up the state */
152     switch (e->type) {
153     case ButtonPress:
154     case ButtonRelease:
155         event_lasttime = e->xbutton.time;
156         e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask);
157         /* kill off the Button1Mask etc, only want the modifiers */
158         e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask |
159                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
160         break;
161     case KeyPress:
162         event_lasttime = e->xkey.time;
163         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
164         /* kill off the Button1Mask etc, only want the modifiers */
165         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
166                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
167         /* add to the state the mask of the modifier being pressed, if it is
168            a modifier key being pressed (this is a little ugly..) */
169 /* I'm commenting this out cuz i don't want "C-Control_L" being returned. */
170 /*      kp = modmap->modifiermap;*/
171 /*      for (i = 0; i < mask_table_size; ++i) {*/
172 /*          for (k = 0; k < modmap->max_keypermod; ++k) {*/
173 /*              if (*kp == e->xkey.keycode) {*/ /* found the keycode */
174                     /* add the mask for it */
175 /*                  e->xkey.state |= mask_table[i];*/
176                     /* cause the first loop to break; */
177 /*                  i = mask_table_size;*/
178 /*                  break;*/ /* get outta here! */
179 /*              }*/
180 /*              ++kp;*/
181 /*          }*/
182 /*      }*/
183
184         break;
185     case KeyRelease:
186         event_lasttime = e->xkey.time;
187         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
188         /* kill off the Button1Mask etc, only want the modifiers */
189         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
190                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
191         /* remove from the state the mask of the modifier being released, if
192            it is a modifier key being released (this is a little ugly..) */
193         kp = modmap->modifiermap;
194         for (i = 0; i < mask_table_size; ++i) {
195             for (k = 0; k < modmap->max_keypermod; ++k) {
196                 if (*kp == e->xkey.keycode) { /* found the keycode */
197                     /* remove the mask for it */
198                     e->xkey.state &= ~mask_table[i];
199                     /* cause the first loop to break; */
200                     i = mask_table_size;
201                     break; /* get outta here! */
202                 }
203                 ++kp;
204             }
205         }
206         break;
207     case MotionNotify:
208         event_lasttime = e->xmotion.time;
209         e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask);
210         /* kill off the Button1Mask etc, only want the modifiers */
211         e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask |
212                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
213         /* compress events */
214         while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) {
215             e->xmotion.x_root = ce.xmotion.x_root;
216             e->xmotion.y_root = ce.xmotion.y_root;
217         }
218         break;
219     case PropertyNotify:
220         event_lasttime = e->xproperty.time;
221         break;
222     case FocusIn:
223         if (e->xfocus.mode == NotifyGrab ||
224             !(e->xfocus.detail == NotifyNonlinearVirtual ||
225               e->xfocus.detail == NotifyNonlinear))
226             return;
227         break;
228     case FocusOut:
229         if (e->xfocus.mode == NotifyGrab ||
230             !(e->xfocus.detail == NotifyNonlinearVirtual ||
231               e->xfocus.detail == NotifyNonlinear))
232             return;
233
234         /* FocusOut events just make us look for FocusIn events. They
235            are mostly ignored otherwise. */
236         {
237             XEvent fi;
238             if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
239                 event_process(&fi);
240
241                 if (fi.xfocus.window == e->xfocus.window)
242                     return;
243             }
244         }
245         break;
246     case EnterNotify:
247     case LeaveNotify:
248         event_lasttime = e->xcrossing.time;
249         /* NotifyUngrab occurs when a mouse button is released and the event is
250            caused, like when lowering a window */
251         if (e->xcrossing.mode == NotifyGrab) return;
252         break;
253     }
254
255     client = g_hash_table_lookup(client_map, &window);
256
257     /* deal with it in the kernel */
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 == MapRequest)
263         client_manage(window);
264     else if (e->type == ConfigureRequest) {
265         /* unhandled configure requests must be used to configure the
266            window directly */
267         XWindowChanges xwc;
268                
269         xwc.x = e->xconfigurerequest.x;
270         xwc.y = e->xconfigurerequest.y;
271         xwc.width = e->xconfigurerequest.width;
272         xwc.height = e->xconfigurerequest.height;
273         xwc.border_width = e->xconfigurerequest.border_width;
274         xwc.sibling = e->xconfigurerequest.above;
275         xwc.stack_mode = e->xconfigurerequest.detail;
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 the event to registered handlers */
286     dispatch_x(e, client);
287 }
288
289 static void event_handle_root(XEvent *e)
290 {
291     Atom msgtype;
292      
293     switch(e->type) {
294     case ClientMessage:
295         if (e->xclient.format != 32) break;
296
297         msgtype = e->xclient.message_type;
298         if (msgtype == prop_atoms.net_current_desktop) {
299             unsigned int d = e->xclient.data.l[0];
300             if (d <= screen_num_desktops)
301                 screen_set_desktop(d);
302         } else if (msgtype == prop_atoms.net_number_of_desktops) {
303             unsigned int d = e->xclient.data.l[0];
304             if (d > 0)
305                 screen_set_num_desktops(d);
306         } else if (msgtype == prop_atoms.net_showing_desktop) {
307             screen_show_desktop(e->xclient.data.l[0] != 0);
308         }
309         break;
310     case PropertyNotify:
311         if (e->xproperty.atom == prop_atoms.net_desktop_names)
312             screen_update_desktop_names();
313         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
314             screen_update_layout();
315         break;
316     }
317 }
318
319 static void event_handle_client(Client *client, XEvent *e)
320 {
321     XEvent ce;
322     Atom msgtype;
323         int i=0;
324      
325     switch (e->type) {
326     case FocusIn:
327     case FocusOut:
328         client_set_focused(client, e->type == FocusIn);
329         break;
330     case ConfigureRequest:
331         /* compress these */
332         while (XCheckTypedWindowEvent(ob_display, client->window,
333                                       ConfigureRequest, &ce)) {
334             ++i;
335             /* XXX if this causes bad things.. we can compress config req's
336                with the same mask. */
337             e->xconfigurerequest.value_mask |=
338                 ce.xconfigurerequest.value_mask;
339             if (ce.xconfigurerequest.value_mask & CWX)
340                 e->xconfigurerequest.x = ce.xconfigurerequest.x;
341             if (ce.xconfigurerequest.value_mask & CWY)
342                 e->xconfigurerequest.y = ce.xconfigurerequest.y;
343             if (ce.xconfigurerequest.value_mask & CWWidth)
344                 e->xconfigurerequest.width = ce.xconfigurerequest.width;
345             if (ce.xconfigurerequest.value_mask & CWHeight)
346                 e->xconfigurerequest.height = ce.xconfigurerequest.height;
347             if (ce.xconfigurerequest.value_mask & CWBorderWidth)
348                 e->xconfigurerequest.border_width =
349                     ce.xconfigurerequest.border_width;
350             if (ce.xconfigurerequest.value_mask & CWStackMode)
351                 e->xconfigurerequest.detail = ce.xconfigurerequest.detail;
352         }
353         if (i) g_message("Compressed %d Configures", i);
354
355         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
356         if (client->iconic || client->shaded) return;
357
358         if (e->xconfigurerequest.value_mask & CWBorderWidth)
359             client->border_width = e->xconfigurerequest.border_width;
360
361         /* resize, then move, as specified in the EWMH section 7.7 */
362         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
363                                                CWX | CWY)) {
364             int x, y, w, h;
365             Corner corner;
366                
367             x = (e->xconfigurerequest.value_mask & CWX) ?
368                 e->xconfigurerequest.x : client->area.x;
369             y = (e->xconfigurerequest.value_mask & CWY) ?
370                 e->xconfigurerequest.y : client->area.y;
371             w = (e->xconfigurerequest.value_mask & CWWidth) ?
372                 e->xconfigurerequest.width : client->area.width;
373             h = (e->xconfigurerequest.value_mask & CWHeight) ?
374                 e->xconfigurerequest.height : client->area.height;
375                
376             switch (client->gravity) {
377             case NorthEastGravity:
378             case EastGravity:
379                 corner = Corner_TopRight;
380                 break;
381             case SouthWestGravity:
382             case SouthGravity:
383                 corner = Corner_BottomLeft;
384                 break;
385             case SouthEastGravity:
386                 corner = Corner_BottomRight;
387                 break;
388             default:     /* NorthWest, Static, etc */
389                 corner = Corner_TopLeft;
390             }
391
392             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
393         }
394
395         if (e->xconfigurerequest.value_mask & CWStackMode) {
396             switch (e->xconfigurerequest.detail) {
397             case Below:
398             case BottomIf:
399                 stacking_lower(client);
400                 break;
401
402             case Above:
403             case TopIf:
404             default:
405                 stacking_raise(client);
406                 break;
407             }
408         }
409         break;
410     case UnmapNotify:
411         if (client->ignore_unmaps) {
412             client->ignore_unmaps--;
413             break;
414         }
415         g_message("UnmapNotify for %lx", client->window);
416         client_unmanage(client);
417         break;
418     case DestroyNotify:
419         g_message("DestroyNotify for %lx", client->window);
420         client_unmanage(client);
421         break;
422     case ReparentNotify:
423         /* this is when the client is first taken captive in the frame */
424         if (e->xreparent.parent == client->frame->plate) break;
425
426         /*
427           This event is quite rare and is usually handled in unmapHandler.
428           However, if the window is unmapped when the reparent event occurs,
429           the window manager never sees it because an unmap event is not sent
430           to an already unmapped window.
431         */
432
433         /* we don't want the reparent event, put it back on the stack for the
434            X server to deal with after we unmanage the window */
435         XPutBackEvent(ob_display, e);
436      
437         client_unmanage(client);
438         break;
439     case MapRequest:
440         if (!client->iconic) break; /* this normally doesn't happen, but if it
441                                        does, we don't want it! */
442         if (screen_showing_desktop)
443             screen_show_desktop(FALSE);
444         client_iconify(client, FALSE, TRUE);
445         if (!client->frame->visible)
446             /* if its not visible still, then don't mess with it */
447             break;
448         if (client->shaded)
449             client_shade(client, FALSE);
450         client_focus(client);
451         stacking_raise(client);
452         break;
453     case ClientMessage:
454         /* validate cuz we query stuff off the client here */
455         if (!client_validate(client)) break;
456   
457         if (e->xclient.format != 32) return;
458
459         msgtype = e->xclient.message_type;
460         if (msgtype == prop_atoms.wm_change_state) {
461             /* compress changes into a single change */
462             while (XCheckTypedWindowEvent(ob_display, e->type,
463                                           client->window, &ce)) {
464                 /* XXX: it would be nice to compress ALL messages of a
465                    type, not just messages in a row without other
466                    message types between. */
467                 if (ce.xclient.message_type != msgtype) {
468                     XPutBackEvent(ob_display, &ce);
469                     break;
470                 }
471                 e->xclient = ce.xclient;
472             }
473             client_set_wm_state(client, e->xclient.data.l[0]);
474         } else if (msgtype == prop_atoms.net_wm_desktop) {
475             /* compress changes into a single change */
476             while (XCheckTypedWindowEvent(ob_display, e->type,
477                                           client->window, &ce)) {
478                 /* XXX: it would be nice to compress ALL messages of a
479                    type, not just messages in a row without other
480                    message types between. */
481                 if (ce.xclient.message_type != msgtype) {
482                     XPutBackEvent(ob_display, &ce);
483                     break;
484                 }
485                 e->xclient = ce.xclient;
486             }
487             if ((unsigned)e->xclient.data.l[0] < screen_num_desktops)
488                 client_set_desktop(client, (unsigned)e->xclient.data.l[0]);
489         } else if (msgtype == prop_atoms.net_wm_state) {
490             /* can't compress these */
491             g_message("net_wm_state %s %ld %ld for 0x%lx",
492                       (e->xclient.data.l[0] == 0 ? "Remove" :
493                        e->xclient.data.l[0] == 1 ? "Add" :
494                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
495                       e->xclient.data.l[1], e->xclient.data.l[2],
496                       client->window);
497             client_set_state(client, e->xclient.data.l[0],
498                              e->xclient.data.l[1], e->xclient.data.l[2]);
499         } else if (msgtype == prop_atoms.net_close_window) {
500             g_message("net_close_window for 0x%lx", client->window);
501             client_close(client);
502         } else if (msgtype == prop_atoms.net_active_window) {
503             g_message("net_active_window for 0x%lx", client->window);
504             if (screen_showing_desktop)
505                 screen_show_desktop(FALSE);
506             if (client->iconic)
507                 client_iconify(client, FALSE, TRUE);
508             else if (!client->frame->visible)
509                 /* if its not visible for other reasons, then don't mess
510                    with it */
511                 break;
512             if (client->shaded)
513                 client_shade(client, FALSE);
514             client_focus(client);
515             stacking_raise(client);
516         }
517         break;
518     case PropertyNotify:
519         /* validate cuz we query stuff off the client here */
520         if (!client_validate(client)) break;
521   
522         /* compress changes to a single property into a single change */
523         while (XCheckTypedWindowEvent(ob_display, e->type,
524                                       client->window, &ce)) {
525             /* XXX: it would be nice to compress ALL changes to a property,
526                not just changes in a row without other props between. */
527             if (ce.xproperty.atom != e->xproperty.atom) {
528                 XPutBackEvent(ob_display, &ce);
529                 break;
530             }
531         }
532
533         msgtype = e->xproperty.atom;
534         if (msgtype == XA_WM_NORMAL_HINTS) {
535             client_update_normal_hints(client);
536             /* normal hints can make a window non-resizable */
537             client_setup_decor_and_functions(client);
538         }
539         else if (msgtype == XA_WM_HINTS)
540             client_update_wmhints(client);
541         else if (msgtype == XA_WM_TRANSIENT_FOR) {
542             client_update_transient_for(client);
543             client_get_type(client);
544             /* type may have changed, so update the layer */
545             client_calc_layer(client);
546             client_setup_decor_and_functions(client);
547         }
548         else if (msgtype == prop_atoms.net_wm_name ||
549                  msgtype == prop_atoms.wm_name)
550             client_update_title(client);
551         else if (msgtype == prop_atoms.net_wm_icon_name ||
552                  msgtype == prop_atoms.wm_icon_name)
553             client_update_icon_title(client);
554         else if (msgtype == prop_atoms.wm_class)
555             client_update_class(client);
556         else if (msgtype == prop_atoms.wm_protocols) {
557             client_update_protocols(client);
558             client_setup_decor_and_functions(client);
559         }
560         else if (msgtype == prop_atoms.net_wm_strut)
561             client_update_strut(client);
562         else if (msgtype == prop_atoms.net_wm_icon)
563             client_update_icons(client);
564         else if (msgtype == prop_atoms.kwm_win_icon)
565             client_update_kwm_icon(client);
566     default:
567         ;
568 #ifdef SHAPE
569         if (extensions_shape && e->type == extensions_shape_event_basep) {
570             client->shaped = ((XShapeEvent*)e)->shaped;
571             engine_frame_adjust_shape(client->frame);
572         }
573 #endif
574     }
575 }