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