]> icculus.org git repositories - mikachu/openbox.git/blob - openbox/event.c
add strict ansi compliance
[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 UnmapNotify:
126         window = e->xunmap.window;
127         break;
128     case DestroyNotify:
129         window = e->xdestroywindow.window;
130         break;
131     case ConfigureRequest:
132         window = e->xconfigurerequest.window;
133         break;
134     default:
135         /* XKB events */
136         if (e->type == extensions_xkb_event_basep) {
137             switch (((XkbAnyEvent*)&e)->xkb_type) {
138             case XkbBellNotify:
139                 window = ((XkbBellNotifyEvent*)&e)->window;
140             default:
141                 window = None;
142             }
143         } else
144             window = e->xany.window;
145     }
146      
147     /* grab the lasttime and hack up the state */
148     switch (e->type) {
149     case ButtonPress:
150     case ButtonRelease:
151         event_lasttime = e->xbutton.time;
152         e->xbutton.state &= ~(LockMask | NumLockMask | ScrollLockMask);
153         /* kill off the Button1Mask etc, only want the modifiers */
154         e->xbutton.state &= (ControlMask | ShiftMask | Mod1Mask |
155                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
156         break;
157     case KeyPress:
158         event_lasttime = e->xkey.time;
159         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
160         /* kill off the Button1Mask etc, only want the modifiers */
161         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
162                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
163         /* add to the state the mask of the modifier being pressed, if it is
164            a modifier key being pressed (this is a little ugly..) */
165 /* I'm commenting this out cuz i don't want "C-Control_L" being returned. */
166 /*      kp = modmap->modifiermap;*/
167 /*      for (i = 0; i < mask_table_size; ++i) {*/
168 /*          for (k = 0; k < modmap->max_keypermod; ++k) {*/
169 /*              if (*kp == e->xkey.keycode) {*/ /* found the keycode */
170                     /* add the mask for it */
171 /*                  e->xkey.state |= mask_table[i];*/
172                     /* cause the first loop to break; */
173 /*                  i = mask_table_size;*/
174 /*                  break;*/ /* get outta here! */
175 /*              }*/
176 /*              ++kp;*/
177 /*          }*/
178 /*      }*/
179
180         break;
181     case KeyRelease:
182         event_lasttime = e->xkey.time;
183         e->xkey.state &= ~(LockMask | NumLockMask | ScrollLockMask);
184         /* kill off the Button1Mask etc, only want the modifiers */
185         e->xkey.state &= (ControlMask | ShiftMask | Mod1Mask |
186                           Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
187         /* remove from the state the mask of the modifier being released, if
188            it is a modifier key being released (this is a little ugly..) */
189         kp = modmap->modifiermap;
190         for (i = 0; i < mask_table_size; ++i) {
191             for (k = 0; k < modmap->max_keypermod; ++k) {
192                 if (*kp == e->xkey.keycode) { /* found the keycode */
193                     /* remove the mask for it */
194                     e->xkey.state &= ~mask_table[i];
195                     /* cause the first loop to break; */
196                     i = mask_table_size;
197                     break; /* get outta here! */
198                 }
199                 ++kp;
200             }
201         }
202         break;
203     case MotionNotify:
204         event_lasttime = e->xmotion.time;
205         e->xmotion.state &= ~(LockMask | NumLockMask | ScrollLockMask);
206         /* kill off the Button1Mask etc, only want the modifiers */
207         e->xmotion.state &= (ControlMask | ShiftMask | Mod1Mask |
208                              Mod2Mask | Mod3Mask | Mod4Mask | Mod5Mask);
209         /* compress events */
210         while (XCheckTypedWindowEvent(ob_display, window, e->type, &ce)) {
211             e->xmotion.x_root = ce.xmotion.x_root;
212             e->xmotion.y_root = ce.xmotion.y_root;
213         }
214         break;
215     case PropertyNotify:
216         event_lasttime = e->xproperty.time;
217         break;
218     case FocusIn:
219     case FocusOut:
220         if (e->xfocus.mode == NotifyGrab)
221             /*|| e.xfocus.mode == NotifyUngrab ||*/
222                
223             /* From Metacity, from WindowMaker, ignore all funky pointer
224                root events. Its commented out cuz I don't think we need this
225                at all. If problems arise we can look into it */
226             /*e.xfocus.detail > NotifyNonlinearVirtual) */
227             return; /* skip me! */
228         if (e->type == FocusOut) {
229             /* FocusOut events just make us look for FocusIn events. They
230                are mostly ignored otherwise. */
231             XEvent fi;
232             if (XCheckTypedEvent(ob_display, FocusIn, &fi)) {
233                 event_process(&fi);
234                 /* dont unfocus the window we just focused! */
235                 if (fi.xfocus.window == e->xfocus.window)
236                     return;
237             }
238         }
239         break;
240     case EnterNotify:
241     case LeaveNotify:
242         event_lasttime = e->xcrossing.time;
243         /* XXX this caused problems before... but i don't remember why. hah.
244            so back it is. if problems arise again, then try filtering on the
245            detail instead of the mode. */
246         if (e->xcrossing.mode != NotifyNormal) return;
247         break;
248     }
249
250     client = g_hash_table_lookup(client_map, (gpointer)window);
251
252     /* deal with it in the kernel */
253     if (client) {
254         event_handle_client(client, e);
255     } else if (window == ob_root)
256         event_handle_root(e);
257     else if (e->type == ConfigureRequest) {
258         /* unhandled configure requests must be used to configure the
259            window directly */
260         XWindowChanges xwc;
261                
262         xwc.x = e->xconfigurerequest.x;
263         xwc.y = e->xconfigurerequest.y;
264         xwc.width = e->xconfigurerequest.width;
265         xwc.height = e->xconfigurerequest.height;
266         xwc.border_width = e->xconfigurerequest.border_width;
267         xwc.sibling = e->xconfigurerequest.above;
268         xwc.stack_mode = e->xconfigurerequest.detail;
269        
270         g_message("Proxying configure event for 0x%lx", window);
271        
272         /* we are not to be held responsible if someone sends us an
273            invalid request! */
274         xerror_set_ignore(TRUE);
275         XConfigureWindow(ob_display, window,
276                          e->xconfigurerequest.value_mask, &xwc);
277         xerror_set_ignore(FALSE);
278     }
279
280     /* dispatch the event to registered handlers */
281     dispatch_x(e, client);
282 }
283
284 static void event_handle_root(XEvent *e)
285 {
286     Atom msgtype;
287      
288     switch(e->type) {
289     case MapRequest:
290         g_message("MapRequest on root");
291         client_manage(e->xmap.window);
292         break;
293     case ClientMessage:
294         if (e->xclient.format != 32) break;
295
296         msgtype = e->xclient.message_type;
297         if (msgtype == prop_atoms.net_current_desktop) {
298             unsigned int d = e->xclient.data.l[0];
299             if (d <= screen_num_desktops)
300                 screen_set_desktop(d);
301         } else if (msgtype == prop_atoms.net_number_of_desktops) {
302             unsigned int d = e->xclient.data.l[0];
303             if (d > 0)
304                 screen_set_num_desktops(d);
305         } else if (msgtype == prop_atoms.net_showing_desktop) {
306             screen_show_desktop(e->xclient.data.l[0] != 0);
307         }
308         break;
309     case PropertyNotify:
310         if (e->xproperty.atom == prop_atoms.net_desktop_names)
311             screen_update_desktop_names();
312         else if (e->xproperty.atom == prop_atoms.net_desktop_layout)
313             screen_update_layout();
314         break;
315     }
316 }
317
318 static void event_handle_client(Client *client, XEvent *e)
319 {
320     XEvent ce;
321     Atom msgtype;
322      
323     switch (e->type) {
324     case FocusIn:
325         client_set_focused(client, TRUE);
326         break;
327     case FocusOut:
328         client_set_focused(client, FALSE);
329         break;
330     case ConfigureRequest:
331         g_message("ConfigureRequest for window %lx", client->window);
332         /* compress these */
333         while (XCheckTypedWindowEvent(ob_display, client->window,
334                                       ConfigureRequest, &ce)) {
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
354         /* if we are iconic (or shaded (fvwm does this)) ignore the event */
355         if (client->iconic || client->shaded) return;
356
357         if (e->xconfigurerequest.value_mask & CWBorderWidth)
358             client->border_width = e->xconfigurerequest.border_width;
359
360         /* resize, then move, as specified in the EWMH section 7.7 */
361         if (e->xconfigurerequest.value_mask & (CWWidth | CWHeight |
362                                                CWX | CWY)) {
363             int x, y, w, h;
364             Corner corner;
365                
366             x = (e->xconfigurerequest.value_mask & CWX) ?
367                 e->xconfigurerequest.x : client->area.x;
368             y = (e->xconfigurerequest.value_mask & CWY) ?
369                 e->xconfigurerequest.y : client->area.y;
370             w = (e->xconfigurerequest.value_mask & CWWidth) ?
371                 e->xconfigurerequest.width : client->area.width;
372             h = (e->xconfigurerequest.value_mask & CWHeight) ?
373                 e->xconfigurerequest.height : client->area.height;
374                
375             switch (client->gravity) {
376             case NorthEastGravity:
377             case EastGravity:
378                 corner = Corner_TopRight;
379                 break;
380             case SouthWestGravity:
381             case SouthGravity:
382                 corner = Corner_BottomLeft;
383                 break;
384             case SouthEastGravity:
385                 corner = Corner_BottomRight;
386                 break;
387             default:     /* NorthWest, Static, etc */
388                 corner = Corner_TopLeft;
389             }
390
391             client_configure(client, corner, x, y, w, h, FALSE, FALSE);
392         }
393
394         if (e->xconfigurerequest.value_mask & CWStackMode) {
395             switch (e->xconfigurerequest.detail) {
396             case Below:
397             case BottomIf:
398                 stacking_lower(client);
399                 break;
400
401             case Above:
402             case TopIf:
403             default:
404                 stacking_raise(client);
405                 break;
406             }
407         }
408         break;
409     case UnmapNotify:
410         if (client->ignore_unmaps) {
411             client->ignore_unmaps--;
412             break;
413         }
414         g_message("UnmapNotify for %lx", client->window);
415         client_unmanage(client);
416         break;
417     case DestroyNotify:
418         g_message("DestroyNotify for %lx", client->window);
419         client_unmanage(client);
420         break;
421     case ReparentNotify:
422         /* this is when the client is first taken captive in the frame */
423         if (e->xreparent.parent == client->frame->plate) break;
424
425         /*
426           This event is quite rare and is usually handled in unmapHandler.
427           However, if the window is unmapped when the reparent event occurs,
428           the window manager never sees it because an unmap event is not sent
429           to an already unmapped window.
430         */
431
432         /* we don't want the reparent event, put it back on the stack for the
433            X server to deal with after we unmanage the window */
434         XPutBackEvent(ob_display, e);
435      
436         client_unmanage(client);
437         break;
438     case MapRequest:
439         /* we shouldn't be able to get this unless we're iconic */
440         g_assert(client->iconic);
441
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             client_set_desktop(client, e->xclient.data.l[0]);
488         } else if (msgtype == prop_atoms.net_wm_state) {
489             /* can't compress these */
490             g_message("net_wm_state %s %ld %ld for 0x%lx\n",
491                       (e->xclient.data.l[0] == 0 ? "Remove" :
492                        e->xclient.data.l[0] == 1 ? "Add" :
493                        e->xclient.data.l[0] == 2 ? "Toggle" : "INVALID"),
494                       e->xclient.data.l[1], e->xclient.data.l[2],
495                       client->window);
496             client_set_state(client, e->xclient.data.l[0],
497                              e->xclient.data.l[1], e->xclient.data.l[2]);
498         } else if (msgtype == prop_atoms.net_close_window) {
499             g_message("net_close_window for 0x%lx\n", client->window);
500             client_close(client);
501         } else if (msgtype == prop_atoms.net_active_window) {
502             g_message("net_active_window for 0x%lx\n", client->window);
503             if (screen_showing_desktop)
504                 screen_show_desktop(FALSE);
505             if (client->iconic)
506                 client_iconify(client, FALSE, TRUE);
507             else if (!client->frame->visible)
508                 /* if its not visible for other reasons, then don't mess
509                    with it */
510                 break;
511             if (client->shaded)
512                 client_shade(client, FALSE);
513             client_focus(client);
514             stacking_raise(client);
515         }
516         break;
517     case PropertyNotify:
518         /* validate cuz we query stuff off the client here */
519         if (!client_validate(client)) break;
520   
521         /* compress changes to a single property into a single change */
522         while (XCheckTypedWindowEvent(ob_display, e->type,
523                                       client->window, &ce)) {
524             /* XXX: it would be nice to compress ALL changes to a property,
525                not just changes in a row without other props between. */
526             if (ce.xproperty.atom != e->xproperty.atom) {
527                 XPutBackEvent(ob_display, &ce);
528                 break;
529             }
530         }
531
532         msgtype = e->xproperty.atom;
533         if (msgtype == XA_WM_NORMAL_HINTS) {
534             client_update_normal_hints(client);
535             /* normal hints can make a window non-resizable */
536             client_setup_decor_and_functions(client);
537         }
538         else if (msgtype == XA_WM_HINTS)
539             client_update_wmhints(client);
540         else if (msgtype == XA_WM_TRANSIENT_FOR) {
541             client_update_transient_for(client);
542             client_get_type(client);
543             /* type may have changed, so update the layer */
544             client_calc_layer(client);
545             client_setup_decor_and_functions(client);
546         }
547         else if (msgtype == prop_atoms.net_wm_name ||
548                  msgtype == prop_atoms.wm_name)
549             client_update_title(client);
550         else if (msgtype == prop_atoms.net_wm_icon_name ||
551                  msgtype == prop_atoms.wm_icon_name)
552             client_update_icon_title(client);
553         else if (msgtype == prop_atoms.wm_class)
554             client_update_class(client);
555         else if (msgtype == prop_atoms.wm_protocols) {
556             client_update_protocols(client);
557             client_setup_decor_and_functions(client);
558         }
559         else if (msgtype == prop_atoms.net_wm_strut)
560             client_update_strut(client);
561         else if (msgtype == prop_atoms.net_wm_icon)
562             client_update_icons(client);
563         else if (msgtype == prop_atoms.kwm_win_icon)
564             client_update_kwm_icon(client);
565     }
566 }